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