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/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
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
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 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
196void 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
248void 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
371void 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
401void 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
432int main(int argc, char* argv[])
433{
434 DCCLGenerator generator;
435 return google::protobuf::compiler::PluginMain(argc, argv, &generator);
436}