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