Line data Source code
1 : /***************************************************************************\
2 : * Name : json dumper *
3 : * Description : generate C++ src files for json de/serialization *
4 : * Author : antonin.kriz@gmail.com *
5 : * ------------------------------------------------------------------------- *
6 : * This is free software; you can redistribute it and/or modify it under the *
7 : * terms of the MIT license. A copy of the license can be found in the file *
8 : * "LICENSE" at the root of this distribution. *
9 : \***************************************************************************/
10 :
11 : #include "dumper.h"
12 : #include "ast/ast.h"
13 : #include "ast/proto-field.h"
14 : #include "ast/proto-file.h"
15 : #include "io/file.h"
16 : #include "parser/parser.h"
17 : #include "template-h.h"
18 : #include <algorithm>
19 : #include <array>
20 : #include <cctype>
21 : #include <cstdint>
22 : #include <map>
23 : #include <set>
24 : #include <spb/json/deserialize.hpp>
25 : #include <sstream>
26 : #include <stdexcept>
27 : #include <string>
28 : #include <string_view>
29 :
30 : using namespace std::literals;
31 :
32 : namespace
33 : {
34 :
35 555 : auto replace( std::string_view input, std::string_view what, std::string_view with ) -> std::string
36 : {
37 555 : auto result = std::string( input );
38 555 : auto pos = size_t{ };
39 :
40 1631 : while( ( pos = result.find( what, pos ) ) != std::string::npos )
41 : {
42 1076 : result.replace( pos, what.size( ), with );
43 1076 : pos += with.size( );
44 : }
45 :
46 555 : return result;
47 0 : }
48 :
49 531 : void dump_prototypes( std::ostream & stream, std::string_view type )
50 : {
51 531 : stream << replace( file_json_header_template, "$", type );
52 531 : }
53 :
54 2480 : auto json_name_from_options( const proto_options & options ) -> std::string_view
55 : {
56 2480 : if( auto p_option = options.find( "json_name" ); p_option != options.end( ) )
57 : {
58 0 : return p_option->second;
59 : }
60 :
61 2480 : return ""sv;
62 : }
63 :
64 1266 : auto convert_to_camelCase( std::string_view input ) -> std::string
65 : {
66 1266 : auto result = std::string( input );
67 1266 : if( !result.empty( ) )
68 : {
69 1266 : result[ 0 ] = char( std::tolower( result[ 0 ] ) );
70 : }
71 :
72 1266 : if( input.find( '_' ) == std::string::npos )
73 : {
74 797 : return result;
75 : }
76 :
77 469 : auto index = 0U;
78 469 : auto up = false;
79 7624 : for( auto c : input )
80 : {
81 7155 : if( c == '_' )
82 : {
83 703 : up = true;
84 : }
85 : else
86 : {
87 6452 : result[ index ] = char( ( up && index > 0 ) ? std::toupper( c ) : std::tolower( c ) );
88 6452 : index += 1;
89 6452 : up = false;
90 : }
91 : }
92 469 : result.resize( index );
93 469 : return result;
94 0 : }
95 :
96 1214 : auto json_field_name( const proto_base & field ) -> std::string
97 : {
98 1214 : if( const auto result = json_name_from_options( field.options ); !result.empty( ) )
99 : {
100 0 : return std::string( result );
101 : }
102 :
103 2428 : return std::string( field.name );
104 : }
105 :
106 1266 : auto json_field_name_or_camelCase( const proto_base & field ) -> std::string
107 : {
108 1266 : if( const auto result = json_name_from_options( field.options ); !result.empty( ) )
109 : {
110 0 : return std::string( result );
111 : }
112 :
113 1266 : return convert_to_camelCase( field.name );
114 : }
115 :
116 448 : void dump_prototypes( std::ostream & stream, const proto_message & message,
117 : std::string_view parent )
118 : {
119 1344 : const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name );
120 448 : dump_prototypes( stream, message_with_parent );
121 448 : }
122 :
123 83 : void dump_prototypes( std::ostream & stream, const proto_enum & my_enum, std::string_view parent )
124 : {
125 249 : const auto enum_with_parent = std::string( parent ) + "::" + std::string( my_enum.name );
126 83 : dump_prototypes( stream, enum_with_parent );
127 83 : }
128 :
129 75 : void dump_prototypes( std::ostream & stream, const proto_enums & enums, std::string_view parent )
130 : {
131 158 : for( const auto & my_enum : enums )
132 : {
133 83 : dump_prototypes( stream, my_enum, parent );
134 : }
135 75 : }
136 :
137 63 : void dump_prototypes( std::ostream & stream, const proto_messages & messages,
138 : std::string_view parent )
139 : {
140 511 : for( const auto & message : messages )
141 : {
142 448 : dump_prototypes( stream, message, parent );
143 : }
144 :
145 511 : for( const auto & message : messages )
146 : {
147 448 : if( message.messages.empty( ) )
148 : {
149 397 : continue;
150 : }
151 153 : const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name );
152 51 : dump_prototypes( stream, message.messages, message_with_parent );
153 51 : }
154 :
155 511 : for( const auto & message : messages )
156 : {
157 448 : if( message.enums.empty( ) )
158 : {
159 385 : continue;
160 : }
161 189 : const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name );
162 63 : dump_prototypes( stream, message.enums, message_with_parent );
163 63 : }
164 63 : }
165 :
166 12 : void dump_prototypes( std::ostream & stream, const proto_file & file )
167 : {
168 12 : const auto package_name = replace( file.package.name, ".", "::" );
169 12 : dump_prototypes( stream, file.package.messages, package_name );
170 12 : dump_prototypes( stream, file.package.enums, package_name );
171 12 : }
172 :
173 12 : void dump_cpp_includes( std::ostream & stream, std::string_view header_file_path )
174 : {
175 : stream << "#include \"" << header_file_path << "\"\n"
176 : << "#include <spb/json.hpp>\n"
177 : "#include <system_error>\n"
178 12 : "#include <type_traits>\n\n";
179 12 : }
180 :
181 555 : void dump_cpp_close_namespace( std::ostream & stream, std::string_view name )
182 : {
183 555 : stream << "} // namespace " << name << "\n";
184 555 : }
185 :
186 555 : void dump_cpp_open_namespace( std::ostream & stream, std::string_view name )
187 : {
188 555 : stream << "namespace " << name << "\n{\n";
189 555 : }
190 :
191 4 : void dump_cpp_serialize_value( std::ostream & stream, const proto_oneof & oneof )
192 : {
193 4 : stream << "\t{\n\t\tconst auto index = value." << oneof.name << ".index( );\n";
194 4 : stream << "\t\tswitch( index )\n\t\t{\n";
195 15 : for( size_t i = 0; i < oneof.fields.size( ); ++i )
196 : {
197 11 : stream << "\t\t\tcase " << i << ":\n\t\t\t\treturn stream.serialize( \""
198 22 : << json_field_name( oneof.fields[ i ] ) << "\"sv, std::get< " << i << " >( value."
199 11 : << oneof.name << ") );\n";
200 : }
201 4 : stream << "\t\t}\n\t}\n\n";
202 4 : }
203 :
204 83 : void dump_cpp_serialize_value( std::ostream & stream, const proto_enum & my_enum,
205 : std::string_view full_name )
206 : {
207 83 : if( my_enum.fields.empty( ) )
208 : {
209 0 : stream << "void serialize_value( detail::ostream &, const " << full_name << " & )\n{\n";
210 0 : stream << "\treturn ;\n}\n\n";
211 0 : return;
212 : }
213 :
214 : stream << "void serialize_value( detail::ostream & stream, const " << full_name
215 83 : << " & value )\n{\n";
216 83 : stream << "\tswitch( value )\n\t{\n";
217 :
218 83 : std::set< int32_t > numbers_taken;
219 521 : for( const auto & field : my_enum.fields )
220 : {
221 438 : if( !numbers_taken.insert( field.number ).second )
222 : {
223 4 : continue;
224 : }
225 :
226 : stream << "\tcase " << full_name << "::" << field.name
227 434 : << ":\n\t\treturn stream.serialize( \"" << field.name << "\"sv);\n";
228 : }
229 : stream << "\tdefault:\n\t\tthrow std::system_error( std::make_error_code( "
230 83 : "std::errc::invalid_argument ) );\n";
231 83 : stream << "\t}\n}\n\n";
232 83 : }
233 :
234 83 : void dump_cpp_deserialize_value( std::ostream & stream, const proto_enum & my_enum,
235 : std::string_view full_name )
236 : {
237 83 : if( my_enum.fields.empty( ) )
238 : {
239 0 : stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n";
240 0 : stream << "\n}\n\n";
241 0 : return;
242 : }
243 :
244 83 : size_t key_size_min = UINT32_MAX;
245 83 : size_t key_size_max = 0;
246 :
247 83 : auto name_map = std::multimap< uint32_t, std::string_view >( );
248 521 : for( const auto & field : my_enum.fields )
249 : {
250 438 : name_map.emplace( spb::json::detail::djb2_hash( field.name ), field.name );
251 438 : key_size_min = std::min( key_size_min, field.name.size( ) );
252 438 : key_size_max = std::max( key_size_max, field.name.size( ) );
253 : }
254 :
255 : stream << "void deserialize_value( detail::istream & stream, " << full_name
256 83 : << " & value )\n{\n";
257 83 : stream << "\tauto enum_value = stream.deserialize_string_or_int( " << key_size_min << ", "
258 83 : << key_size_max << " );\n";
259 83 : stream << "\tstd::visit( detail::overloaded{\n\t\t[&]( std::string_view enum_str )\n\t\t{\n";
260 83 : stream << "\t\t\tconst auto enum_hash = djb2_hash( enum_str );\n";
261 83 : stream << "\t\t\tswitch( enum_hash )\n\t\t\t{\n";
262 83 : auto last_hash = name_map.begin( )->first + 1;
263 83 : auto put_break = false;
264 521 : for( const auto & [ hash, name ] : name_map )
265 : {
266 438 : if( hash != last_hash )
267 : {
268 438 : last_hash = hash;
269 438 : if( put_break )
270 : {
271 355 : stream << "\t\t\t\tbreak ;\n";
272 : }
273 438 : put_break = true;
274 438 : stream << "\t\t\tcase detail::djb2_hash( \"" << name << "\"sv ):\n";
275 : }
276 : stream << "\t\t\t\tif( enum_str == \"" << name << "\"sv ){\n\t\t\t\t\tvalue = " << full_name
277 438 : << "::" << name << ";\n\t\t\t\t\treturn ;\t\t\t\t}\n";
278 : }
279 83 : stream << "\t\t\t\tbreak ;\n";
280 : stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( "
281 83 : "std::errc::invalid_argument ) );\n";
282 : stream << "\t\t},\n\t\t[&]( int32_t enum_int )\n\t\t{\n\t\t\tswitch( " << full_name
283 83 : << "( enum_int ) )\n\t\t\t{\n";
284 83 : std::set< int32_t > numbers_taken;
285 521 : for( const auto & field : my_enum.fields )
286 : {
287 438 : if( !numbers_taken.insert( field.number ).second )
288 : {
289 4 : continue;
290 : }
291 :
292 434 : stream << "\t\t\tcase " << full_name << "::" << field.name << ":\n";
293 : }
294 83 : stream << "\t\t\t\tvalue = " << full_name << "( enum_int );\n\t\t\t\treturn ;\n";
295 : stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( "
296 83 : "std::errc::invalid_argument ) );\n";
297 83 : stream << "\t\t}\n\t}, enum_value );\n}\n\n";
298 83 : }
299 :
300 448 : void dump_cpp_serialize_value( std::ostream & stream, const proto_message & message,
301 : std::string_view full_name )
302 : {
303 448 : if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) )
304 : {
305 : stream << "void serialize_value( detail::ostream & , const " << full_name
306 9 : << " & )\n{\n}\n\n";
307 9 : return;
308 : }
309 :
310 : stream << "void serialize_value( detail::ostream & stream, const " << full_name
311 439 : << " & value )\n{\n";
312 1642 : for( const auto & field : message.fields )
313 : {
314 1203 : stream << "\tstream.serialize( \"" << json_field_name( field ) << "\"sv, value."
315 1203 : << field.name << " );\n";
316 : }
317 491 : for( const auto & map : message.maps )
318 : {
319 52 : stream << "\tstream.serialize( \"" << map.name << "\"sv, value." << map.name << " );\n";
320 : }
321 443 : for( const auto & oneof : message.oneofs )
322 : {
323 4 : dump_cpp_serialize_value( stream, oneof );
324 : }
325 439 : stream << "}\n";
326 : }
327 :
328 448 : void dump_cpp_deserialize_value( std::ostream & stream, const proto_message & message,
329 : std::string_view full_name )
330 : {
331 448 : if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) )
332 : {
333 9 : stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n";
334 9 : stream << "\n}\n\n";
335 9 : return;
336 : }
337 :
338 : //- json deserializer needs to accept both camelCase (parsed_name) and the original field name
339 : struct one_field
340 : {
341 : std::string parsed_name;
342 : std::string_view name;
343 : size_t oneof_index = SIZE_MAX;
344 : std::string_view bitfield;
345 : };
346 :
347 439 : size_t key_size_min = UINT32_MAX;
348 439 : size_t key_size_max = 0;
349 :
350 439 : auto name_map = std::multimap< uint32_t, one_field >( );
351 1642 : for( const auto & field : message.fields )
352 : {
353 1203 : key_size_min = std::min( key_size_min, field.name.size( ) );
354 1203 : key_size_max = std::max( key_size_max, field.name.size( ) );
355 :
356 1203 : const auto field_name = json_field_name_or_camelCase( field );
357 1203 : name_map.emplace( spb::json::detail::djb2_hash( field_name ),
358 2406 : one_field{
359 : .parsed_name = field_name,
360 : .name = field.name,
361 : .bitfield = field.bit_field,
362 : } );
363 1203 : if( field_name != field.name )
364 : {
365 482 : key_size_min = std::min( key_size_min, field_name.size( ) );
366 482 : key_size_max = std::max( key_size_max, field_name.size( ) );
367 :
368 482 : name_map.emplace( spb::json::detail::djb2_hash( field.name ),
369 1446 : one_field{
370 964 : .parsed_name = std::string( field.name ),
371 : .name = field.name,
372 : .bitfield = field.bit_field,
373 : } );
374 : }
375 1203 : }
376 491 : for( const auto & field : message.maps )
377 : {
378 52 : key_size_min = std::min( key_size_min, field.name.size( ) );
379 52 : key_size_max = std::max( key_size_max, field.name.size( ) );
380 :
381 52 : const auto field_name = json_field_name_or_camelCase( field );
382 52 : name_map.emplace( spb::json::detail::djb2_hash( field_name ),
383 104 : one_field{
384 : .parsed_name = field_name,
385 : .name = field.name,
386 : } );
387 52 : if( field_name != field.name )
388 : {
389 48 : key_size_min = std::min( key_size_min, field_name.size( ) );
390 48 : key_size_max = std::max( key_size_max, field_name.size( ) );
391 :
392 48 : name_map.emplace(
393 48 : spb::json::detail::djb2_hash( field.name ),
394 192 : one_field{ .parsed_name = std::string( field.name ), .name = field.name } );
395 : }
396 52 : }
397 443 : for( const auto & oneof : message.oneofs )
398 : {
399 15 : for( size_t i = 0; i < oneof.fields.size( ); ++i )
400 : {
401 11 : key_size_min = std::min( key_size_min, oneof.fields[ i ].name.size( ) );
402 11 : key_size_max = std::max( key_size_max, oneof.fields[ i ].name.size( ) );
403 :
404 11 : const auto field_name = json_field_name_or_camelCase( oneof.fields[ i ] );
405 11 : name_map.emplace( spb::json::detail::djb2_hash( field_name ),
406 22 : one_field{
407 : .parsed_name = field_name,
408 : .name = oneof.name,
409 : .oneof_index = i,
410 : } );
411 11 : if( field_name != oneof.fields[ i ].name )
412 : {
413 8 : key_size_min = std::min( key_size_min, field_name.size( ) );
414 8 : key_size_max = std::max( key_size_max, field_name.size( ) );
415 :
416 8 : name_map.emplace( spb::json::detail::djb2_hash( oneof.fields[ i ].name ),
417 24 : one_field{
418 16 : .parsed_name = std::string( oneof.fields[ i ].name ),
419 : .name = oneof.name,
420 : .oneof_index = i,
421 : } );
422 : }
423 11 : }
424 : }
425 :
426 : stream << "void deserialize_value( detail::istream & stream, " << full_name
427 439 : << " & value )\n{\n";
428 439 : stream << "\tauto key = stream.deserialize_key( " << key_size_min << ", " << key_size_max
429 439 : << " );\n";
430 439 : stream << "\tswitch( djb2_hash( key ) )\n\t{\n";
431 :
432 439 : auto last_hash = name_map.begin( )->first + 1;
433 439 : auto put_break = false;
434 2243 : for( const auto & [ hash, field ] : name_map )
435 : {
436 1804 : if( hash != last_hash )
437 : {
438 1803 : if( put_break )
439 : {
440 1364 : stream << "\t\t\t\tbreak;\n";
441 : }
442 1803 : put_break = true;
443 1803 : last_hash = hash;
444 1803 : stream << "\t\tcase detail::djb2_hash( \"" << field.parsed_name << "\"sv ):\n";
445 : }
446 1804 : stream << "\t\t\tif( key == \"" << field.parsed_name << "\"sv )\n\t\t\t{\n";
447 1804 : if( field.oneof_index == SIZE_MAX )
448 : {
449 1785 : if( !field.bitfield.empty( ) )
450 : {
451 :
452 : stream << "\t\t\t\tvalue." << field.name
453 : << " = stream.deserialize_bitfield< decltype( value." << field.name
454 50 : << " ) >( " << field.bitfield << " );\n\t\t\t\treturn ;\n";
455 : }
456 : else
457 : {
458 1735 : stream << "\t\t\t\treturn stream.deserialize( value." << field.name << " );\n";
459 : }
460 : }
461 : else
462 : {
463 19 : stream << "\t\t\t\treturn stream.deserialize_variant<" << field.oneof_index
464 19 : << ">( value." << field.name << " );\n";
465 : }
466 1804 : stream << "\t\t\t}\n";
467 : }
468 439 : stream << "\t\t\tbreak;\n\t}\n\treturn stream.skip_value( );\n}\n";
469 439 : }
470 :
471 83 : void dump_cpp_enum( std::ostream & stream, const proto_enum & my_enum, std::string_view parent )
472 : {
473 249 : const auto full_name = std::string( parent ) + "::" + std::string( my_enum.name );
474 83 : dump_cpp_open_namespace( stream, "detail" );
475 83 : dump_cpp_serialize_value( stream, my_enum, full_name );
476 83 : dump_cpp_deserialize_value( stream, my_enum, full_name );
477 83 : dump_cpp_close_namespace( stream, "detail" );
478 83 : }
479 :
480 460 : void dump_cpp_enums( std::ostream & stream, const proto_enums & enums, std::string_view parent )
481 : {
482 543 : for( const auto & my_enum : enums )
483 : {
484 83 : dump_cpp_enum( stream, my_enum, parent );
485 : }
486 460 : }
487 :
488 : void dump_cpp_messages( std::ostream & stream, const proto_messages & messages,
489 : std::string_view parent );
490 :
491 448 : void dump_cpp_message( std::ostream & stream, const proto_message & message,
492 : std::string_view parent )
493 : {
494 1344 : const auto full_name = std::string( parent ) + "::" + std::string( message.name );
495 :
496 448 : dump_cpp_enums( stream, message.enums, full_name );
497 :
498 448 : dump_cpp_open_namespace( stream, "detail" );
499 448 : dump_cpp_serialize_value( stream, message, full_name );
500 448 : dump_cpp_deserialize_value( stream, message, full_name );
501 448 : dump_cpp_close_namespace( stream, "detail" );
502 :
503 448 : dump_cpp_messages( stream, message.messages, full_name );
504 448 : }
505 :
506 460 : void dump_cpp_messages( std::ostream & stream, const proto_messages & messages,
507 : std::string_view parent )
508 : {
509 908 : for( const auto & message : messages )
510 : {
511 448 : dump_cpp_message( stream, message, parent );
512 : }
513 460 : }
514 :
515 12 : void dump_cpp( std::ostream & stream, const proto_file & file )
516 : {
517 12 : const auto str_namespace = "::" + replace( file.package.name, ".", "::" );
518 12 : dump_cpp_enums( stream, file.package.enums, str_namespace );
519 12 : dump_cpp_messages( stream, file.package.messages, str_namespace );
520 12 : }
521 :
522 : }// namespace
523 :
524 12 : void dump_json_header( const proto_file & file, std::ostream & stream )
525 : {
526 12 : dump_cpp_open_namespace( stream, "spb::json::detail" );
527 12 : stream << "struct ostream;\nstruct istream;\n";
528 12 : dump_prototypes( stream, file );
529 12 : dump_cpp_close_namespace( stream, "spb::json::detail" );
530 12 : }
531 :
532 12 : void dump_json_cpp( const proto_file & file, const std::filesystem::path & header_file,
533 : std::ostream & stream )
534 : {
535 12 : dump_cpp_includes( stream, header_file.string( ) );
536 12 : dump_cpp_open_namespace( stream, "spb::json" );
537 12 : dump_cpp( stream, file );
538 12 : dump_cpp_close_namespace( stream, "spb::json" );
539 12 : }
|