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 add_absolute(std::string& before, std::string& after)
311{
312 before = "boost::units::absolute<" + before;
313 after += "> ";
314}
315
316inline void add_prefix(const std::string& prefix, std::string& before, std::string& after)
317{
318 int power = 1;
319 if (prefix == "yotta")
320 power = 24;
321 else if (prefix == "zetta")
322 power = 21;
323 else if (prefix == "exa")
324 power = 18;
325 else if (prefix == "peta")
326 power = 15;
327 else if (prefix == "tera")
328 power = 12;
329 else if (prefix == "giga")
330 power = 9;
331 else if (prefix == "mega")
332 power = 6;
333 else if (prefix == "kilo")
334 power = 3;
335 else if (prefix == "hecto")
336 power = 2;
337 else if (prefix == "deka")
338 power = 1;
339 else if (prefix == "deci")
340 power = -1;
341 else if (prefix == "centi")
342 power = -2;
343 else if (prefix == "milli")
344 power = -3;
345 else if (prefix == "micro")
346 power = -6;
347 else if (prefix == "nano")
348 power = -9;
349 else if (prefix == "pico")
350 power = -12;
351 else if (prefix == "femto")
352 power = -15;
353 else if (prefix == "atto")
354 power = -18;
355 else if (prefix == "zepto")
356 power = -21;
357 else if (prefix == "yocto")
358 power = -24;
359 else
360 throw(std::runtime_error(std::string("Invalid SI prefix: " + prefix)));
361
362 std::stringstream power_ss;
363 power_ss << power;
364
365 before = "boost::units::make_scaled_unit<" + before;
366 after += ", boost::units::scale<10, boost::units::static_rational<" + power_ss.str() +
367 "> > >::type ";
368}
369
370// Generate a unit typedef when given derived_ or base_dimensions
371inline void construct_units_typedef_from_dimension(const std::string& fieldname,
372 const std::string& sysname, const bool& absolute,
373 const std::string& prefix, std::ostream& os)
374{
375 // Namespace the sysname if necessary
376 std::string sysname_ns;
377 if (sysname == "si" || sysname == "cgs" || sysname == "celsius" || sysname == "fahrenheit" ||
378 sysname == "degree" || sysname == "gradian" || sysname == "revolution")
379 sysname_ns = "boost::units::" + sysname + "::system";
380 else if (sysname == "boost::units::si" || sysname == "boost::units::cgs")
381 sysname_ns = sysname + "::system";
382 else if (sysname == "temperature::celsius" || sysname == "temperature::fahrenheit")
383 sysname_ns = boost::replace_all_copy(sysname, "temperature::", "boost::units::");
384 else if (sysname == "angle::degree" || sysname == "angle::gradian" ||
385 sysname == "angle::revolution")
386 sysname_ns = boost::replace_all_copy(sysname, "angle::", "boost::units::") + "::system";
387 else if (sysname == "angle::radian")
388 sysname_ns = "boost::units::si::system";
389 else
390 sysname_ns = sysname;
391
392 std::string before;
393 std::string after;
394
395 if (absolute && !prefix.empty())
396 throw(std::runtime_error(
397 std::string("'prefix' is not supported with an absolute temperature field")));
398
399 if (absolute)
400 add_absolute(before, after);
401
402 if (!prefix.empty())
403 add_prefix(prefix, before, after);
404
405 // Typedef the unit
406 os << "typedef " << before << "boost::units::unit<" << fieldname << "_dimension," << sysname_ns
407 << "> " << after << fieldname << "_unit;" << std::endl;
408 os << std::endl;
409}
410
411// Generate a dimension typedef when given base_dimensions
412inline void construct_base_dims_typedef(const std::vector<std::string>& dim_vec,
413 const std::vector<double>& power_vec,
414 const std::string& fieldname, const std::string& sysname,
415 const bool& rel_temperature, const std::string& prefix,
416 std::ostream& os)
417{
419 bool temperature_dimension = false;
420 if (dim_vec[0] == "temperature" && dim_vec.size() == 1)
421 temperature_dimension = true;
422 if (dim_vec[0] == "dimensionless" && dim_vec.size() == 1)
423 {
424 os << "typedef boost::units::dimensionless_type " << fieldname << "_dimension;"
425 << std::endl;
426 ;
427 }
428 else
429 {
430 os << "typedef boost::units::derived_dimension< ";
431 for (std::size_t i = 0, n = dim_vec.size(); i < n; i++)
432 {
433 os << "boost::units::" << dim_vec[i] << "_base_dimension," << power_vec[i];
434 if (i != dim_vec.size() - 1)
435 os << ", ";
436 }
437 os << " >::type " << fieldname << "_dimension;" << std::endl;
438 }
439 os << std::endl;
440
441 construct_units_typedef_from_dimension(fieldname, sysname,
442 temperature_dimension && !rel_temperature, prefix, os);
443}
444
445// Generate a dimension typedef when given derived_dimensions
446inline void construct_derived_dims_typedef(const std::vector<std::string>& dim_vec,
447 const std::vector<std::string>& operator_vec,
448 const std::string& fieldname, const std::string& sysname,
449 const bool& rel_temperature, const std::string& prefix,
450 std::ostream& os)
451{
453
454 bool temperature_dimension = false;
455
456 if (dim_vec.size() == 1)
457 {
458 if (dim_vec[0] == "temperature")
459 temperature_dimension = true;
460 if (dim_vec[0] == "dimensionless")
461 os << "typedef boost::units::dimensionless_type " << fieldname << "_dimension;"
462 << std::endl;
463 else
464 os << "typedef boost::units::" << dim_vec[0] << "_dimension " << fieldname
465 << "_dimension;" << std::endl;
466 }
467 else
468 { //construct new dimension type with mpl divides/times calls based on operators and powers
469 //see example here:
470 //http://www.boost.org/doc/libs/1_57_0/doc/html/boost_units/Examples.html#boost_units.Examples.DimensionExample
471 os << "typedef ";
472 std::string result = dim_vec[0];
473 for (std::size_t i = 0, n = operator_vec.size(); i < n; i++)
474 {
475 if (operator_vec[i] == "/")
476 result = "boost::mpl::divides<boost::units::" + result +
477 "_dimension,boost::units::" + dim_vec[i + 1] + "_dimension>::type";
478 else if (operator_vec[i] == "*")
479 result = "boost::mpl::times<boost::units::" + result +
480 "_dimension,boost::units::" + dim_vec[i + 1] + "_dimension>::type";
481 }
482 os << result << " " << fieldname << "_dimension;" << std::endl;
483 }
484 os << std::endl;
485
486 construct_units_typedef_from_dimension(fieldname, sysname,
487 temperature_dimension && !rel_temperature, prefix, os);
488}
489
490//=============VVVV
491
492//===========
493// Generate a unit typedef when given base_unit
494inline void construct_units_typedef_from_base_unit(const std::string& fieldname,
495 const std::string& base_unit_category_and_name,
496 const bool& rel_temperature,
497 const std::string& prefix, std::ostream& os)
498{
499 bool temperature_unit = false;
500
501 //expect to see "temperature::celsius" or "temperature::fahrenheit" or "si::kelvin"
502 if ((base_unit_category_and_name.find("temperature") != std::string::npos) ||
503 (base_unit_category_and_name.find("kelvin") != std::string::npos))
504 temperature_unit = true;
505
506 bool absolute = temperature_unit && !rel_temperature;
507
508 std::string before;
509 std::string after;
510 if (absolute)
511 add_absolute(before, after);
512
513 if (!prefix.empty())
514 add_prefix(prefix, before, after);
515
516 // Namespace and typedef the unit
517 os << "typedef " << before << "boost::units::" << base_unit_category_and_name
518 << "_base_unit::unit_type " << after << fieldname << "_unit;" << std::endl;
519
520 os << std::endl;
521}
522
523//===========
524// Generate the body of the units plugin for the message class
525inline void construct_field_class_plugin(const std::string& fieldname, std::ostream& os,
526 const std::string& value_type, bool is_repeated)
527{
529
530 // Overloading set_fieldname to accept boost units, i.e. quantity<unit<fieldname_dimension,sysname::system> >
531 os << "template<typename Quantity >" << std::endl;
532 os << " void set_" << fieldname << "_with_units(";
533 if (is_repeated)
534 os << "int index, ";
535 os << "Quantity value_w_units)" << std::endl;
536 os << " { set_" << fieldname << "(";
537 if (is_repeated)
538 os << "index, ";
539 os << "boost::units::quantity<" << fieldname << "_unit," << value_type
540 << " >(value_w_units).value() ); };" << std::endl;
541 os << std::endl;
542
543 if (is_repeated)
544 {
545 os << "template<typename Quantity >" << std::endl;
546 os << " void add_" << fieldname << "_with_units(Quantity value_w_units)" << std::endl;
547 os << " { add_" << fieldname << "(boost::units::quantity<" << fieldname << "_unit,"
548 << value_type << " >(value_w_units).value() ); };" << std::endl;
549 os << std::endl;
550 }
551
552 //returns whatever units are requested
553 os << "template<typename Quantity >" << std::endl;
554 os << " Quantity " << fieldname << "_with_units(";
555 if (is_repeated)
556 os << "int index";
557 os << ") const" << std::endl;
558 os << " { return Quantity(" << fieldname << "(";
559 if (is_repeated)
560 os << "index";
561 os << ") * " << fieldname << "_unit()); };" << std::endl;
562 os << std::endl;
563
564 //returns same units only
565 os << "boost::units::quantity< " << fieldname << "_unit," << value_type << " > " << fieldname
566 << "_with_units(";
567 if (is_repeated)
568 os << "int index";
569 os << ") const" << std::endl;
570 os << " { return " << fieldname << "_with_units<boost::units::quantity< " << fieldname
571 << "_unit," << value_type << " > >(";
572 if (is_repeated)
573 os << "index";
574 os << "); };" << std::endl;
575 os << std::endl;
576}
577
578//=============^^^^
579
580#endif
Dynamic Compact Control Language namespace.
Definition any.h:47