DCCL v4
Loading...
Searching...
No Matches
gen_units_class_plugin.h
1// Copyright 2015-2023:
2// GobySoft, LLC (2013-)
3// Community contributors (see AUTHORS file)
4// File authors:
5// Toby Schneider <toby@gobysoft.org>
6// Stephanie Petillo <stephanie@gobysoft.org>
7//
8//
9// This file is part of the Dynamic Compact Control Language Library
10// ("DCCL").
11//
12// DCCL is free software: you can redistribute it and/or modify
13// it under the terms of the GNU Lesser General Public License as published by
14// the Free Software Foundation, either version 2.1 of the License, or
15// (at your option) any later version.
16//
17// DCCL is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU Lesser General Public License for more details.
21//
22// You should have received a copy of the GNU Lesser General Public License
23// along with DCCL. If not, see <http://www.gnu.org/licenses/>.
24#ifndef GenUnitsClassPlugin20150310H
25#define GenUnitsClassPlugin20150310H
26
27#include <google/protobuf/descriptor.h>
28
29#include <boost/config/warning_disable.hpp>
30#include <boost/lambda/lambda.hpp>
31
32#include <boost/phoenix/bind.hpp>
33#include <boost/phoenix/core.hpp>
34#include <boost/phoenix/operator.hpp>
35#include <boost/phoenix/stl.hpp>
36
37#include <boost/spirit/include/qi.hpp>
38#include <boost/spirit/include/qi_expect.hpp>
39#include <boost/spirit/include/qi_lit.hpp>
40
41#include <boost/algorithm/string/replace.hpp>
42
43#include <iostream>
44#include <sstream>
45#include <string>
46#include <vector>
47
48#include <boost/bimap.hpp>
49
51// Parsing methods for protobuf messages' units fields:
52// base_dimensions, derived_dimensions, unit_system
54
55namespace dccl
56{
57namespace units
58{
59namespace qi = boost::spirit::qi;
60namespace ascii = boost::spirit::ascii;
61namespace phoenix = boost::phoenix;
62
63// Create a bimap of the available base dimensions
64inline boost::bimap<std::string, char> make_dim_bimap()
65{
66 typedef boost::bimap<std::string, char> dim_bimap;
67 using dimension = dim_bimap::value_type;
68
69 dim_bimap dims;
70 dims.insert(dimension("length", 'L'));
71 dims.insert(dimension("time", 'T'));
72 dims.insert(dimension("mass", 'M'));
73 dims.insert(dimension("plane_angle", 'A'));
74 dims.insert(dimension("solid_angle", 'S'));
75 dims.insert(dimension("current", 'I'));
76 dims.insert(dimension("temperature", 'K'));
77 dims.insert(dimension("amount", 'N'));
78 dims.insert(dimension("luminous_intensity", 'J'));
79 dims.insert(dimension("information", 'B'));
80 dims.insert(dimension("dimensionless", '-'));
81
82 return dims;
83}
84
85// Create vectors of base dimension long and short strings from short (char) inputs
86inline void push_char_base(std::vector<std::string>& vc, std::vector<std::string>& vs,
87 const char& c)
88{
89 vc.emplace_back(1, c);
90
91 using bimap_type = boost::bimap<std::string, char>;
92 bimap_type dim_bimap = make_dim_bimap();
93
94 bimap_type::right_const_iterator it_right = dim_bimap.right.find(c);
95
96 vs.push_back(it_right->second);
97}
98
99// Create vectors of base dimension long and short strings from long (string) inputs
100inline void push_string_base(std::vector<std::string>& vc, std::vector<std::string>& vs,
101 const std::string& s)
102{
103 vs.push_back(s);
104
105 using bimap_type = boost::bimap<std::string, char>;
106 bimap_type dim_bimap = make_dim_bimap();
107
108 bimap_type::left_const_iterator it_left = dim_bimap.left.find(s);
109
110 vc.emplace_back(1, it_left->second);
111}
112
113// Make a vector of strings from char inputs (for creating the derived_dimensions operator vector)
114inline void push_char(std::vector<std::string>& vc, const char& c) { vc.emplace_back(1, c); }
115
116// Make a vector of strings from vector<char> inputs (for creating the derived_dimensions vector of strings)
117inline void push_char_vec(std::vector<std::string>& vc, const std::vector<char>& c)
118{
119 vc.emplace_back(c.begin(), c.end());
120}
121
122// Parser for base_dimensions input
123template <typename Iterator>
124bool parse_base_dimensions(Iterator first, Iterator last, std::vector<double>& base_dim_powers,
125 std::vector<std::string>& base_dim_chars,
126 std::vector<std::string>& base_dim_strings)
127{
128 //base_dim_chars = vc;
129 //base_dim_strings = vs;
130 //base_dim_powers = v;
131
132 using ascii::space;
133 using phoenix::push_back;
134 using qi::_1;
135 using qi::char_;
136 using qi::double_;
137 using qi::eps;
138 using qi::phrase_parse;
139
140 try
141 {
142 bool r = phrase_parse(
143 first, /* start iterator */
144 last, /* end iterator */
145 +((char_("LTMASIKNJB-")[phoenix::bind(&push_char_base, phoenix::ref(base_dim_chars),
146 phoenix::ref(base_dim_strings), _1)] |
147 ((ascii::string("length") | ascii::string("time") | ascii::string("mass") |
148 ascii::string("plane_angle") | ascii::string("solid_angle") |
149 ascii::string("current") | ascii::string("temperature") | ascii::string("amount") |
150 ascii::string("luminous_intensity") | ascii::string("information") |
151 ascii::string("dimensionless"))[phoenix::bind(
152 &push_string_base, phoenix::ref(base_dim_chars), phoenix::ref(base_dim_strings),
153 _1)])) >>
154 -(ascii::string("_base_dimension")) >>
155 (('^' > double_[push_back(phoenix::ref(base_dim_powers), _1)]) |
156 eps[push_back(phoenix::ref(base_dim_powers), 1)])),
157 space /* the skip-parser */
158 );
159
160 if (first != last) // fail if we did not get a full match
161 return false;
162 return r;
163 }
164 catch (const std::runtime_error& e)
165 {
166 return false;
167 }
168}
169
170// Parser for derived_dimensions input
171template <typename Iterator>
172bool parse_derived_dimensions(Iterator first, Iterator last,
173 std::vector<std::string>& derived_dim_operators,
174 std::vector<std::string>& derived_dim_strings)
175{
176 using ascii::space;
177 using phoenix::push_back;
178 using qi::_1;
179 using qi::char_;
180 using qi::double_;
181 using qi::eps;
182 using qi::phrase_parse;
183
184 try
185 {
186 std::vector<std::string> params;
187 bool r = boost::spirit::qi::parse(
188 first, last,
189 +((+char_("a-z1_"))[phoenix::bind(&push_char_vec, boost::phoenix::ref(params), _1)] >>
190 -(*char_(" ") >>
191 (char_("*/")[phoenix::bind(&push_char, phoenix::ref(derived_dim_operators), _1)] |
192 eps[push_back(phoenix::ref(derived_dim_operators), std::string("*"))]) >>
193 *char_(" "))));
194
195 if (derived_dim_operators.size())
196 derived_dim_operators.pop_back();
197
198 for (auto& param : params)
199 {
200 std::string::size_type dim_pos = param.find("_dimension");
201 if (dim_pos != std::string::npos)
202 param = param.substr(0, dim_pos);
203 //std::cout << *it << std::endl;
204 derived_dim_strings.push_back(param);
205 }
206
207 if (first != last) // fail if we did not get a full match
208 return false;
209 return r;
210 }
211 catch (const std::runtime_error& e)
212 {
213 return false;
214 }
215}
216
217// Extract field type name as string
218inline std::string get_field_type_name(google::protobuf::FieldDescriptor::CppType type)
219{
220 switch (type)
221 {
222 case google::protobuf::FieldDescriptor::CPPTYPE_INT32: return "google::protobuf::int32";
223 case google::protobuf::FieldDescriptor::CPPTYPE_INT64: return "google::protobuf::int64";
224 case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: return "google::protobuf::uint32";
225 case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: return "google::protobuf::uint64";
226 case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: return "double";
227 case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: return "float";
228 default: return "double";
229 }
230}
231
232} // namespace units
233} // namespace dccl
234
236// Print units class plugin pieces to some type of ostream
238
239// Example:
240// ClassName (Range)
241// fieldname (distance)
242// sysname (si)
243// dimtype_dimension (length_dimension)
244
245// Boost Units - Dimensions Reference:
246// http://www.boost.org/doc/libs/1_57_0/doc/html/boost_units/Reference.html#dimensions_reference
247
248// Generate necessary units systems headers
249inline void include_units_headers(const std::string& sysname, std::ostream& os)
250{
251 os << std::endl;
252 /* os <<std::endl; */
253 /* os <<"//===================" <<std::endl; */
254 /* os <<std::endl; */
255
256 // pre-defined systems from boost units:
257 // http://www.boost.org/doc/libs/1_54_0/boost/units/systems/
258
259 if (sysname == "si" || sysname == "boost::units::si" || sysname == "angle::radian")
260 {
261 os << "#include <boost/units/systems/si.hpp>" << std::endl;
262 }
263 else if (sysname == "cgs" || sysname == "boost::units::cgs")
264 {
265 os << "#include <boost/units/systems/cgs.hpp>" << std::endl;
266 }
267 else if (sysname == "celsius" || sysname == "boost::units::celsius" ||
268 sysname == "temperature::celsius")
269 {
270 os << "#include <boost/units/systems/temperature/celsius.hpp>" << std::endl;
271 }
272 else if (sysname == "fahrenheit" || sysname == "boost::units::fahrenheit" ||
273 sysname == "temperature::fahrenheit")
274 {
275 os << "#include <boost/units/systems/temperature/fahrenheit.hpp>" << std::endl;
276 }
277 else if (sysname == "degree" || sysname == "boost::units::degree" || sysname == "angle::degree")
278 {
279 os << "#include <boost/units/systems/angle/degrees.hpp>" << std::endl;
280 }
281 else if (sysname == "gradian" || sysname == "boost::units::gradian" ||
282 sysname == "angle::gradian")
283 {
284 os << "#include <boost/units/systems/angle/gradians.hpp>" << std::endl;
285 }
286 else if (sysname == "revolution" || sysname == "boost::units::revolution" ||
287 sysname == "angle::revolution")
288 {
289 os << "#include <boost/units/systems/angle/revolutions.hpp>" << std::endl;
290 }
291 else
292 {
293 //include necessary non-boost-units system headers
294 std::string sysname_sub = boost::replace_all_copy(sysname, "::", "/");
295 os << "#include \"" << sysname_sub << ".hpp\"" << std::endl;
296 }
297}
298
299// Generate necessary units systems headers for an individually defined base unit.
300// Unit must be available in this list:
301// http://www.boost.org/doc/libs/1_57_0/doc/html/boost_units/Reference.html#boost_units.Reference.alphabetical_listing_of_base_units
302inline void include_base_unit_headers(const std::string& base_unit_category_and_name,
303 std::ostream& os)
304{
305 os << std::endl;
306 std::string cat_name_sub = boost::replace_all_copy(base_unit_category_and_name, "::", "/");
307 os << "#include <boost/units/base_units/" << cat_name_sub << ".hpp>" << std::endl;
308}
309
310inline void include_custom_unit_headers(const std::string& header, std::ostream& os)
311{
312 os << std::endl;
313 os << "#include \"" << header << "\"" << std::endl;
314}
315
316inline void add_absolute(std::string& before, std::string& after)
317{
318 before = "boost::units::absolute<" + before;
319 after += "> ";
320}
321
322inline void add_prefix(const std::string& prefix, std::string& before, std::string& after)
323{
324 int power = 1;
325 if (prefix == "yotta")
326 power = 24;
327 else if (prefix == "zetta")
328 power = 21;
329 else if (prefix == "exa")
330 power = 18;
331 else if (prefix == "peta")
332 power = 15;
333 else if (prefix == "tera")
334 power = 12;
335 else if (prefix == "giga")
336 power = 9;
337 else if (prefix == "mega")
338 power = 6;
339 else if (prefix == "kilo")
340 power = 3;
341 else if (prefix == "hecto")
342 power = 2;
343 else if (prefix == "deka")
344 power = 1;
345 else if (prefix == "deci")
346 power = -1;
347 else if (prefix == "centi")
348 power = -2;
349 else if (prefix == "milli")
350 power = -3;
351 else if (prefix == "micro")
352 power = -6;
353 else if (prefix == "nano")
354 power = -9;
355 else if (prefix == "pico")
356 power = -12;
357 else if (prefix == "femto")
358 power = -15;
359 else if (prefix == "atto")
360 power = -18;
361 else if (prefix == "zepto")
362 power = -21;
363 else if (prefix == "yocto")
364 power = -24;
365 else
366 throw(std::runtime_error(std::string("Invalid SI prefix: " + prefix)));
367
368 std::stringstream power_ss;
369 power_ss << power;
370
371 before = "boost::units::make_scaled_unit<" + before;
372 after += ", boost::units::scale<10, boost::units::static_rational<" + power_ss.str() +
373 "> > >::type ";
374}
375
376// Generate a unit typedef when given derived_ or base_dimensions
377inline void construct_units_typedef_from_dimension(const std::string& fieldname,
378 const std::string& sysname, const bool& absolute,
379 const std::string& prefix, std::ostream& os)
380{
381 // Namespace the sysname if necessary
382 std::string sysname_ns;
383 if (sysname == "si" || sysname == "cgs" || sysname == "celsius" || sysname == "fahrenheit" ||
384 sysname == "degree" || sysname == "gradian" || sysname == "revolution")
385 sysname_ns = "boost::units::" + sysname + "::system";
386 else if (sysname == "boost::units::si" || sysname == "boost::units::cgs")
387 sysname_ns = sysname + "::system";
388 else if (sysname == "temperature::celsius" || sysname == "temperature::fahrenheit")
389 sysname_ns = boost::replace_all_copy(sysname, "temperature::", "boost::units::");
390 else if (sysname == "angle::degree" || sysname == "angle::gradian" ||
391 sysname == "angle::revolution")
392 sysname_ns = boost::replace_all_copy(sysname, "angle::", "boost::units::") + "::system";
393 else if (sysname == "angle::radian")
394 sysname_ns = "boost::units::si::system";
395 else
396 sysname_ns = sysname;
397
398 std::string before;
399 std::string after;
400
401 if (absolute && !prefix.empty())
402 throw(std::runtime_error(
403 std::string("'prefix' is not supported with an absolute temperature field")));
404
405 if (absolute)
406 add_absolute(before, after);
407
408 if (!prefix.empty())
409 add_prefix(prefix, before, after);
410
411 // Typedef the unit
412 os << "typedef " << before << "boost::units::unit<" << fieldname << "_dimension," << sysname_ns
413 << "> " << after << fieldname << "_unit;" << std::endl;
414 os << std::endl;
415}
416
417// Generate a dimension typedef when given base_dimensions
418inline void construct_base_dims_typedef(const std::vector<std::string>& dim_vec,
419 const std::vector<double>& power_vec,
420 const std::string& fieldname, const std::string& sysname,
421 const bool& rel_temperature, const std::string& prefix,
422 std::ostream& os)
423{
425 bool temperature_dimension = false;
426 if (dim_vec[0] == "temperature" && dim_vec.size() == 1)
427 temperature_dimension = true;
428 if (dim_vec[0] == "dimensionless" && dim_vec.size() == 1)
429 {
430 os << "typedef boost::units::dimensionless_type " << fieldname << "_dimension;"
431 << std::endl;
432 ;
433 }
434 else
435 {
436 os << "typedef boost::units::derived_dimension< ";
437 for (std::size_t i = 0, n = dim_vec.size(); i < n; i++)
438 {
439 os << "boost::units::" << dim_vec[i] << "_base_dimension," << power_vec[i];
440 if (i != dim_vec.size() - 1)
441 os << ", ";
442 }
443 os << " >::type " << fieldname << "_dimension;" << std::endl;
444 }
445 os << std::endl;
446
447 construct_units_typedef_from_dimension(fieldname, sysname,
448 temperature_dimension && !rel_temperature, prefix, os);
449}
450
451// Generate a dimension typedef when given derived_dimensions
452inline void construct_derived_dims_typedef(const std::vector<std::string>& dim_vec,
453 const std::vector<std::string>& operator_vec,
454 const std::string& fieldname, const std::string& sysname,
455 const bool& rel_temperature, const std::string& prefix,
456 std::ostream& os)
457{
459
460 bool temperature_dimension = false;
461
462 if (dim_vec.size() == 1)
463 {
464 if (dim_vec[0] == "temperature")
465 temperature_dimension = true;
466 if (dim_vec[0] == "dimensionless")
467 os << "typedef boost::units::dimensionless_type " << fieldname << "_dimension;"
468 << std::endl;
469 else
470 os << "typedef boost::units::" << dim_vec[0] << "_dimension " << fieldname
471 << "_dimension;" << std::endl;
472 }
473 else
474 { //construct new dimension type with mpl divides/times calls based on operators and powers
475 //see example here:
476 //http://www.boost.org/doc/libs/1_57_0/doc/html/boost_units/Examples.html#boost_units.Examples.DimensionExample
477 os << "typedef ";
478 std::string result = dim_vec[0];
479 for (std::size_t i = 0, n = operator_vec.size(); i < n; i++)
480 {
481 if (operator_vec[i] == "/")
482 result = "boost::mpl::divides<boost::units::" + result +
483 "_dimension,boost::units::" + dim_vec[i + 1] + "_dimension>::type";
484 else if (operator_vec[i] == "*")
485 result = "boost::mpl::times<boost::units::" + result +
486 "_dimension,boost::units::" + dim_vec[i + 1] + "_dimension>::type";
487 }
488 os << result << " " << fieldname << "_dimension;" << std::endl;
489 }
490 os << std::endl;
491
492 construct_units_typedef_from_dimension(fieldname, sysname,
493 temperature_dimension && !rel_temperature, prefix, os);
494}
495
496//=============VVVV
497
498//===========
499// Generate a unit typedef when given base_unit
500inline void construct_units_typedef_from_base_unit(const std::string& fieldname,
501 const std::string& base_unit_category_and_name,
502 const bool& rel_temperature,
503 const std::string& prefix, std::ostream& os)
504{
505 bool temperature_unit = false;
506
507 //expect to see "temperature::celsius" or "temperature::fahrenheit" or "si::kelvin"
508 if ((base_unit_category_and_name.find("temperature") != std::string::npos) ||
509 (base_unit_category_and_name.find("kelvin") != std::string::npos))
510 temperature_unit = true;
511
512 bool absolute = temperature_unit && !rel_temperature;
513
514 std::string before;
515 std::string after;
516 if (absolute)
517 add_absolute(before, after);
518
519 if (!prefix.empty())
520 add_prefix(prefix, before, after);
521
522 // Namespace and typedef the unit
523 os << "typedef " << before << "boost::units::" << base_unit_category_and_name
524 << "_base_unit::unit_type " << after << fieldname << "_unit;" << std::endl;
525
526 os << std::endl;
527}
528
529//===========
530// Generate a unit typedef when given custom unit
531inline void construct_units_typedef_from_custom_unit(const std::string& fieldname,
532 const std::string& unit_name,
533 const bool& rel_temperature,
534 const std::string& prefix, std::ostream& os)
535{
536 bool temperature_unit = false;
537
538 //expect to see "temperature::celsius" or "temperature::fahrenheit" or "si::kelvin"
539 if ((unit_name.find("temperature") != std::string::npos) ||
540 (unit_name.find("kelvin") != std::string::npos))
541 temperature_unit = true;
542
543 bool absolute = temperature_unit && !rel_temperature;
544
545 std::string before;
546 std::string after;
547 if (absolute)
548 add_absolute(before, after);
549
550 if (!prefix.empty())
551 add_prefix(prefix, before, after);
552
553 // Namespace and typedef the unit
554 os << "typedef " << before << unit_name << " " << after << fieldname << "_unit;" << std::endl;
555 os << std::endl;
556}
557
558//===========
559// Generate the body of the units plugin for the message class
560inline void construct_field_class_plugin(const std::string& fieldname, std::ostream& os,
561 const std::string& value_type, bool is_repeated)
562{
564
565 // Overloading set_fieldname to accept boost units, i.e. quantity<unit<fieldname_dimension,sysname::system> >
566 os << "template<typename Quantity >" << std::endl;
567 os << " void set_" << fieldname << "_with_units(";
568 if (is_repeated)
569 os << "int index, ";
570 os << "Quantity value_w_units)" << std::endl;
571 os << " { set_" << fieldname << "(";
572 if (is_repeated)
573 os << "index, ";
574 os << "boost::units::quantity<" << fieldname << "_unit," << value_type
575 << " >(value_w_units).value() ); };" << std::endl;
576 os << std::endl;
577
578 if (is_repeated)
579 {
580 os << "template<typename Quantity >" << std::endl;
581 os << " void add_" << fieldname << "_with_units(Quantity value_w_units)" << std::endl;
582 os << " { add_" << fieldname << "(boost::units::quantity<" << fieldname << "_unit,"
583 << value_type << " >(value_w_units).value() ); };" << std::endl;
584 os << std::endl;
585 }
586
587 //returns whatever units are requested
588 os << "template<typename Quantity >" << std::endl;
589 os << " Quantity " << fieldname << "_with_units(";
590 if (is_repeated)
591 os << "int index";
592 os << ") const" << std::endl;
593 os << " { return Quantity(" << fieldname << "(";
594 if (is_repeated)
595 os << "index";
596 os << ") * " << fieldname << "_unit()); };" << std::endl;
597 os << std::endl;
598
599 //returns same units only
600 os << "boost::units::quantity< " << fieldname << "_unit," << value_type << " > " << fieldname
601 << "_with_units(";
602 if (is_repeated)
603 os << "int index";
604 os << ") const" << std::endl;
605 os << " { return " << fieldname << "_with_units<boost::units::quantity< " << fieldname
606 << "_unit," << value_type << " > >(";
607 if (is_repeated)
608 os << "index";
609 os << "); };" << std::endl;
610 os << std::endl;
611}
612
613//=============^^^^
614
615#endif
Dynamic Compact Control Language namespace.
Definition any.h:47