DCCL v3
pb_plugin.cpp
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 #include <google/protobuf/compiler/plugin.h>
23 #include <google/protobuf/descriptor.h>
24 #include <google/protobuf/compiler/code_generator.h>
25 #include <iostream>
26 #include <sstream>
27 #include <set>
28 #include <boost/shared_ptr.hpp>
29 #include <google/protobuf/io/printer.h>
30 #include <google/protobuf/io/zero_copy_stream.h>
31 #include "option_extensions.pb.h"
32 #include "gen_units_class_plugin.h"
33 
34 std::set<std::string> systems_to_include_;
35 std::set<std::string> base_units_to_include_;
36 std::string filename_h_;
37 
38 
40  public:
41  DCCLGenerator() { }
42  ~DCCLGenerator() { }
43 
44 
45  // implements CodeGenerator ----------------------------------------
46  bool Generate(const google::protobuf::FileDescriptor* file,
47  const std::string& parameter,
48  google::protobuf::compiler::GeneratorContext* generator_context,
49  std::string* error) const;
50  private:
51  void generate_message(const google::protobuf::Descriptor* desc,
52  google::protobuf::compiler::GeneratorContext* generator_context,
53  boost::shared_ptr<std::string> message_unit_system = boost::shared_ptr<std::string>()) const;
54  void generate_field(const google::protobuf::FieldDescriptor* field,
55  google::protobuf::io::Printer* printer,
56  boost::shared_ptr<std::string> message_unit_system) const;
57  bool check_field_type(const google::protobuf::FieldDescriptor* field) const;
58 
59 };
60 
61 bool DCCLGenerator::check_field_type(const google::protobuf::FieldDescriptor* field) const
62 {
63  bool is_integer =
64  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_INT32 ||
65  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_INT64 ||
66  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_UINT32 ||
67  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_UINT64;
68 
69  bool is_float =
70  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE ||
71  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_FLOAT;
72 
73  if(!is_float && !is_integer)
74  {
75  throw std::runtime_error("Can only use (dccl.field).base_dimensions on numeric fields");
76  }
77  return is_integer;
78 }
79 
80 
81 bool DCCLGenerator::Generate(const google::protobuf::FileDescriptor* file,
82  const std::string& parameter,
83  google::protobuf::compiler::GeneratorContext* generator_context,
84  std::string* error) const
85 {
86  try
87  {
88  const std::string& filename = file->name();
89  filename_h_ = filename.substr(0, filename.find(".proto")) + ".pb.h";
90 // std::string filename_cc = filename.substr(0, filename.find(".proto")) + ".pb.cc";
91 
92  for(int message_i = 0, message_n = file->message_type_count(); message_i < message_n; ++message_i)
93  {
94  try
95  {
96  generate_message(file->message_type(message_i), generator_context);
97  }
98  catch(std::exception& e)
99  {
100  throw(std::runtime_error(std::string("Failed to generate DCCL code: \n") + e.what()));
101  }
102  }
103 
104  boost::shared_ptr<google::protobuf::io::ZeroCopyOutputStream> include_output(
105  generator_context->OpenForInsert(filename_h_, "includes"));
106  google::protobuf::io::Printer include_printer(include_output.get(), '$');
107  std::stringstream includes_ss;
108 
109  includes_ss << "#include <boost/units/quantity.hpp>" <<std::endl;
110  includes_ss << "#include <boost/units/absolute.hpp>" <<std::endl;
111  includes_ss << "#include <boost/units/dimensionless_type.hpp>" <<std::endl;
112  includes_ss << "#include <boost/units/make_scaled_unit.hpp>" <<std::endl;
113 
114  for(std::set<std::string>::const_iterator it = systems_to_include_.begin(), end = systems_to_include_.end(); it != end; ++it)
115  {
116  include_units_headers(*it, includes_ss);
117  }
118  for(std::set<std::string>::const_iterator it = base_units_to_include_.begin(), end = base_units_to_include_.end(); it != end; ++it)
119  {
120  include_base_unit_headers(*it, includes_ss);
121  }
122  include_printer.Print(includes_ss.str().c_str());
123 
124  return true;
125  }
126  catch (std::exception&e)
127  {
128  *error = e.what();
129  return false;
130  }
131 }
132 
133 
134 void DCCLGenerator::generate_message(const google::protobuf::Descriptor* desc, google::protobuf::compiler::GeneratorContext* generator_context, boost::shared_ptr<std::string> message_unit_system) const
135 {
136  try
137  {
138  boost::shared_ptr<google::protobuf::io::ZeroCopyOutputStream> output(
139  generator_context->OpenForInsert(filename_h_, "class_scope:" + desc->full_name()));
140  google::protobuf::io::Printer printer(output.get(), '$');
141 
142  if(desc->options().HasExtension(dccl::msg))
143  {
144 
145  if(desc->options().GetExtension(dccl::msg).id() != 0)
146  {
147  std::stringstream id_enum;
148  id_enum << "enum DCCLParameters { DCCL_ID = " << desc->options().GetExtension(dccl::msg).id() << ", " <<
149  " DCCL_MAX_BYTES = " << desc->options().GetExtension(dccl::msg).max_bytes() << " };\n";
150  printer.Print(id_enum.str().c_str());
151  }
152 
153 
154  // set message level unit system - used if fields do not specify
155  const dccl::DCCLMessageOptions& dccl_msg_options = desc->options().GetExtension(dccl::msg);
156  if(dccl_msg_options.has_unit_system())
157  {
158  message_unit_system.reset(new std::string(dccl_msg_options.unit_system()));
159  systems_to_include_.insert(dccl_msg_options.unit_system());
160  }
161  }
162 
163  for(int field_i = 0, field_n = desc->field_count(); field_i < field_n; ++field_i)
164  {
165  generate_field(desc->field(field_i), &printer, message_unit_system);
166  }
167 
168  for(int nested_type_i = 0, nested_type_n = desc->nested_type_count(); nested_type_i < nested_type_n; ++nested_type_i)
169  generate_message(desc->nested_type(nested_type_i), generator_context, message_unit_system);
170  }
171  catch(std::exception& e)
172  {
173  throw(std::runtime_error(std::string("Message: \n" + desc->full_name() + "\n" + e.what())));
174  }
175 }
176 
177 void DCCLGenerator::generate_field(const google::protobuf::FieldDescriptor* field, google::protobuf::io::Printer* printer, boost::shared_ptr<std::string> message_unit_system) const
178 {
179  try
180  {
181  const dccl::DCCLFieldOptions& dccl_field_options = field->options().GetExtension(dccl::field);
182 
183  if(!dccl_field_options.has_units()) {return;}
184 
185  if((dccl_field_options.units().has_base_dimensions() && dccl_field_options.units().has_derived_dimensions())||
186  (dccl_field_options.units().has_base_dimensions() && dccl_field_options.units().has_unit()) ||
187  (dccl_field_options.units().has_unit() && dccl_field_options.units().has_derived_dimensions()))
188  {
189  throw(std::runtime_error("May define either (dccl.field).units.base_dimensions or (dccl.field).units.derived_dimensions or (dccl.field).units.unit, but not more than one."));
190  }
191  else if(dccl_field_options.units().has_unit())
192  {
193  std::stringstream new_methods;
194 
195  construct_units_typedef_from_base_unit(field->name(), dccl_field_options.units().unit(), dccl_field_options.units().relative_temperature(), dccl_field_options.units().prefix(), new_methods);
196  construct_field_class_plugin(field->name(),
197  new_methods,
198  dccl::units::get_field_type_name(field->cpp_type()),
199  field->is_repeated());
200  printer->Print(new_methods.str().c_str());
201  base_units_to_include_.insert(dccl_field_options.units().unit());
202  }
203  else if(dccl_field_options.units().has_base_dimensions())
204  {
205 
206  std::stringstream new_methods;
207 
208  std::vector<double> powers;
209  std::vector<std::string> short_dimensions;
210  std::vector<std::string> dimensions;
211  if(dccl::units::parse_base_dimensions(dccl_field_options.units().base_dimensions().begin(),
212  dccl_field_options.units().base_dimensions().end(),
213  powers, short_dimensions, dimensions))
214  {
215  if(!dccl_field_options.units().has_system() && !message_unit_system)
216  throw(std::runtime_error(std::string("Field must have 'system' defined or message must have 'unit_system' defined when using 'base_dimensions'.")));
217 
218  // default to system set in the field, otherwise use the system set at the message level
219  const std::string& unit_system = (!dccl_field_options.units().has_system() && message_unit_system) ? *message_unit_system : dccl_field_options.units().system();
220 
221  construct_base_dims_typedef(dimensions, powers, field->name(), unit_system, dccl_field_options.units().relative_temperature(), dccl_field_options.units().prefix(), new_methods);
222 
223  construct_field_class_plugin(field->name(),
224  new_methods,
225  dccl::units::get_field_type_name(field->cpp_type()),
226  field->is_repeated());
227  printer->Print(new_methods.str().c_str());
228  systems_to_include_.insert(unit_system);
229  }
230  else
231  {
232  throw(std::runtime_error(std::string("Failed to parse base_dimensions string: \"" + dccl_field_options.units().base_dimensions() + "\"")));
233  }
234  }
235  else if(dccl_field_options.units().has_derived_dimensions())
236  {
237  std::stringstream new_methods;
238 
239  std::vector<std::string> operators;
240  std::vector<std::string> dimensions;
241  if(dccl::units::parse_derived_dimensions(dccl_field_options.units().derived_dimensions().begin(),
242  dccl_field_options.units().derived_dimensions().end(),
243  operators, dimensions))
244  {
245  if(!dccl_field_options.units().has_system() && !message_unit_system)
246  throw(std::runtime_error(std::string("Field must have 'system' defined or message must have 'unit_system' defined when using 'derived_dimensions'.")));
247  const std::string& unit_system = (!dccl_field_options.units().has_system() && message_unit_system) ? *message_unit_system : dccl_field_options.units().system();
248 
249  construct_derived_dims_typedef(dimensions, operators, field->name(), unit_system, dccl_field_options.units().relative_temperature(), dccl_field_options.units().prefix(), new_methods);
250 
251  construct_field_class_plugin(field->name(),
252  new_methods,
253  dccl::units::get_field_type_name(field->cpp_type()),
254  field->is_repeated());
255  printer->Print(new_methods.str().c_str());
256  systems_to_include_.insert(unit_system);
257  }
258  else
259  {
260  throw(std::runtime_error(std::string("Failed to parse derived_dimensions string: \"" + dccl_field_options.units().derived_dimensions() + "\"")));
261  }
262  }
263 
264  }
265  catch(std::exception& e)
266  {
267  throw(std::runtime_error(std::string("Field: \n" + field->DebugString() + "\n" + e.what())));
268  }
269 }
270 
271 
272 int main(int argc, char* argv[])
273 {
274  DCCLGenerator generator;
275  return google::protobuf::compiler::PluginMain(argc, argv, &generator);
276 }