Line data Source code
1 : /***************************************************************************\
2 : * Name : .proto parser *
3 : * Description : parse proto file and constructs an ast tree *
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 "parser.h"
12 : #include "ast/proto-field.h"
13 : #include "ast/proto-file.h"
14 : #include "dumper/header.h"
15 : #include "options.h"
16 : #include <array>
17 : #include <ast/ast-types.h>
18 : #include <ast/ast.h>
19 : #include <cctype>
20 : #include <cerrno>
21 : #include <concepts>
22 : #include <cstdio>
23 : #include <cstring>
24 : #include <exception>
25 : #include <filesystem>
26 : #include <io/file.h>
27 : #include <parser/char_stream.h>
28 : #include <spb/to_from_chars.h>
29 : #include <stdexcept>
30 : #include <string_view>
31 :
32 : namespace
33 : {
34 : namespace fs = std::filesystem;
35 : using parsed_files = std::set< std::string >;
36 :
37 : [[nodiscard]] auto parse_proto_file( fs::path file, parsed_files &,
38 : std::span< const fs::path > import_paths,
39 : const fs::path & base_dir ) -> proto_file;
40 :
41 14 : auto find_file_in_paths( const fs::path & file_name, std::span< const fs::path > import_paths,
42 : const fs::path & base_dir ) -> fs::path
43 : {
44 14 : if( file_name.has_root_path( ) )
45 : {
46 12 : if( fs::exists( file_name ) )
47 : {
48 12 : return file_name;
49 : }
50 : }
51 : else
52 : {
53 2 : if( fs::exists( base_dir / file_name ) )
54 : {
55 2 : return base_dir / file_name;
56 : }
57 :
58 0 : for( const auto & import_path : import_paths )
59 : {
60 0 : auto file_path = import_path.has_root_path( ) ? import_path / file_name
61 0 : : base_dir / import_path / file_name;
62 0 : if( fs::exists( file_path ) )
63 : {
64 0 : return file_path;
65 : }
66 0 : }
67 : }
68 :
69 0 : throw std::runtime_error( strerror( ENOENT ) );
70 : }
71 :
72 14 : [[nodiscard]] auto parse_all_imports( const proto_file & file, parsed_files & already_parsed,
73 : std::span< const fs::path > import_paths,
74 : const fs::path & base_dir ) -> proto_files
75 : {
76 14 : proto_files result;
77 14 : result.reserve( file.imports.size( ) );
78 16 : for( const auto & import : file.imports )
79 : {
80 4 : if( !already_parsed.contains( std::string( import.file_name ) ) )
81 : {
82 : try
83 : {
84 2 : result.emplace_back(
85 4 : parse_proto_file( import.file_name, already_parsed, import_paths, base_dir ) );
86 : }
87 0 : catch( const std::runtime_error & error )
88 : {
89 0 : throw_parse_error( file, import.file_name, error.what( ) );
90 0 : }
91 : }
92 : }
93 14 : return result;
94 0 : }
95 :
96 373 : void parse_or_throw( bool parsed, spb::char_stream & stream, std::string_view message )
97 : {
98 373 : if( !parsed )
99 : {
100 0 : stream.throw_parse_error( message );
101 : }
102 373 : }
103 :
104 2761 : void consume_or_fail( spb::char_stream & stream, char c )
105 : {
106 2761 : if( !stream.consume( c ) )
107 : {
108 0 : return stream.throw_parse_error( "(expecting '" + std::string( 1, c ) + "')" );
109 : }
110 : }
111 :
112 16 : void consume_or_fail( spb::char_stream & stream, std::string_view token )
113 : {
114 16 : if( !stream.consume( token ) )
115 : {
116 0 : return stream.throw_parse_error( "(expecting '" + std::string( token ) + "')" );
117 : }
118 : }
119 :
120 1791 : void skip_white_space_until_new_line( spb::char_stream & stream )
121 : {
122 1963 : while( ( isspace( stream.current_char( ) ) != 0 ) && stream.current_char( ) != '\n' )
123 : {
124 172 : stream.consume_current_char( false );
125 : }
126 1791 : }
127 :
128 : template < typename T >
129 : concept int_or_float = std::integral< T > || std::floating_point< T >;
130 :
131 1748 : auto consume_number( spb::char_stream & stream, std::integral auto & number ) -> bool
132 : {
133 1748 : number = { };
134 1748 : auto base = 10;
135 1748 : if( stream.consume( '0' ) )
136 : {
137 63 : base = 8;
138 63 : if( stream.consume( 'x' ) || stream.consume( 'X' ) )
139 : {
140 1 : base = 16;
141 : }
142 63 : if( !::isdigit( stream.current_char( ) ) )
143 : {
144 62 : return true;
145 : }
146 : }
147 1686 : auto result = spb_std_emu::from_chars( stream.begin( ), stream.end( ), number, base );
148 1686 : if( result.ec == std::errc{ } ) [[likely]]
149 : {
150 1686 : stream.skip_to( result.ptr );
151 1686 : return true;
152 : }
153 0 : return false;
154 : }
155 :
156 : auto consume_number( spb::char_stream & stream, std::floating_point auto & number ) -> bool
157 : {
158 : auto result = spb_std_emu::from_chars( stream.begin( ), stream.end( ), number );
159 : if( result.ec == std::errc{ } ) [[likely]]
160 : {
161 : stream.skip_to( result.ptr );
162 : return true;
163 : }
164 : return false;
165 : }
166 :
167 : auto consume_int( spb::char_stream & stream, std::integral auto & number ) -> bool
168 : {
169 : return consume_number( stream, number );
170 : }
171 :
172 : auto consume_float( spb::char_stream & stream, std::floating_point auto & number ) -> bool
173 : {
174 : return consume_number( stream, number );
175 : }
176 :
177 : template < int_or_float T >
178 1748 : auto parse_number( spb::char_stream & stream ) -> T
179 : {
180 1748 : auto result = T{ };
181 1748 : if( consume_number( stream, result ) )
182 : {
183 1748 : return result;
184 : }
185 0 : stream.throw_parse_error( "expecting number" );
186 : }
187 :
188 12 : auto parse_int_or_float( spb::char_stream & stream ) -> std::string_view
189 : {
190 12 : const auto * start = stream.begin( );
191 12 : auto number = double( );
192 12 : auto result = spb_std_emu::from_chars( stream.begin( ), stream.end( ), number );
193 12 : if( result.ec == std::errc{ } ) [[likely]]
194 : {
195 12 : stream.skip_to( result.ptr );
196 12 : return { start, static_cast< size_t >( result.ptr - start ) };
197 : }
198 0 : stream.throw_parse_error( "expecting number" );
199 : }
200 :
201 : //- parse single line comment // \n
202 2510 : void parse_comment_line( spb::char_stream & stream, proto_comment & comment )
203 : {
204 2510 : const auto * start = stream.begin( );
205 2510 : const auto end = stream.content( ).find( '\n' );
206 2510 : if( end == std::string_view::npos )
207 : {
208 0 : stream.throw_parse_error( "expecting \\n" );
209 : }
210 :
211 2510 : comment.comments.emplace_back( start - 2, end + 2 );
212 :
213 2510 : stream.skip_to( start + end + 1 );
214 2510 : }
215 :
216 : //- parse multiline comment /* */
217 89 : void parse_comment_multiline( spb::char_stream & stream, proto_comment & comment )
218 : {
219 89 : const auto * start = stream.begin( );
220 89 : const auto end = stream.content( ).find( "*/" );
221 89 : if( end == std::string_view::npos )
222 : {
223 0 : stream.throw_parse_error( "expecting */" );
224 : }
225 :
226 89 : comment.comments.emplace_back( start - 2, end + 4 );
227 89 : stream.skip_to( start + end + 2 );
228 89 : }
229 :
230 : //- parse // \n or /**/
231 2735 : auto parse_comment( spb::char_stream & stream ) -> proto_comment
232 : {
233 2735 : auto result = proto_comment{ };
234 :
235 5334 : while( stream.current_char( ) == '/' )
236 : {
237 2599 : stream.consume_current_char( false );
238 2599 : if( stream.current_char( ) == '/' )
239 : {
240 2510 : stream.consume_current_char( false );
241 2510 : parse_comment_line( stream, result );
242 : }
243 89 : else if( stream.current_char( ) == '*' )
244 : {
245 89 : stream.consume_current_char( false );
246 89 : parse_comment_multiline( stream, result );
247 : }
248 : else
249 : {
250 0 : stream.throw_parse_error( "expecting // or /*" );
251 : }
252 : }
253 2735 : return result;
254 0 : }
255 :
256 : //- parse ;
257 1917 : [[nodiscard]] auto parse_empty_statement( spb::char_stream & stream ) -> bool
258 : {
259 1917 : return stream.consume( ';' );
260 : }
261 :
262 : //- parse "string" | 'string'
263 149 : [[nodiscard]] auto parse_string_literal( spb::char_stream & stream ) -> std::string_view
264 : {
265 149 : const auto c = stream.current_char( );
266 149 : if( c != '"' && c != '\'' )
267 : {
268 0 : stream.throw_parse_error( "expecting \" or '" );
269 : return { };
270 : }
271 :
272 149 : stream.consume_current_char( false );
273 149 : const auto * start = stream.begin( );
274 149 : auto current = stream.current_char( );
275 1188 : while( ( current != 0 ) && current != c )
276 : {
277 1039 : stream.consume_current_char( false );
278 1039 : current = stream.current_char( );
279 : }
280 :
281 149 : if( current != c )
282 : {
283 0 : stream.throw_parse_error( "missing string end" );
284 : }
285 :
286 149 : auto result = std::string_view( start, static_cast< size_t >( stream.begin( ) - start ) );
287 149 : stream.consume_current_char( true );
288 149 : return result;
289 : }
290 :
291 4098 : [[nodiscard]] auto parse_ident( spb::char_stream & stream, bool skip_last_white_space = true )
292 : -> std::string_view
293 : {
294 4098 : const auto * start = stream.begin( );
295 :
296 4098 : if( isalpha( stream.current_char( ) ) == 0 )
297 : {
298 0 : stream.throw_parse_error( "expecting identifier(a-zA-Z)" );
299 : return { };
300 : }
301 :
302 4098 : stream.consume_current_char( false );
303 4098 : auto current = stream.current_char( );
304 36079 : while( ( current != 0 ) && ( isalnum( current ) != 0 || current == '_' ) )
305 : {
306 31981 : stream.consume_current_char( false );
307 31981 : current = stream.current_char( );
308 : }
309 :
310 4098 : auto result = std::string_view( start, static_cast< size_t >( stream.begin( ) - start ) );
311 4098 : if( skip_last_white_space )
312 : {
313 2712 : stream.consume_space( );
314 : }
315 4098 : return result;
316 : }
317 :
318 1336 : [[nodiscard]] auto parse_full_ident( spb::char_stream & stream ) -> std::string_view
319 : {
320 1336 : const auto * start = stream.begin( );
321 :
322 : for( ;; )
323 : {
324 1386 : ( void ) parse_ident( stream, false );
325 1386 : if( stream.current_char( ) != '.' )
326 : {
327 1336 : break;
328 : }
329 50 : stream.consume_current_char( false );
330 : }
331 1336 : auto result = std::string_view{ start, static_cast< size_t >( stream.begin( ) - start ) };
332 1336 : stream.consume_space( );
333 1336 : return result;
334 : }
335 :
336 0 : void parse_top_level_service_body( spb::char_stream & stream, proto_file &, proto_comment && )
337 : {
338 0 : return stream.throw_parse_error( "not implemented" );
339 : }
340 :
341 1791 : void consume_statement_end( spb::char_stream & stream, proto_comment & comment )
342 : {
343 1791 : if( stream.current_char( ) != ';' )
344 : {
345 0 : return stream.throw_parse_error( R"(expecting ";")" );
346 : }
347 1791 : stream.consume_current_char( false );
348 1791 : skip_white_space_until_new_line( stream );
349 1791 : if( stream.current_char( ) == '/' )
350 : {
351 76 : const auto line_comment = parse_comment( stream );
352 76 : comment.comments.insert( comment.comments.end( ), line_comment.comments.begin( ),
353 : line_comment.comments.end( ) );
354 76 : }
355 1791 : stream.consume_space( );
356 : }
357 :
358 14 : void parse_top_level_syntax_body( spb::char_stream & stream, proto_syntax & syntax,
359 : proto_comment && comment )
360 : {
361 : //- syntax = ( "proto2" | "proto3" );
362 :
363 14 : consume_or_fail( stream, '=' );
364 :
365 14 : syntax.comments = std::move( comment );
366 14 : if( stream.consume( R"("proto2")" ) )
367 : {
368 11 : syntax.version = 2;
369 11 : return consume_statement_end( stream, syntax.comments );
370 : }
371 :
372 3 : if( stream.consume( R"("proto3")" ) )
373 : {
374 3 : syntax.version = 3;
375 3 : return consume_statement_end( stream, syntax.comments );
376 : }
377 :
378 0 : stream.throw_parse_error( "expecting proto2 or proto3" );
379 : }
380 :
381 14 : void parse_top_level_syntax_or_service( spb::char_stream & stream, proto_file & file,
382 : proto_comment && comment )
383 : {
384 14 : if( stream.consume( "syntax" ) )
385 : {
386 14 : return parse_top_level_syntax_body( stream, file.syntax, std::move( comment ) );
387 : }
388 :
389 0 : if( stream.consume( "service" ) )
390 : {
391 0 : return parse_top_level_service_body( stream, file, std::move( comment ) );
392 : }
393 :
394 0 : stream.throw_parse_error( "expecting syntax or service" );
395 : }
396 :
397 2 : void parse_top_level_import( spb::char_stream & stream, proto_imports & imports,
398 : proto_comment && comment )
399 : {
400 : // "import" [ "weak" | "public" ] strLit ";"
401 2 : consume_or_fail( stream, "import" );
402 2 : stream.consume( "weak" ) || stream.consume( "public" );
403 4 : imports.emplace_back( proto_import{ .file_name = parse_string_literal( stream ),
404 2 : .comments = std::move( comment ) } );
405 2 : consume_statement_end( stream, imports.back( ).comments );
406 2 : }
407 :
408 14 : void parse_top_level_package( spb::char_stream & stream, proto_base & package,
409 : proto_comment && comment )
410 : {
411 : //- "package" fullIdent ";"
412 14 : consume_or_fail( stream, "package" );
413 14 : package.name = parse_full_ident( stream );
414 14 : package.comment = std::move( comment );
415 14 : consume_statement_end( stream, package.comment );
416 14 : }
417 :
418 314 : [[nodiscard]] auto parse_option_name( spb::char_stream & stream ) -> std::string_view
419 : {
420 314 : auto ident = std::string_view{ };
421 :
422 : //- ( ident | "(" fullIdent ")" ) { "." ident }
423 314 : parse_comment( stream );
424 314 : if( stream.consume( '(' ) )
425 : {
426 0 : ident = parse_full_ident( stream );
427 0 : consume_or_fail( stream, ')' );
428 : }
429 : else
430 : {
431 314 : ident = parse_ident( stream );
432 : }
433 314 : auto ident2 = std::string_view{ };
434 :
435 451 : while( stream.consume( '.' ) )
436 : {
437 137 : ident2 = parse_ident( stream );
438 : }
439 :
440 314 : if( ident2.empty( ) )
441 : {
442 177 : return ident;
443 : }
444 :
445 : return { ident.data( ),
446 137 : static_cast< size_t >( ident2.data( ) + ident2.size( ) - ident.data( ) ) };
447 : }
448 :
449 314 : [[nodiscard]] auto parse_constant( spb::char_stream & stream ) -> std::string_view
450 : {
451 : //- fullIdent | ( [ "-" | "+" ] intLit ) | ( [ "-" | "+" ] floatLit ) | strLit | boolLit |
452 : // MessageValue
453 314 : if( stream.consume( "true" ) )
454 : {
455 69 : return "true";
456 : }
457 245 : if( stream.consume( "false" ) )
458 : {
459 39 : return "false";
460 : }
461 206 : const auto c = stream.current_char( );
462 206 : if( c == '"' || c == '\'' )
463 : {
464 147 : return parse_string_literal( stream );
465 : }
466 59 : if( isdigit( c ) || c == '+' || c == '-' )
467 : {
468 12 : return parse_int_or_float( stream );
469 : }
470 47 : return parse_full_ident( stream );
471 : }
472 :
473 314 : void parse_option_body( spb::char_stream & stream, proto_options & options )
474 : {
475 314 : const auto option_name = parse_option_name( stream );
476 314 : consume_or_fail( stream, '=' );
477 314 : options[ option_name ] = parse_constant( stream );
478 314 : }
479 :
480 2322 : void parse_option_from_comment( const spb::char_stream & stream, proto_options & options,
481 : std::string_view comment )
482 : {
483 : for( ;; )
484 : {
485 2459 : auto start = comment.find( "[[" );
486 2459 : if( start == std::string_view::npos )
487 : {
488 2322 : return;
489 : }
490 137 : auto end = comment.find( "]]", start + 2 );
491 137 : if( end == std::string_view::npos )
492 : {
493 0 : return;
494 : }
495 137 : auto option = comment.substr( start + 2, end - start - 2 );
496 137 : comment.remove_prefix( end + 2 );
497 137 : auto option_stream = stream;
498 137 : option_stream.skip_to( option.data( ) );
499 137 : parse_option_body( option_stream, options );
500 137 : }
501 : }
502 :
503 2171 : void parse_options_from_comments( const spb::char_stream & stream, proto_options & options,
504 : const proto_comment & comment )
505 : {
506 4493 : for( auto & c : comment.comments )
507 : {
508 2322 : parse_option_from_comment( stream, options, c );
509 : }
510 2171 : }
511 :
512 1684 : [[nodiscard]] auto parse_option( spb::char_stream & stream, proto_options & options,
513 : proto_comment && comment ) -> bool
514 : {
515 : //- "option" optionName "=" constant ";"
516 1684 : if( !stream.consume( "option" ) )
517 : {
518 1668 : return false;
519 : }
520 16 : parse_option_body( stream, options );
521 16 : consume_statement_end( stream, comment );
522 16 : parse_options_from_comments( stream, options, comment );
523 16 : return true;
524 : }
525 :
526 0 : void parse_reserved_names( spb::char_stream & stream, proto_reserved_name & name,
527 : proto_comment && comment )
528 : {
529 : //- strFieldNames = strFieldName { "," strFieldName }
530 : //- strFieldName = "'" fieldName "'" | '"' fieldName '"'
531 : do
532 : {
533 0 : name.insert( parse_string_literal( stream ) );
534 0 : } while( stream.consume( ',' ) );
535 :
536 0 : consume_statement_end( stream, comment );
537 0 : comment.comments.clear( );
538 0 : }
539 :
540 25 : void parse_reserved_ranges( spb::char_stream & stream, proto_reserved_range & range,
541 : proto_comment && comment )
542 : {
543 : //- ranges = range { "," range }
544 : //- range = intLit [ "to" ( intLit | "max" ) ]
545 : do
546 : {
547 27 : const auto number = parse_number< uint32_t >( stream );
548 27 : auto number2 = number;
549 :
550 27 : if( stream.consume( "to" ) )
551 : {
552 10 : if( stream.consume( "max" ) )
553 : {
554 9 : number2 = std::numeric_limits< decltype( number2 ) >::max( );
555 : }
556 : else
557 : {
558 1 : number2 = parse_number< uint32_t >( stream );
559 : }
560 : }
561 :
562 27 : range.emplace_back( number, number2 );
563 27 : } while( stream.consume( ',' ) );
564 :
565 25 : consume_statement_end( stream, comment );
566 25 : comment.comments.clear( );
567 25 : }
568 :
569 1293 : [[nodiscard]] auto parse_extensions( spb::char_stream & stream, proto_reserved_range & extensions,
570 : proto_comment && comment ) -> bool
571 : {
572 : //- extensions = "extensions" ranges ";"
573 1293 : if( !stream.consume( "extensions" ) )
574 : {
575 1278 : return false;
576 : }
577 :
578 15 : parse_reserved_ranges( stream, extensions, std::move( comment ) );
579 15 : return true;
580 : }
581 :
582 1667 : [[nodiscard]] auto parse_reserved( spb::char_stream & stream, proto_reserved & reserved,
583 : proto_comment && comment ) -> bool
584 : {
585 : //- reserved = "reserved" ( ranges | strFieldNames ) ";"
586 1667 : if( !stream.consume( "reserved" ) )
587 : {
588 1657 : return false;
589 : }
590 :
591 10 : auto c = stream.current_char( );
592 10 : if( c == '\'' || c == '"' )
593 : {
594 0 : parse_reserved_names( stream, reserved.reserved_name, std::move( comment ) );
595 0 : return true;
596 : }
597 10 : parse_reserved_ranges( stream, reserved.reserved_range, std::move( comment ) );
598 10 : return true;
599 : }
600 :
601 1720 : [[nodiscard]] auto parse_field_options( spb::char_stream & stream ) -> proto_options
602 : {
603 1720 : auto options = proto_options{ };
604 1720 : if( stream.consume( '[' ) )
605 : {
606 145 : auto first = true;
607 306 : while( !stream.consume( ']' ) )
608 : {
609 161 : if( !first )
610 : {
611 16 : consume_or_fail( stream, ',' );
612 : }
613 161 : parse_option_body( stream, options );
614 161 : first = false;
615 : }
616 : }
617 1720 : return options;
618 0 : }
619 :
620 445 : void parse_enum_field( spb::char_stream & stream, proto_enum & new_enum, proto_comment && comment )
621 : {
622 : //- enumField = ident "=" [ "-" ] intLit [ "[" enumValueOption { "," enumValueOption } "]" ]";"
623 : //- enumValueOption = optionName "=" constant
624 : auto field =
625 445 : proto_base{ .name = parse_ident( stream ),
626 445 : .number = ( consume_or_fail( stream, '=' ),
627 445 : parse_number< decltype( proto_field::number ) >( stream ) ),
628 : .options = parse_field_options( stream ),
629 445 : .comment = std::move( comment ) };
630 :
631 445 : consume_statement_end( stream, field.comment );
632 445 : new_enum.fields.push_back( field );
633 445 : }
634 :
635 85 : [[nodiscard]] auto parse_enum_body( spb::char_stream & stream, proto_comment && enum_comment )
636 : -> proto_enum
637 : {
638 : //- enumBody = "{" { option | enumField | emptyStatement | reserved } "}"
639 :
640 85 : auto new_enum = proto_enum{
641 : proto_base{
642 85 : .name = parse_ident( stream ),
643 85 : .comment = std::move( enum_comment ),
644 : },
645 85 : };
646 85 : consume_or_fail( stream, '{' );
647 :
648 85 : parse_options_from_comments( stream, new_enum.options, new_enum.comment );
649 :
650 534 : while( !stream.consume( '}' ) )
651 : {
652 450 : auto comment = parse_comment( stream );
653 450 : if( stream.consume( '}' ) )
654 : {
655 1 : break;
656 : }
657 :
658 449 : if( !parse_option( stream, new_enum.options, std::move( comment ) ) &&
659 894 : !parse_reserved( stream, new_enum.reserved, std::move( comment ) ) &&
660 445 : !parse_empty_statement( stream ) )
661 : {
662 445 : parse_enum_field( stream, new_enum, std::move( comment ) );
663 : }
664 450 : }
665 85 : return new_enum;
666 0 : }
667 :
668 1480 : [[nodiscard]] auto parse_enum( spb::char_stream & stream, proto_enums & enums,
669 : proto_comment && comment ) -> bool
670 : {
671 : //- enum = "enum" enumName enumBody
672 1480 : if( !stream.consume( "enum" ) )
673 : {
674 1395 : return false;
675 : }
676 85 : enums.push_back( parse_enum_body( stream, std::move( comment ) ) );
677 85 : return true;
678 : }
679 :
680 1212 : [[nodiscard]] auto parse_field_label( spb::char_stream & stream ) -> proto_field::Label
681 : {
682 1212 : if( stream.consume( "optional" ) )
683 : {
684 701 : return proto_field::Label::OPTIONAL;
685 : }
686 511 : if( stream.consume( "repeated" ) )
687 : {
688 245 : return proto_field::Label::REPEATED;
689 : }
690 266 : if( stream.consume( "required" ) )
691 : {
692 235 : return proto_field::Label::NONE;
693 : }
694 :
695 31 : return proto_field::Label::OPTIONAL;
696 : // stream.throw_parse_error( "expecting label" );
697 : }
698 :
699 1212 : void parse_field( spb::char_stream & stream, proto_fields & fields, proto_comment && comment )
700 : {
701 : //- field = label type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
702 : //- fieldOptions = fieldOption { "," fieldOption }
703 : //- fieldOption = optionName "=" constant
704 1212 : auto new_field = proto_field{
705 1212 : .label = parse_field_label( stream ),
706 1212 : .type_name = parse_full_ident( stream ),
707 1212 : };
708 :
709 1212 : new_field.name = parse_ident( stream );
710 1212 : new_field.number = ( consume_or_fail( stream, '=' ),
711 1212 : parse_number< decltype( proto_field::number ) >( stream ) );
712 1212 : new_field.options = parse_field_options( stream );
713 1212 : new_field.comment = std::move( comment );
714 1212 : consume_statement_end( stream, new_field.comment );
715 1212 : parse_options_from_comments( stream, new_field.options, new_field.comment );
716 1212 : fields.push_back( new_field );
717 1212 : }
718 :
719 : //[[nodiscard]] auto parse_extend( spb::char_stream & stream, proto_ast & ) -> bool;
720 : //[[nodiscard]] auto parse_extensions( spb::char_stream & stream, proto_fields & ) -> bool;
721 : //[[nodiscard]] auto parse_oneof( spb::char_stream & stream, proto_ast & ) -> bool;
722 :
723 52 : auto parse_map_key_type( spb::char_stream & stream ) -> std::string_view
724 : {
725 : //- keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | "fixed32" |
726 : //"fixed64" | "sfixed32" | "sfixed64" | "bool" | "string"
727 52 : constexpr auto key_types =
728 : std::array< std::string_view, 12 >{ { "int32", "int64", "uint32", "uint64", "sint32",
729 : "sint64", "fixed32", "fixed64", "sfixed32",
730 : "sfixed64", "bool", "string" } };
731 327 : for( auto key_type : key_types )
732 : {
733 327 : if( stream.consume( key_type ) )
734 : {
735 52 : return key_type;
736 : }
737 : }
738 0 : stream.throw_parse_error( "expecting map key type" );
739 : }
740 :
741 52 : auto parse_map_body( spb::char_stream & stream, proto_comment && comment ) -> proto_map
742 : {
743 : //- "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
744 :
745 52 : auto new_map = proto_map{
746 52 : .key = proto_field{ .type_name = parse_map_key_type( stream ) },
747 : .value = proto_field{ .type_name =
748 52 : ( consume_or_fail( stream, ',' ), parse_full_ident( stream ) ) },
749 52 : };
750 52 : new_map.name = ( consume_or_fail( stream, '>' ), parse_ident( stream ) );
751 52 : new_map.number =
752 52 : ( consume_or_fail( stream, '=' ), parse_number< decltype( proto_map::number ) >( stream ) );
753 52 : new_map.options = parse_field_options( stream );
754 52 : new_map.comment = std::move( comment );
755 52 : consume_statement_end( stream, new_map.comment );
756 52 : return new_map;
757 0 : }
758 :
759 1274 : [[nodiscard]] auto parse_map_field( spb::char_stream & stream, proto_maps & maps,
760 : proto_comment && comment ) -> bool
761 : {
762 : //- "map" "<"
763 1274 : if( !stream.consume( "map" ) )
764 : {
765 1222 : return false;
766 : }
767 52 : consume_or_fail( stream, '<' );
768 52 : maps.push_back( parse_map_body( stream, std::move( comment ) ) );
769 52 : return true;
770 : }
771 :
772 11 : void parse_oneof_field( spb::char_stream & stream, proto_fields & fields, proto_comment && comment )
773 : {
774 : //- oneofField = type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
775 11 : auto new_field = proto_field{ .type_name = parse_full_ident( stream ) };
776 :
777 11 : new_field.name = parse_ident( stream );
778 11 : new_field.number = ( consume_or_fail( stream, '=' ),
779 11 : parse_number< decltype( proto_field::number ) >( stream ) );
780 11 : new_field.options = parse_field_options( stream );
781 11 : new_field.comment = std::move( comment );
782 11 : consume_statement_end( stream, new_field.comment );
783 11 : fields.push_back( new_field );
784 11 : }
785 :
786 4 : [[nodiscard]] auto parse_oneof_body( spb::char_stream & stream, proto_comment && oneof_comment )
787 : -> proto_oneof
788 : {
789 : //- oneof = "oneof" oneofName "{" { option | oneofField } "}"
790 4 : auto new_oneof = proto_oneof{ proto_base{
791 4 : .name = parse_ident( stream ),
792 4 : .comment = std::move( oneof_comment ),
793 4 : } };
794 4 : consume_or_fail( stream, '{' );
795 15 : while( !stream.consume( '}' ) )
796 : {
797 11 : auto comment = parse_comment( stream );
798 11 : if( stream.consume( '}' ) )
799 : {
800 0 : break;
801 : }
802 :
803 11 : if( !parse_option( stream, new_oneof.options, std::move( comment ) ) )
804 : {
805 11 : parse_oneof_field( stream, new_oneof.fields, std::move( comment ) );
806 : }
807 11 : }
808 4 : return new_oneof;
809 0 : }
810 :
811 1278 : [[nodiscard]] auto parse_oneof( spb::char_stream & stream, proto_oneofs & oneofs,
812 : proto_comment && comment ) -> bool
813 : {
814 : //- oneof = "oneof" oneofName "{" { option | oneofField } "}"
815 1278 : if( !stream.consume( "oneof" ) )
816 : {
817 1274 : return false;
818 : }
819 4 : oneofs.push_back( parse_oneof_body( stream, std::move( comment ) ) );
820 4 : return true;
821 : }
822 :
823 : [[nodiscard]] auto parse_message( spb::char_stream & stream, proto_messages & messages,
824 : proto_comment && comment ) -> bool;
825 :
826 452 : void parse_message_body( spb::char_stream & stream, proto_messages & messages,
827 : proto_comment && message_comment )
828 : {
829 : //- messageBody = messageName "{" { field | enum | message | extend | extensions | group |
830 : // option | oneof | mapField | reserved | emptyStatement } "}"
831 452 : auto new_message = proto_message{ proto_base{
832 452 : .name = parse_ident( stream ),
833 452 : .comment = std::move( message_comment ),
834 452 : } };
835 :
836 452 : consume_or_fail( stream, '{' );
837 452 : parse_options_from_comments( stream, new_message.options, new_message.comment );
838 :
839 1921 : while( !stream.consume( '}' ) )
840 : {
841 1478 : auto comment = parse_comment( stream );
842 1478 : if( stream.consume( '}' ) )
843 : {
844 9 : break;
845 : }
846 :
847 1469 : if( !parse_empty_statement( stream ) &&
848 1469 : !parse_enum( stream, new_message.enums, std::move( comment ) ) &&
849 1395 : !parse_message( stream, new_message.messages, std::move( comment ) ) &&
850 : //! parse_extend( stream, new_message.extends ) &&
851 1293 : !parse_extensions( stream, new_message.extensions, std::move( comment ) ) &&
852 1278 : !parse_oneof( stream, new_message.oneofs, std::move( comment ) ) &&
853 1274 : !parse_map_field( stream, new_message.maps, std::move( comment ) ) &&
854 4150 : !parse_reserved( stream, new_message.reserved, std::move( comment ) ) &&
855 1212 : !parse_option( stream, new_message.options, std::move( comment ) ) )
856 : {
857 1212 : parse_field( stream, new_message.fields, std::move( comment ) );
858 : }
859 1478 : }
860 452 : messages.push_back( new_message );
861 452 : }
862 :
863 1745 : [[nodiscard]] auto parse_message( spb::char_stream & stream, proto_messages & messages,
864 : proto_comment && comment ) -> bool
865 : {
866 : //- "message" messageName messageBody
867 1745 : if( !stream.consume( "message" ) )
868 : {
869 1293 : return false;
870 : }
871 :
872 452 : parse_message_body( stream, messages, std::move( comment ) );
873 452 : return true;
874 : }
875 :
876 12 : void parse_top_level_option( spb::char_stream & stream, proto_options & options,
877 : proto_comment && comment )
878 : {
879 12 : parse_or_throw( parse_option( stream, options, std::move( comment ) ), stream,
880 : "expecting option" );
881 12 : }
882 :
883 350 : void parse_top_level_message( spb::char_stream & stream, proto_messages & messages,
884 : proto_comment && comment )
885 : {
886 350 : parse_or_throw( parse_message( stream, messages, std::move( comment ) ), stream,
887 : "expecting message" );
888 350 : }
889 :
890 11 : void parse_top_level_enum( spb::char_stream & stream, proto_enums & enums,
891 : proto_comment && comment )
892 : {
893 11 : parse_or_throw( parse_enum( stream, enums, std::move( comment ) ), stream, "expecting enum" );
894 11 : }
895 :
896 406 : void parse_top_level( spb::char_stream & stream, proto_file & file, proto_comment && comment )
897 : {
898 406 : switch( stream.current_char( ) )
899 : {
900 0 : case '\0':
901 0 : return;
902 14 : case 's':
903 14 : return parse_top_level_syntax_or_service( stream, file, std::move( comment ) );
904 2 : case 'i':
905 2 : return parse_top_level_import( stream, file.imports, std::move( comment ) );
906 14 : case 'p':
907 14 : return parse_top_level_package( stream, file.package, std::move( comment ) );
908 12 : case 'o':
909 12 : return parse_top_level_option( stream, file.options, std::move( comment ) );
910 350 : case 'm':
911 350 : return parse_top_level_message( stream, file.package.messages, std::move( comment ) );
912 11 : case 'e':
913 11 : return parse_top_level_enum( stream, file.package.enums, std::move( comment ) );
914 3 : case ';':
915 3 : return ( void ) parse_empty_statement( stream );
916 :
917 0 : default:
918 0 : return stream.throw_parse_error( "expecting top level definition" );
919 : }
920 : }
921 :
922 14 : void set_default_options( proto_file & file )
923 : {
924 14 : file.options[ option_optional_type ] = "std::optional<$>";
925 14 : file.options[ option_optional_include ] = "<optional>";
926 :
927 14 : file.options[ option_repeated_type ] = "std::vector<$>";
928 14 : file.options[ option_repeated_include ] = "<vector>";
929 :
930 14 : file.options[ option_string_type ] = "std::string";
931 14 : file.options[ option_string_include ] = "<string>";
932 :
933 14 : file.options[ option_bytes_type ] = "std::vector<$>";
934 14 : file.options[ option_bytes_include ] = "<vector>";
935 :
936 14 : file.options[ option_pointer_type ] = "std::unique_ptr<$>";
937 14 : file.options[ option_pointer_include ] = "<memory>";
938 :
939 14 : file.options[ option_enum_type ] = "int32";
940 14 : }
941 :
942 14 : [[nodiscard]] auto parse_proto_file( fs::path file, parsed_files & already_parsed,
943 : std::span< const fs::path > import_paths,
944 : const fs::path & base_dir ) -> proto_file
945 : {
946 : try
947 : {
948 14 : file = find_file_in_paths( file, import_paths, base_dir );
949 :
950 14 : auto result = proto_file{
951 : .path = file,
952 : .content = load_file( file ),
953 14 : };
954 :
955 14 : parse_proto_file_content( result );
956 14 : already_parsed.insert( file.string( ) );
957 : result.file_imports =
958 14 : parse_all_imports( result, already_parsed, import_paths, file.parent_path( ) );
959 14 : resolve_messages( result );
960 28 : return result;
961 0 : }
962 0 : catch( const std::exception & e )
963 : {
964 0 : throw std::runtime_error( file.string( ) + ":" + e.what( ) );
965 0 : }
966 : }
967 :
968 : }// namespace
969 :
970 14 : void parse_proto_file_content( proto_file & file )
971 : {
972 14 : set_default_options( file );
973 :
974 14 : auto stream = spb::char_stream( file.content );
975 :
976 420 : while( !stream.empty( ) )
977 : {
978 406 : auto comment = parse_comment( stream );
979 406 : parse_options_from_comments( stream, file.options, comment );
980 406 : parse_top_level( stream, file, std::move( comment ) );
981 406 : }
982 14 : }
983 :
984 12 : auto parse_proto_file( const fs::path & file, std::span< const fs::path > import_paths,
985 : const fs::path & base_dir ) -> proto_file
986 : {
987 12 : auto already_parsed = parsed_files( );
988 24 : return parse_proto_file( file, already_parsed, import_paths, base_dir );
989 12 : }
990 :
991 26 : [[nodiscard]] auto cpp_file_name_from_proto( const fs::path & proto_file_path,
992 : std::string_view extension ) -> fs::path
993 : {
994 26 : return proto_file_path.stem( ).concat( extension );
995 : }
|