DCCL v4
pb_plugin.cpp
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 // 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 #include "gen_units_class_plugin.h"
26 #include "option_extensions.pb.h"
27 #include <boost/algorithm/string.hpp>
28 #include <fstream>
29 #include <google/protobuf/compiler/code_generator.h>
30 #include <google/protobuf/compiler/plugin.h>
31 #include <google/protobuf/descriptor.h>
32 #include <google/protobuf/io/printer.h>
33 #include <google/protobuf/io/zero_copy_stream.h>
34 #include <iostream>
35 #include <memory>
36 #include <set>
37 #include <sstream>
38 
39 std::set<std::string> systems_to_include_;
40 std::set<std::string> base_units_to_include_;
41 std::string filename_h_;
42 std::string load_file_cpp_;
43 std::shared_ptr<std::fstream> load_file_output_;
44 
45 std::string load_file_base_{
46  R"DEL(#include <dccl/codec.h>
47 
48 // DO NOT REMOVE: required by loader code
49 std::vector<const google::protobuf::Descriptor*> descriptors;
50 template<typename PB> struct DCCLLoader { DCCLLoader() { descriptors.push_back(PB::descriptor()); }};
51 // END: required by loader code
52 
53 
54 extern "C"
55 {
56  void dccl3_load(dccl::Codec* dccl)
57  {
58  // DO NOT REMOVE: required by loader code
59  for(auto d : descriptors)
60  dccl->load(d);
61  // END: required by loader code
62  }
63 
64  void dccl3_unload(dccl::Codec* dccl)
65  {
66  // DO NOT REMOVE: required by loader code
67  for(auto d : descriptors)
68  dccl->unload(d);
69  // END: required by loader code
70  }
71 }
72 
73 // BEGIN (TO END OF FILE): AUTOGENERATED LOADERS
74 )DEL"};
75 
77 {
78  public:
79  DCCLGenerator() = default;
80  ~DCCLGenerator() override = default;
81 
82  // implements CodeGenerator ----------------------------------------
83  bool Generate(const google::protobuf::FileDescriptor* file, const std::string& parameter,
84  google::protobuf::compiler::GeneratorContext* generator_context,
85  std::string* error) const override;
86 
87  private:
88  void generate_message(
89  const google::protobuf::Descriptor* desc,
90  google::protobuf::compiler::GeneratorContext* generator_context,
91  std::shared_ptr<std::string> message_unit_system = std::shared_ptr<std::string>()) const;
92  void generate_field(const google::protobuf::FieldDescriptor* field,
93  google::protobuf::io::Printer* printer,
94  std::shared_ptr<std::string> message_unit_system) const;
95  bool check_field_type(const google::protobuf::FieldDescriptor* field) const;
96 
97  void generate_load_file_headers() const;
98  void generate_load_file_message_loader(const google::protobuf::Descriptor* desc) const;
99 };
100 
101 bool DCCLGenerator::check_field_type(const google::protobuf::FieldDescriptor* field) const
102 {
103  bool is_integer = field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_INT32 ||
104  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_INT64 ||
105  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_UINT32 ||
106  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_UINT64;
107 
108  bool is_float = field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE ||
109  field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_FLOAT;
110 
111  if (!is_float && !is_integer)
112  {
113  throw std::runtime_error("Can only use (dccl.field).base_dimensions on numeric fields");
114  }
115  return is_integer;
116 }
117 
118 bool DCCLGenerator::Generate(const google::protobuf::FileDescriptor* file,
119  const std::string& parameter,
120  google::protobuf::compiler::GeneratorContext* generator_context,
121  std::string* error) const
122 {
123  std::vector<std::pair<std::string, std::string>> options;
124  google::protobuf::compiler::ParseGeneratorParameter(parameter, &options);
125 
126  for (const auto& option : options)
127  {
128  const auto& key = option.first;
129  const auto& value = option.second;
130  if (key == "dccl3_load_file")
131  {
132  load_file_cpp_ = value;
133  load_file_output_ = std::make_shared<std::fstream>(
134  load_file_cpp_, std::ios::in | std::ios::out | std::ios::app);
135 
136  if (!load_file_output_->is_open())
137  {
138  *error = "Failed to open dccl3_load_file: " + load_file_cpp_;
139  return false;
140  }
141  }
142  else
143  {
144  *error = "Unknown parameter: " + key;
145  return false;
146  }
147  }
148 
149  try
150  {
151  const std::string& filename = file->name();
152  filename_h_ = filename.substr(0, filename.find(".proto")) + ".pb.h";
153  // std::string filename_cc = filename.substr(0, filename.find(".proto")) + ".pb.cc";
154 
155  if (load_file_output_)
156  generate_load_file_headers();
157 
158  for (int message_i = 0, message_n = file->message_type_count(); message_i < message_n;
159  ++message_i)
160  {
161  try
162  {
163  generate_message(file->message_type(message_i), generator_context);
164  }
165  catch (std::exception& e)
166  {
167  throw(
168  std::runtime_error(std::string("Failed to generate DCCL code: \n") + e.what()));
169  }
170  }
171 
172  std::shared_ptr<google::protobuf::io::ZeroCopyOutputStream> include_output(
173  generator_context->OpenForInsert(filename_h_, "includes"));
174  google::protobuf::io::Printer include_printer(include_output.get(), '$');
175  std::stringstream includes_ss;
176 
177  includes_ss << "#include <boost/units/quantity.hpp>" << std::endl;
178  includes_ss << "#include <boost/units/absolute.hpp>" << std::endl;
179  includes_ss << "#include <boost/units/dimensionless_type.hpp>" << std::endl;
180  includes_ss << "#include <boost/units/make_scaled_unit.hpp>" << std::endl;
181 
182  for (const auto& it : systems_to_include_) { include_units_headers(it, includes_ss); }
183  for (const auto& it : base_units_to_include_)
184  { include_base_unit_headers(it, includes_ss); }
185  include_printer.Print(includes_ss.str().c_str());
186 
187  return true;
188  }
189  catch (std::exception& e)
190  {
191  *error = e.what();
192  return false;
193  }
194 }
195 
196 void DCCLGenerator::generate_message(
197  const google::protobuf::Descriptor* desc,
198  google::protobuf::compiler::GeneratorContext* generator_context,
199  std::shared_ptr<std::string> message_unit_system) const
200 {
201  try
202  {
203  {
204  std::shared_ptr<google::protobuf::io::ZeroCopyOutputStream> output(
205  generator_context->OpenForInsert(filename_h_, "class_scope:" + desc->full_name()));
206  google::protobuf::io::Printer printer(output.get(), '$');
207 
208  if (desc->options().HasExtension(dccl::msg))
209  {
210  if (desc->options().GetExtension(dccl::msg).id() != 0)
211  {
212  std::stringstream id_enum;
213  id_enum << "enum DCCLParameters { DCCL_ID = "
214  << desc->options().GetExtension(dccl::msg).id() << ", "
215  << " DCCL_MAX_BYTES = "
216  << desc->options().GetExtension(dccl::msg).max_bytes() << " };\n";
217  printer.Print(id_enum.str().c_str());
218 
219  if (load_file_output_)
220  generate_load_file_message_loader(desc);
221  }
222 
223  // set message level unit system - used if fields do not specify
224  const dccl::DCCLMessageOptions& dccl_msg_options =
225  desc->options().GetExtension(dccl::msg);
226  if (dccl_msg_options.has_unit_system())
227  {
228  message_unit_system.reset(new std::string(dccl_msg_options.unit_system()));
229  systems_to_include_.insert(dccl_msg_options.unit_system());
230  }
231  }
232 
233  for (int field_i = 0, field_n = desc->field_count(); field_i < field_n; ++field_i)
234  { generate_field(desc->field(field_i), &printer, message_unit_system); }
235 
236  for (int nested_type_i = 0, nested_type_n = desc->nested_type_count();
237  nested_type_i < nested_type_n; ++nested_type_i)
238  generate_message(desc->nested_type(nested_type_i), generator_context,
239  message_unit_system);
240  }
241  }
242  catch (std::exception& e)
243  {
244  throw(std::runtime_error(std::string("Message: \n" + desc->full_name() + "\n" + e.what())));
245  }
246 }
247 
248 void DCCLGenerator::generate_field(const google::protobuf::FieldDescriptor* field,
249  google::protobuf::io::Printer* printer,
250  std::shared_ptr<std::string> message_unit_system) const
251 {
252  try
253  {
254  const dccl::DCCLFieldOptions& dccl_field_options =
255  field->options().GetExtension(dccl::field);
256 
257  if (!dccl_field_options.has_units())
258  {
259  return;
260  }
261 
262  if ((dccl_field_options.units().has_base_dimensions() &&
263  dccl_field_options.units().has_derived_dimensions()) ||
264  (dccl_field_options.units().has_base_dimensions() &&
265  dccl_field_options.units().has_unit()) ||
266  (dccl_field_options.units().has_unit() &&
267  dccl_field_options.units().has_derived_dimensions()))
268  {
269  throw(std::runtime_error("May define either (dccl.field).units.base_dimensions or "
270  "(dccl.field).units.derived_dimensions or "
271  "(dccl.field).units.unit, but not more than one."));
272  }
273  else if (dccl_field_options.units().has_unit())
274  {
275  std::stringstream new_methods;
276 
277  construct_units_typedef_from_base_unit(
278  field->name(), dccl_field_options.units().unit(),
279  dccl_field_options.units().relative_temperature(),
280  dccl_field_options.units().prefix(), new_methods);
281  construct_field_class_plugin(field->name(), new_methods,
282  dccl::units::get_field_type_name(field->cpp_type()),
283  field->is_repeated());
284  printer->Print(new_methods.str().c_str());
285  base_units_to_include_.insert(dccl_field_options.units().unit());
286  }
287  else if (dccl_field_options.units().has_base_dimensions())
288  {
289  std::stringstream new_methods;
290 
291  std::vector<double> powers;
292  std::vector<std::string> short_dimensions;
293  std::vector<std::string> dimensions;
294  if (dccl::units::parse_base_dimensions(
295  dccl_field_options.units().base_dimensions().begin(),
296  dccl_field_options.units().base_dimensions().end(), powers, short_dimensions,
297  dimensions))
298  {
299  if (!dccl_field_options.units().has_system() && !message_unit_system)
300  throw(std::runtime_error(
301  std::string("Field must have 'system' defined or message must have "
302  "'unit_system' defined when using 'base_dimensions'.")));
303 
304  // default to system set in the field, otherwise use the system set at the message level
305  const std::string& unit_system =
306  (!dccl_field_options.units().has_system() && message_unit_system)
307  ? *message_unit_system
308  : dccl_field_options.units().system();
309 
310  construct_base_dims_typedef(dimensions, powers, field->name(), unit_system,
311  dccl_field_options.units().relative_temperature(),
312  dccl_field_options.units().prefix(), new_methods);
313 
314  construct_field_class_plugin(field->name(), new_methods,
315  dccl::units::get_field_type_name(field->cpp_type()),
316  field->is_repeated());
317  printer->Print(new_methods.str().c_str());
318  systems_to_include_.insert(unit_system);
319  }
320  else
321  {
322  throw(std::runtime_error(std::string("Failed to parse base_dimensions string: \"" +
323  dccl_field_options.units().base_dimensions() +
324  "\"")));
325  }
326  }
327  else if (dccl_field_options.units().has_derived_dimensions())
328  {
329  std::stringstream new_methods;
330 
331  std::vector<std::string> operators;
332  std::vector<std::string> dimensions;
333  if (dccl::units::parse_derived_dimensions(
334  dccl_field_options.units().derived_dimensions().begin(),
335  dccl_field_options.units().derived_dimensions().end(), operators, dimensions))
336  {
337  if (!dccl_field_options.units().has_system() && !message_unit_system)
338  throw(std::runtime_error(
339  std::string("Field must have 'system' defined or message must have "
340  "'unit_system' defined when using 'derived_dimensions'.")));
341  const std::string& unit_system =
342  (!dccl_field_options.units().has_system() && message_unit_system)
343  ? *message_unit_system
344  : dccl_field_options.units().system();
345 
346  construct_derived_dims_typedef(dimensions, operators, field->name(), unit_system,
347  dccl_field_options.units().relative_temperature(),
348  dccl_field_options.units().prefix(), new_methods);
349 
350  construct_field_class_plugin(field->name(), new_methods,
351  dccl::units::get_field_type_name(field->cpp_type()),
352  field->is_repeated());
353  printer->Print(new_methods.str().c_str());
354  systems_to_include_.insert(unit_system);
355  }
356  else
357  {
358  throw(std::runtime_error(
359  std::string("Failed to parse derived_dimensions string: \"" +
360  dccl_field_options.units().derived_dimensions() + "\"")));
361  }
362  }
363  }
364  catch (std::exception& e)
365  {
366  throw(
367  std::runtime_error(std::string("Field: \n" + field->DebugString() + "\n" + e.what())));
368  }
369 }
370 
371 void DCCLGenerator::generate_load_file_headers() const
372 {
373  bool file_is_empty = (load_file_output_->peek() == std::ifstream::traits_type::eof());
374  load_file_output_->clear();
375 
376  bool header_already_written = false;
377  if (file_is_empty)
378  {
379  *load_file_output_ << load_file_base_ << std::flush;
380  }
381  else
382  {
383  for (std::string line; getline(*load_file_output_, line);)
384  {
385  if (line.find("\"" + filename_h_ + "\"") != std::string::npos)
386  {
387  header_already_written = true;
388  break;
389  }
390  }
391  }
392  // clear EOF
393  load_file_output_->clear();
394  // seek to the end
395  load_file_output_->seekp(0, std::ios_base::end);
396 
397  if (!header_already_written)
398  *load_file_output_ << "#include \"" << filename_h_ << "\"" << std::endl;
399 }
400 
401 void DCCLGenerator::generate_load_file_message_loader(
402  const google::protobuf::Descriptor* desc) const
403 {
404  // seek to the beginning
405  load_file_output_->seekp(0, std::ios_base::beg);
406 
407  // cpp class name
408  std::string cpp_name = desc->full_name();
409  boost::algorithm::replace_all(cpp_name, ".", "::");
410 
411  // lower case class name with underscores
412  std::string loader_name = desc->full_name() + "_loader";
413  boost::algorithm::replace_all(loader_name, ".", "_");
414  boost::algorithm::to_lower(loader_name);
415 
416  bool loader_already_written = false;
417  for (std::string line; getline(*load_file_output_, line);)
418  {
419  if (line.find("<" + cpp_name + ">") != std::string::npos)
420  {
421  loader_already_written = true;
422  break;
423  }
424  }
425  load_file_output_->clear();
426  load_file_output_->seekp(0, std::ios_base::end);
427 
428  if (!loader_already_written)
429  *load_file_output_ << "DCCLLoader<" << cpp_name << "> " << loader_name << ";" << std::endl;
430 }
431 
432 int main(int argc, char* argv[])
433 {
434  DCCLGenerator generator;
435  return google::protobuf::compiler::PluginMain(argc, argv, &generator);
436 }
dccl::DCCLFieldOptions
Definition: option_extensions.pb.h:476
dccl::DCCLMessageOptions
Definition: option_extensions.pb.h:783
CodeGenerator
DCCLGenerator
Definition: pb_plugin.cpp:76