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 = file.package.name.empty( )
169 12 : ? std::string( )
170 12 : : "::" + replace( file.package.name, ".", "::" );
171 12 : dump_prototypes( stream, file.package.messages, package_name );
172 12 : dump_prototypes( stream, file.package.enums, package_name );
173 12 : }
174 :
175 12 : void dump_cpp_includes( std::ostream & stream, std::string_view header_file_path )
176 : {
177 : stream << "#include \"" << header_file_path << "\"\n"
178 : << "#include <spb/json.hpp>\n"
179 : "#include <system_error>\n"
180 12 : "#include <type_traits>\n\n";
181 12 : }
182 :
183 555 : void dump_cpp_close_namespace( std::ostream & stream, std::string_view name )
184 : {
185 555 : stream << "} // namespace " << name << "\n";
186 555 : }
187 :
188 555 : void dump_cpp_open_namespace( std::ostream & stream, std::string_view name )
189 : {
190 555 : stream << "namespace " << name << "\n{\n";
191 555 : }
192 :
193 4 : void dump_cpp_serialize_value( std::ostream & stream, const proto_oneof & oneof )
194 : {
195 4 : stream << "\t{\n\t\tconst auto index = value." << oneof.name << ".index( );\n";
196 4 : stream << "\t\tswitch( index )\n\t\t{\n";
197 15 : for( size_t i = 0; i < oneof.fields.size( ); ++i )
198 : {
199 11 : stream << "\t\t\tcase " << i << ":\n\t\t\t\treturn stream.serialize( \""
200 22 : << json_field_name( oneof.fields[ i ] ) << "\"sv, std::get< " << i << " >( value."
201 11 : << oneof.name << ") );\n";
202 : }
203 4 : stream << "\t\t}\n\t}\n\n";
204 4 : }
205 :
206 83 : void dump_cpp_serialize_value( std::ostream & stream, const proto_enum & my_enum,
207 : std::string_view full_name )
208 : {
209 83 : if( my_enum.fields.empty( ) )
210 : {
211 0 : stream << "void serialize_value( detail::ostream &, const " << full_name << " & )\n{\n";
212 0 : stream << "\treturn ;\n}\n\n";
213 0 : return;
214 : }
215 :
216 : stream << "void serialize_value( detail::ostream & stream, const " << full_name
217 83 : << " & value )\n{\n";
218 83 : stream << "\tswitch( value )\n\t{\n";
219 :
220 83 : std::set< int32_t > numbers_taken;
221 521 : for( const auto & field : my_enum.fields )
222 : {
223 438 : if( !numbers_taken.insert( field.number ).second )
224 : {
225 4 : continue;
226 : }
227 :
228 : stream << "\tcase " << full_name << "::" << field.name
229 434 : << ":\n\t\treturn stream.serialize( \"" << field.name << "\"sv);\n";
230 : }
231 : stream << "\tdefault:\n\t\tthrow std::system_error( std::make_error_code( "
232 83 : "std::errc::invalid_argument ) );\n";
233 83 : stream << "\t}\n}\n\n";
234 83 : }
235 :
236 83 : void dump_cpp_deserialize_value( std::ostream & stream, const proto_enum & my_enum,
237 : std::string_view full_name )
238 : {
239 83 : if( my_enum.fields.empty( ) )
240 : {
241 0 : stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n";
242 0 : stream << "\n}\n\n";
243 0 : return;
244 : }
245 :
246 83 : size_t key_size_min = UINT32_MAX;
247 83 : size_t key_size_max = 0;
248 :
249 83 : auto name_map = std::multimap< uint32_t, std::string_view >( );
250 521 : for( const auto & field : my_enum.fields )
251 : {
252 438 : name_map.emplace( spb::json::detail::djb2_hash( field.name ), field.name );
253 438 : key_size_min = std::min( key_size_min, field.name.size( ) );
254 438 : key_size_max = std::max( key_size_max, field.name.size( ) );
255 : }
256 :
257 : stream << "void deserialize_value( detail::istream & stream, " << full_name
258 83 : << " & value )\n{\n";
259 83 : stream << "\tauto enum_value = stream.deserialize_string_or_int( " << key_size_min << ", "
260 83 : << key_size_max << " );\n";
261 83 : stream << "\tstd::visit( detail::overloaded{\n\t\t[&]( std::string_view enum_str )\n\t\t{\n";
262 83 : stream << "\t\t\tconst auto enum_hash = djb2_hash( enum_str );\n";
263 83 : stream << "\t\t\tswitch( enum_hash )\n\t\t\t{\n";
264 83 : auto last_hash = name_map.begin( )->first + 1;
265 83 : auto put_break = false;
266 521 : for( const auto & [ hash, name ] : name_map )
267 : {
268 438 : if( hash != last_hash )
269 : {
270 438 : last_hash = hash;
271 438 : if( put_break )
272 : {
273 355 : stream << "\t\t\t\tbreak ;\n";
274 : }
275 438 : put_break = true;
276 438 : stream << "\t\t\tcase detail::djb2_hash( \"" << name << "\"sv ):\n";
277 : }
278 : stream << "\t\t\t\tif( enum_str == \"" << name << "\"sv ){\n\t\t\t\t\tvalue = " << full_name
279 438 : << "::" << name << ";\n\t\t\t\t\treturn ;\t\t\t\t}\n";
280 : }
281 83 : stream << "\t\t\t\tbreak ;\n";
282 : stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( "
283 83 : "std::errc::invalid_argument ) );\n";
284 : stream << "\t\t},\n\t\t[&]( int32_t enum_int )\n\t\t{\n\t\t\tswitch( " << full_name
285 83 : << "( enum_int ) )\n\t\t\t{\n";
286 83 : std::set< int32_t > numbers_taken;
287 521 : for( const auto & field : my_enum.fields )
288 : {
289 438 : if( !numbers_taken.insert( field.number ).second )
290 : {
291 4 : continue;
292 : }
293 :
294 434 : stream << "\t\t\tcase " << full_name << "::" << field.name << ":\n";
295 : }
296 83 : stream << "\t\t\t\tvalue = " << full_name << "( enum_int );\n\t\t\t\treturn ;\n";
297 : stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( "
298 83 : "std::errc::invalid_argument ) );\n";
299 83 : stream << "\t\t}\n\t}, enum_value );\n}\n\n";
300 83 : }
301 :
302 448 : void dump_cpp_serialize_value( std::ostream & stream, const proto_message & message,
303 : std::string_view full_name )
304 : {
305 448 : if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) )
306 : {
307 : stream << "void serialize_value( detail::ostream & , const " << full_name
308 9 : << " & )\n{\n}\n\n";
309 9 : return;
310 : }
311 :
312 : stream << "void serialize_value( detail::ostream & stream, const " << full_name
313 439 : << " & value )\n{\n";
314 1642 : for( const auto & field : message.fields )
315 : {
316 1203 : stream << "\tstream.serialize( \"" << json_field_name( field ) << "\"sv, value."
317 1203 : << field.name << " );\n";
318 : }
319 491 : for( const auto & map : message.maps )
320 : {
321 52 : stream << "\tstream.serialize( \"" << map.name << "\"sv, value." << map.name << " );\n";
322 : }
323 443 : for( const auto & oneof : message.oneofs )
324 : {
325 4 : dump_cpp_serialize_value( stream, oneof );
326 : }
327 439 : stream << "}\n";
328 : }
329 :
330 448 : void dump_cpp_deserialize_value( std::ostream & stream, const proto_message & message,
331 : std::string_view full_name )
332 : {
333 448 : if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) )
334 : {
335 9 : stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n";
336 9 : stream << "\n}\n\n";
337 9 : return;
338 : }
339 :
340 : //- json deserializer needs to accept both camelCase (parsed_name) and the original field name
341 : struct one_field
342 : {
343 : std::string parsed_name;
344 : std::string_view name;
345 : size_t oneof_index = SIZE_MAX;
346 : std::string_view bitfield;
347 : };
348 :
349 439 : size_t key_size_min = UINT32_MAX;
350 439 : size_t key_size_max = 0;
351 :
352 439 : auto name_map = std::multimap< uint32_t, one_field >( );
353 1642 : for( const auto & field : message.fields )
354 : {
355 1203 : key_size_min = std::min( key_size_min, field.name.size( ) );
356 1203 : key_size_max = std::max( key_size_max, field.name.size( ) );
357 :
358 1203 : const auto field_name = json_field_name_or_camelCase( field );
359 1203 : name_map.emplace( spb::json::detail::djb2_hash( field_name ),
360 2406 : one_field{
361 : .parsed_name = field_name,
362 : .name = field.name,
363 : .bitfield = field.bit_field,
364 : } );
365 1203 : if( field_name != field.name )
366 : {
367 482 : key_size_min = std::min( key_size_min, field_name.size( ) );
368 482 : key_size_max = std::max( key_size_max, field_name.size( ) );
369 :
370 482 : name_map.emplace( spb::json::detail::djb2_hash( field.name ),
371 1446 : one_field{
372 964 : .parsed_name = std::string( field.name ),
373 : .name = field.name,
374 : .bitfield = field.bit_field,
375 : } );
376 : }
377 1203 : }
378 491 : for( const auto & field : message.maps )
379 : {
380 52 : key_size_min = std::min( key_size_min, field.name.size( ) );
381 52 : key_size_max = std::max( key_size_max, field.name.size( ) );
382 :
383 52 : const auto field_name = json_field_name_or_camelCase( field );
384 52 : name_map.emplace( spb::json::detail::djb2_hash( field_name ),
385 104 : one_field{
386 : .parsed_name = field_name,
387 : .name = field.name,
388 : } );
389 52 : if( field_name != field.name )
390 : {
391 48 : key_size_min = std::min( key_size_min, field_name.size( ) );
392 48 : key_size_max = std::max( key_size_max, field_name.size( ) );
393 :
394 48 : name_map.emplace(
395 48 : spb::json::detail::djb2_hash( field.name ),
396 192 : one_field{ .parsed_name = std::string( field.name ), .name = field.name } );
397 : }
398 52 : }
399 443 : for( const auto & oneof : message.oneofs )
400 : {
401 15 : for( size_t i = 0; i < oneof.fields.size( ); ++i )
402 : {
403 11 : key_size_min = std::min( key_size_min, oneof.fields[ i ].name.size( ) );
404 11 : key_size_max = std::max( key_size_max, oneof.fields[ i ].name.size( ) );
405 :
406 11 : const auto field_name = json_field_name_or_camelCase( oneof.fields[ i ] );
407 11 : name_map.emplace( spb::json::detail::djb2_hash( field_name ),
408 22 : one_field{
409 : .parsed_name = field_name,
410 : .name = oneof.name,
411 : .oneof_index = i,
412 : } );
413 11 : if( field_name != oneof.fields[ i ].name )
414 : {
415 8 : key_size_min = std::min( key_size_min, field_name.size( ) );
416 8 : key_size_max = std::max( key_size_max, field_name.size( ) );
417 :
418 8 : name_map.emplace( spb::json::detail::djb2_hash( oneof.fields[ i ].name ),
419 24 : one_field{
420 16 : .parsed_name = std::string( oneof.fields[ i ].name ),
421 : .name = oneof.name,
422 : .oneof_index = i,
423 : } );
424 : }
425 11 : }
426 : }
427 :
428 : stream << "void deserialize_value( detail::istream & stream, " << full_name
429 439 : << " & value )\n{\n";
430 439 : stream << "\tauto key = stream.deserialize_key( " << key_size_min << ", " << key_size_max
431 439 : << " );\n";
432 439 : stream << "\tswitch( djb2_hash( key ) )\n\t{\n";
433 :
434 439 : auto last_hash = name_map.begin( )->first + 1;
435 439 : auto put_break = false;
436 2243 : for( const auto & [ hash, field ] : name_map )
437 : {
438 1804 : if( hash != last_hash )
439 : {
440 1803 : if( put_break )
441 : {
442 1364 : stream << "\t\t\t\tbreak;\n";
443 : }
444 1803 : put_break = true;
445 1803 : last_hash = hash;
446 1803 : stream << "\t\tcase detail::djb2_hash( \"" << field.parsed_name << "\"sv ):\n";
447 : }
448 1804 : stream << "\t\t\tif( key == \"" << field.parsed_name << "\"sv )\n\t\t\t{\n";
449 1804 : if( field.oneof_index == SIZE_MAX )
450 : {
451 1785 : if( !field.bitfield.empty( ) )
452 : {
453 :
454 : stream << "\t\t\t\tvalue." << field.name
455 : << " = stream.deserialize_bitfield< decltype( value." << field.name
456 50 : << " ) >( " << field.bitfield << " );\n\t\t\t\treturn ;\n";
457 : }
458 : else
459 : {
460 1735 : stream << "\t\t\t\treturn stream.deserialize( value." << field.name << " );\n";
461 : }
462 : }
463 : else
464 : {
465 19 : stream << "\t\t\t\treturn stream.deserialize_variant<" << field.oneof_index
466 19 : << ">( value." << field.name << " );\n";
467 : }
468 1804 : stream << "\t\t\t}\n";
469 : }
470 439 : stream << "\t\t\tbreak;\n\t}\n\treturn stream.skip_value( );\n}\n";
471 439 : }
472 :
473 83 : void dump_cpp_enum( std::ostream & stream, const proto_enum & my_enum, std::string_view parent )
474 : {
475 249 : const auto full_name = std::string( parent ) + "::" + std::string( my_enum.name );
476 83 : dump_cpp_open_namespace( stream, "detail" );
477 83 : dump_cpp_serialize_value( stream, my_enum, full_name );
478 83 : dump_cpp_deserialize_value( stream, my_enum, full_name );
479 83 : dump_cpp_close_namespace( stream, "detail" );
480 83 : }
481 :
482 460 : void dump_cpp_enums( std::ostream & stream, const proto_enums & enums, std::string_view parent )
483 : {
484 543 : for( const auto & my_enum : enums )
485 : {
486 83 : dump_cpp_enum( stream, my_enum, parent );
487 : }
488 460 : }
489 :
490 : void dump_cpp_messages( std::ostream & stream, const proto_messages & messages,
491 : std::string_view parent );
492 :
493 448 : void dump_cpp_message( std::ostream & stream, const proto_message & message,
494 : std::string_view parent )
495 : {
496 1344 : const auto full_name = std::string( parent ) + "::" + std::string( message.name );
497 :
498 448 : dump_cpp_enums( stream, message.enums, full_name );
499 :
500 448 : dump_cpp_open_namespace( stream, "detail" );
501 448 : dump_cpp_serialize_value( stream, message, full_name );
502 448 : dump_cpp_deserialize_value( stream, message, full_name );
503 448 : dump_cpp_close_namespace( stream, "detail" );
504 :
505 448 : dump_cpp_messages( stream, message.messages, full_name );
506 448 : }
507 :
508 460 : void dump_cpp_messages( std::ostream & stream, const proto_messages & messages,
509 : std::string_view parent )
510 : {
511 908 : for( const auto & message : messages )
512 : {
513 448 : dump_cpp_message( stream, message, parent );
514 : }
515 460 : }
516 :
517 12 : void dump_cpp( std::ostream & stream, const proto_file & file )
518 : {
519 12 : const auto str_namespace = file.package.name.empty( )
520 12 : ? std::string( )
521 12 : : "::" + replace( file.package.name, ".", "::" );
522 12 : dump_cpp_enums( stream, file.package.enums, str_namespace );
523 12 : dump_cpp_messages( stream, file.package.messages, str_namespace );
524 12 : }
525 :
526 : }// namespace
527 :
528 12 : void dump_json_header( const proto_file & file, std::ostream & stream )
529 : {
530 12 : dump_cpp_open_namespace( stream, "spb::json::detail" );
531 12 : stream << "struct ostream;\nstruct istream;\n";
532 12 : dump_prototypes( stream, file );
533 12 : dump_cpp_close_namespace( stream, "spb::json::detail" );
534 12 : }
535 :
536 12 : void dump_json_cpp( const proto_file & file, const std::filesystem::path & header_file,
537 : std::ostream & stream )
538 : {
539 12 : dump_cpp_includes( stream, header_file.string( ) );
540 12 : dump_cpp_open_namespace( stream, "spb::json" );
541 12 : dump_cpp( stream, file );
542 12 : dump_cpp_close_namespace( stream, "spb::json" );
543 12 : }
|