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