DCCL v4
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 
55 namespace dccl
56 {
57 namespace units
58 {
59 namespace qi = boost::spirit::qi;
60 namespace ascii = boost::spirit::ascii;
61 namespace phoenix = boost::phoenix;
62 
63 // Create a bimap of the available base dimensions
64 inline 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
86 inline 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
100 inline 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)
114 inline 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)
117 inline 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
123 template <typename Iterator>
124 bool 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
171 template <typename Iterator>
172 bool 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
218 inline 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
249 inline 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
302 inline 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 
310 inline void add_absolute(std::string& before, std::string& after)
311 {
312  before = "boost::units::absolute<" + before;
313  after += "> ";
314 }
315 
316 inline 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
371 inline 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
412 inline 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
446 inline 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
494 inline 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
525 inline 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
dccl
Dynamic Compact Control Language namespace.
Definition: any.h:46