25#include "gen_units_class_plugin.h"
26#include "option_extensions.pb.h"
30#include <google/protobuf/compiler/code_generator.h>
31#include <google/protobuf/compiler/plugin.h>
32#include <google/protobuf/descriptor.h>
33#include <google/protobuf/io/printer.h>
34#include <google/protobuf/io/zero_copy_stream.h>
41std::set<std::string> systems_to_include_;
42std::set<std::string> base_units_to_include_;
43std::set<std::string> custom_unit_headers_to_include_;
44std::string filename_h_;
45std::string load_file_cpp_;
46std::shared_ptr<std::fstream> load_file_output_;
48std::string load_file_base_{
49 R
"DEL(#include <dccl/codec.h>
51// DO NOT REMOVE: required by loader code
52std::vector<const google::protobuf::Descriptor*> descriptors;
53template<typename PB> struct DCCLLoader { DCCLLoader() { descriptors.push_back(PB::descriptor()); }};
54// END: required by loader code
59 void dccl3_load(dccl::Codec* dccl)
61 // DO NOT REMOVE: required by loader code
62 for(auto d : descriptors)
64 // END: required by loader code
67 void dccl3_unload(dccl::Codec* dccl)
69 // DO NOT REMOVE: required by loader code
70 for(auto d : descriptors)
72 // END: required by loader code
76// BEGIN (TO END OF FILE): AUTOGENERATED LOADERS
86 bool Generate(
const google::protobuf::FileDescriptor* file,
const std::string& parameter,
87 google::protobuf::compiler::GeneratorContext* generator_context,
88 std::string* error)
const override;
90 uint64_t GetSupportedFeatures()
const override
92 return FEATURE_PROTO3_OPTIONAL;
96 void generate_message(
97 const google::protobuf::Descriptor* desc,
98 google::protobuf::compiler::GeneratorContext* generator_context,
99 std::shared_ptr<std::string> message_unit_system = std::shared_ptr<std::string>())
const;
100 void generate_field(
const google::protobuf::FieldDescriptor* field,
101 google::protobuf::io::Printer* printer,
102 std::shared_ptr<std::string> message_unit_system)
const;
103 bool check_field_type(
const google::protobuf::FieldDescriptor* field)
const;
105 void generate_load_file_headers()
const;
106 void generate_load_file_message_loader(
const google::protobuf::Descriptor* desc)
const;
109bool DCCLGenerator::check_field_type(
const google::protobuf::FieldDescriptor* field)
const
111 bool is_integer = field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_INT32 ||
112 field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_INT64 ||
113 field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_UINT32 ||
114 field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_UINT64;
116 bool is_float = field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE ||
117 field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_FLOAT;
119 if (!is_float && !is_integer)
121 throw std::runtime_error(
"Can only use (dccl.field).base_dimensions on numeric fields");
126bool DCCLGenerator::Generate(
const google::protobuf::FileDescriptor* file,
127 const std::string& parameter,
128 google::protobuf::compiler::GeneratorContext* generator_context,
129 std::string* error)
const
131 std::vector<std::pair<std::string, std::string>> options;
132 google::protobuf::compiler::ParseGeneratorParameter(parameter, &options);
134 for (
const auto& [key, value] : options)
136 if (key ==
"dccl3_load_file")
138 load_file_cpp_ = value;
139 load_file_output_ = std::make_shared<std::fstream>(
140 load_file_cpp_, std::ios::in | std::ios::out | std::ios::app);
142 if (!load_file_output_->is_open())
144 *error =
"Failed to open dccl3_load_file: " + load_file_cpp_;
150 *error =
"Unknown parameter: " + key;
157 const std::string filename(file->name());
158 filename_h_ = filename.substr(0, filename.find(
".proto")) +
".pb.h";
161 if (load_file_output_)
162 generate_load_file_headers();
164 for (
int message_i = 0, message_n = file->message_type_count(); message_i < message_n;
169 generate_message(file->message_type(message_i), generator_context);
171 catch (std::exception& e)
174 std::runtime_error(std::string(
"Failed to generate DCCL code: \n") + e.what()));
178 std::shared_ptr<google::protobuf::io::ZeroCopyOutputStream> include_output(
179 generator_context->OpenForInsert(filename_h_,
"includes"));
180 google::protobuf::io::Printer include_printer(include_output.get(),
'$');
181 std::stringstream includes_ss;
183 includes_ss <<
"#include <boost/units/quantity.hpp>" << std::endl;
184 includes_ss <<
"#include <boost/units/absolute.hpp>" << std::endl;
185 includes_ss <<
"#include <boost/units/dimensionless_type.hpp>" << std::endl;
186 includes_ss <<
"#include <boost/units/make_scaled_unit.hpp>" << std::endl;
188 for (
const auto& it : systems_to_include_) include_units_headers(it, includes_ss);
189 for (
const auto& it : base_units_to_include_) include_base_unit_headers(it, includes_ss);
190 for (
const auto& it : custom_unit_headers_to_include_)
191 include_custom_unit_headers(it, includes_ss);
193 include_printer.Print(includes_ss.str().c_str());
197 catch (std::exception& e)
204void DCCLGenerator::generate_message(
205 const google::protobuf::Descriptor* desc,
206 google::protobuf::compiler::GeneratorContext* generator_context,
207 std::shared_ptr<std::string> message_unit_system)
const
212 std::shared_ptr<google::protobuf::io::ZeroCopyOutputStream> output(
213 generator_context->OpenForInsert(filename_h_,
"class_scope:" + std::string(desc->full_name())));
214 google::protobuf::io::Printer printer(output.get(),
'$');
216 if (desc->options().HasExtension(dccl::msg))
218 if (desc->options().GetExtension(dccl::msg).id() != 0)
220 std::stringstream id_enum;
221 id_enum <<
"enum DCCLParameters { DCCL_ID = "
222 << desc->options().GetExtension(dccl::msg).id() <<
", "
223 <<
" DCCL_MAX_BYTES = "
224 << desc->options().GetExtension(dccl::msg).max_bytes() <<
" };\n";
225 printer.Print(id_enum.str().c_str());
227 if (load_file_output_)
228 generate_load_file_message_loader(desc);
233 desc->options().GetExtension(dccl::msg);
234 if (dccl_msg_options.has_unit_system())
236 message_unit_system.reset(
new std::string(dccl_msg_options.unit_system()));
237 systems_to_include_.insert(dccl_msg_options.unit_system());
241 for (
int field_i = 0, field_n = desc->field_count(); field_i < field_n; ++field_i)
243 generate_field(desc->field(field_i), &printer, message_unit_system);
246 for (
int nested_type_i = 0, nested_type_n = desc->nested_type_count();
247 nested_type_i < nested_type_n; ++nested_type_i)
248 generate_message(desc->nested_type(nested_type_i), generator_context,
249 message_unit_system);
252 catch (std::exception& e)
254 throw(std::runtime_error(std::string(
"Message: \n") + std::string(desc->full_name()) +
"\n" + e.what()));
258void DCCLGenerator::generate_field(
const google::protobuf::FieldDescriptor* field,
259 google::protobuf::io::Printer* printer,
260 std::shared_ptr<std::string> message_unit_system)
const
265 field->options().GetExtension(dccl::field);
267 if (!dccl_field_options.has_units())
272 if ((dccl_field_options.units().has_base_dimensions() &&
273 dccl_field_options.units().has_derived_dimensions()) ||
274 (dccl_field_options.units().has_base_dimensions() &&
275 dccl_field_options.units().has_unit()) ||
276 (dccl_field_options.units().has_unit() &&
277 dccl_field_options.units().has_derived_dimensions()))
279 throw(std::runtime_error(
"May define either (dccl.field).units.base_dimensions or "
280 "(dccl.field).units.derived_dimensions or "
281 "(dccl.field).units.unit, but not more than one."));
283 else if (dccl_field_options.units().has_unit())
285 std::stringstream new_methods;
287 construct_units_typedef_from_base_unit(
288 std::string(field->name()), dccl_field_options.units().unit(),
289 dccl_field_options.units().relative_temperature(),
290 dccl_field_options.units().prefix(), new_methods);
291 construct_field_class_plugin(std::string(field->name()), new_methods,
292 dccl::units::get_field_type_name(field->cpp_type()),
293 field->is_repeated());
294 printer->Print(new_methods.str().c_str());
295 base_units_to_include_.insert(dccl_field_options.units().unit());
297 else if (dccl_field_options.units().has_custom())
299 std::stringstream new_methods;
301 construct_units_typedef_from_custom_unit(
302 std::string(field->name()), dccl_field_options.units().custom().unit(),
303 dccl_field_options.units().relative_temperature(),
304 dccl_field_options.units().prefix(), new_methods);
305 construct_field_class_plugin(std::string(field->name()), new_methods,
306 dccl::units::get_field_type_name(field->cpp_type()),
307 field->is_repeated());
308 printer->Print(new_methods.str().c_str());
310 if (dccl_field_options.units().custom().has_header())
311 custom_unit_headers_to_include_.insert(
312 dccl_field_options.units().custom().header());
314 else if (dccl_field_options.units().has_base_dimensions())
316 std::stringstream new_methods;
318 std::vector<double> powers;
319 std::vector<std::string> short_dimensions;
320 std::vector<std::string> dimensions;
321 if (dccl::units::parse_base_dimensions(
322 dccl_field_options.units().base_dimensions().begin(),
323 dccl_field_options.units().base_dimensions().end(), powers, short_dimensions,
326 if (!dccl_field_options.units().has_system() && !message_unit_system)
327 throw(std::runtime_error(
328 std::string(
"Field must have 'system' defined or message must have "
329 "'unit_system' defined when using 'base_dimensions'.")));
332 const std::string unit_system =
333 (!dccl_field_options.units().has_system() && message_unit_system)
334 ? *message_unit_system
335 : std::string(dccl_field_options.units().system());
337 construct_base_dims_typedef(dimensions, powers, std::string(field->name()), unit_system,
338 dccl_field_options.units().relative_temperature(),
339 dccl_field_options.units().prefix(), new_methods);
341 construct_field_class_plugin(std::string(field->name()), new_methods,
342 dccl::units::get_field_type_name(field->cpp_type()),
343 field->is_repeated());
344 printer->Print(new_methods.str().c_str());
345 systems_to_include_.insert(unit_system);
349 throw(std::runtime_error(std::string(
"Failed to parse base_dimensions string: \"") +
350 std::string(dccl_field_options.units().base_dimensions()) +
354 else if (dccl_field_options.units().has_derived_dimensions())
356 std::stringstream new_methods;
358 std::vector<std::string> operators;
359 std::vector<std::string> dimensions;
360 if (dccl::units::parse_derived_dimensions(
361 dccl_field_options.units().derived_dimensions().begin(),
362 dccl_field_options.units().derived_dimensions().end(), operators, dimensions))
364 if (!dccl_field_options.units().has_system() && !message_unit_system)
365 throw(std::runtime_error(
366 std::string(
"Field must have 'system' defined or message must have "
367 "'unit_system' defined when using 'derived_dimensions'.")));
368 const std::string unit_system =
369 (!dccl_field_options.units().has_system() && message_unit_system)
370 ? *message_unit_system
371 : std::string(dccl_field_options.units().system());
373 dccl::units::validate_dimensions_for_system(dimensions, unit_system);
375 construct_derived_dims_typedef(dimensions, operators, std::string(field->name()), unit_system,
376 dccl_field_options.units().relative_temperature(),
377 dccl_field_options.units().prefix(), new_methods);
379 construct_field_class_plugin(std::string(field->name()), new_methods,
380 dccl::units::get_field_type_name(field->cpp_type()),
381 field->is_repeated());
382 printer->Print(new_methods.str().c_str());
383 systems_to_include_.insert(unit_system);
387 throw(std::runtime_error(
388 std::string(
"Failed to parse derived_dimensions string: \"") +
389 std::string(dccl_field_options.units().derived_dimensions()) +
"\""));
393 catch (std::exception& e)
396 std::runtime_error(std::string(
"Field: \n" + field->DebugString() +
"\n" + e.what())));
400void DCCLGenerator::generate_load_file_headers()
const
402 bool file_is_empty = std::filesystem::is_empty(load_file_cpp_);
404 bool header_already_written =
false;
407 *load_file_output_ << load_file_base_ << std::flush;
411 for (std::string line; getline(*load_file_output_, line);)
413 if (line.find(
"\"" + filename_h_ +
"\"") != std::string::npos)
415 header_already_written =
true;
421 load_file_output_->clear();
423 load_file_output_->seekp(0, std::ios_base::end);
425 if (!header_already_written)
426 *load_file_output_ <<
"#include \"" << filename_h_ <<
"\"" << std::endl;
429void DCCLGenerator::generate_load_file_message_loader(
430 const google::protobuf::Descriptor* desc)
const
433 load_file_output_->seekp(0, std::ios_base::beg);
436 std::string cpp_name(desc->full_name());
438 std::string::size_type pos = 0;
439 while ((pos = cpp_name.find(
'.', pos)) != std::string::npos)
441 cpp_name.replace(pos, 1,
"::");
447 std::string loader_name = std::string(desc->full_name()) +
"_loader";
449 std::string::size_type pos = 0;
450 while ((pos = loader_name.find(
'.', pos)) != std::string::npos)
452 loader_name.replace(pos, 1,
"_");
456 std::transform(loader_name.begin(), loader_name.end(), loader_name.begin(),
457 [](
unsigned char c) { return std::tolower(c); });
459 bool loader_already_written =
false;
460 for (std::string line; getline(*load_file_output_, line);)
462 if (line.find(
"<" + cpp_name +
">") != std::string::npos)
464 loader_already_written =
true;
468 load_file_output_->clear();
469 load_file_output_->seekp(0, std::ios_base::end);
471 if (!loader_already_written)
472 *load_file_output_ <<
"DCCLLoader<" << cpp_name <<
"> " << loader_name <<
";" << std::endl;
475int main(
int argc,
char* argv[])
478 return google::protobuf::compiler::PluginMain(argc, argv, &generator);