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.h>
18 : #include <ast/ast-types.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 2659 : auto parse_comment( spb::char_stream & stream ) -> proto_comment
232 : {
233 2659 : auto result = proto_comment{ };
234 :
235 5182 : while( stream.current_char( ) == '/' )
236 : {
237 2523 : stream.consume_current_char( false );
238 2523 : if( stream.current_char( ) == '/' )
239 : {
240 2434 : stream.consume_current_char( false );
241 2434 : 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 2659 : 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 : stream.consume_current_char( false );
352 76 : auto line_comment = proto_comment( );
353 76 : parse_comment_line( stream, line_comment );
354 76 : comment.comments.insert( comment.comments.end( ), line_comment.comments.begin( ),
355 : line_comment.comments.end( ) );
356 76 : }
357 1791 : stream.consume_space( );
358 : }
359 :
360 14 : void parse_top_level_syntax_body( spb::char_stream & stream, proto_syntax & syntax,
361 : proto_comment && comment )
362 : {
363 : //- syntax = ( "proto2" | "proto3" );
364 :
365 14 : consume_or_fail( stream, '=' );
366 :
367 14 : syntax.comments = std::move( comment );
368 14 : if( stream.consume( R"("proto2")" ) )
369 : {
370 11 : syntax.version = 2;
371 11 : return consume_statement_end( stream, syntax.comments );
372 : }
373 :
374 3 : if( stream.consume( R"("proto3")" ) )
375 : {
376 3 : syntax.version = 3;
377 3 : return consume_statement_end( stream, syntax.comments );
378 : }
379 :
380 0 : stream.throw_parse_error( "expecting proto2 or proto3" );
381 : }
382 :
383 14 : void parse_top_level_syntax_or_service( spb::char_stream & stream, proto_file & file,
384 : proto_comment && comment )
385 : {
386 14 : if( stream.consume( "syntax" ) )
387 : {
388 14 : return parse_top_level_syntax_body( stream, file.syntax, std::move( comment ) );
389 : }
390 :
391 0 : if( stream.consume( "service" ) )
392 : {
393 0 : return parse_top_level_service_body( stream, file, std::move( comment ) );
394 : }
395 :
396 0 : stream.throw_parse_error( "expecting syntax or service" );
397 : }
398 :
399 2 : void parse_top_level_import( spb::char_stream & stream, proto_imports & imports,
400 : proto_comment && comment )
401 : {
402 : // "import" [ "weak" | "public" ] strLit ";"
403 2 : consume_or_fail( stream, "import" );
404 2 : stream.consume( "weak" ) || stream.consume( "public" );
405 4 : imports.emplace_back( proto_import{ .file_name = parse_string_literal( stream ),
406 2 : .comments = std::move( comment ) } );
407 2 : consume_statement_end( stream, imports.back( ).comments );
408 2 : }
409 :
410 14 : void parse_top_level_package( spb::char_stream & stream, proto_base & package,
411 : proto_comment && comment )
412 : {
413 : //- "package" fullIdent ";"
414 14 : consume_or_fail( stream, "package" );
415 14 : package.name = parse_full_ident( stream );
416 14 : package.comment = std::move( comment );
417 14 : consume_statement_end( stream, package.comment );
418 14 : }
419 :
420 314 : [[nodiscard]] auto parse_option_name( spb::char_stream & stream ) -> std::string_view
421 : {
422 314 : auto ident = std::string_view{ };
423 :
424 : //- ( ident | "(" fullIdent ")" ) { "." ident }
425 314 : parse_comment( stream );
426 314 : if( stream.consume( '(' ) )
427 : {
428 0 : ident = parse_full_ident( stream );
429 0 : consume_or_fail( stream, ')' );
430 : }
431 : else
432 : {
433 314 : ident = parse_ident( stream );
434 : }
435 314 : auto ident2 = std::string_view{ };
436 :
437 451 : while( stream.consume( '.' ) )
438 : {
439 137 : ident2 = parse_ident( stream );
440 : }
441 :
442 314 : if( ident2.empty( ) )
443 : {
444 177 : return ident;
445 : }
446 :
447 : return { ident.data( ),
448 137 : static_cast< size_t >( ident2.data( ) + ident2.size( ) - ident.data( ) ) };
449 : }
450 :
451 314 : [[nodiscard]] auto parse_constant( spb::char_stream & stream ) -> std::string_view
452 : {
453 : //- fullIdent | ( [ "-" | "+" ] intLit ) | ( [ "-" | "+" ] floatLit ) | strLit | boolLit |
454 : // MessageValue
455 314 : if( stream.consume( "true" ) )
456 : {
457 69 : return "true";
458 : }
459 245 : if( stream.consume( "false" ) )
460 : {
461 39 : return "false";
462 : }
463 206 : const auto c = stream.current_char( );
464 206 : if( c == '"' || c == '\'' )
465 : {
466 147 : return parse_string_literal( stream );
467 : }
468 59 : if( isdigit( c ) || c == '+' || c == '-' )
469 : {
470 12 : return parse_int_or_float( stream );
471 : }
472 47 : return parse_full_ident( stream );
473 : }
474 :
475 314 : void parse_option_body( spb::char_stream & stream, proto_options & options )
476 : {
477 314 : const auto option_name = parse_option_name( stream );
478 314 : consume_or_fail( stream, '=' );
479 314 : options[ option_name ] = parse_constant( stream );
480 314 : }
481 :
482 2342 : void parse_option_from_comment( const spb::char_stream & stream, proto_options & options,
483 : std::string_view comment )
484 : {
485 : for( ;; )
486 : {
487 2479 : auto start = comment.find( "[[" );
488 2479 : if( start == std::string_view::npos )
489 : {
490 2342 : return;
491 : }
492 137 : auto end = comment.find( "]]", start + 2 );
493 137 : if( end == std::string_view::npos )
494 : {
495 0 : return;
496 : }
497 137 : auto option = comment.substr( start + 2, end - start - 2 );
498 137 : comment.remove_prefix( end + 2 );
499 137 : auto option_stream = stream;
500 137 : option_stream.skip_to( option.data( ) );
501 137 : parse_option_body( option_stream, options );
502 137 : }
503 : }
504 :
505 2171 : void parse_options_from_comments( const spb::char_stream & stream, proto_options & options,
506 : const proto_comment & comment )
507 : {
508 4513 : for( auto & c : comment.comments )
509 : {
510 2342 : parse_option_from_comment( stream, options, c );
511 : }
512 2171 : }
513 :
514 1684 : [[nodiscard]] auto parse_option( spb::char_stream & stream, proto_options & options,
515 : proto_comment && comment ) -> bool
516 : {
517 : //- "option" optionName "=" constant ";"
518 1684 : if( !stream.consume( "option" ) )
519 : {
520 1668 : return false;
521 : }
522 16 : parse_option_body( stream, options );
523 16 : consume_statement_end( stream, comment );
524 16 : parse_options_from_comments( stream, options, comment );
525 16 : return true;
526 : }
527 :
528 0 : void parse_reserved_names( spb::char_stream & stream, proto_reserved_name & name,
529 : proto_comment && comment )
530 : {
531 : //- strFieldNames = strFieldName { "," strFieldName }
532 : //- strFieldName = "'" fieldName "'" | '"' fieldName '"'
533 : do
534 : {
535 0 : name.insert( parse_string_literal( stream ) );
536 0 : } while( stream.consume( ',' ) );
537 :
538 0 : consume_statement_end( stream, comment );
539 0 : comment.comments.clear( );
540 0 : }
541 :
542 25 : void parse_reserved_ranges( spb::char_stream & stream, proto_reserved_range & range,
543 : proto_comment && comment )
544 : {
545 : //- ranges = range { "," range }
546 : //- range = intLit [ "to" ( intLit | "max" ) ]
547 : do
548 : {
549 27 : const auto number = parse_number< uint32_t >( stream );
550 27 : auto number2 = number;
551 :
552 27 : if( stream.consume( "to" ) )
553 : {
554 10 : if( stream.consume( "max" ) )
555 : {
556 9 : number2 = std::numeric_limits< decltype( number2 ) >::max( );
557 : }
558 : else
559 : {
560 1 : number2 = parse_number< uint32_t >( stream );
561 : }
562 : }
563 :
564 27 : range.emplace_back( number, number2 );
565 27 : } while( stream.consume( ',' ) );
566 :
567 25 : consume_statement_end( stream, comment );
568 25 : comment.comments.clear( );
569 25 : }
570 :
571 1293 : [[nodiscard]] auto parse_extensions( spb::char_stream & stream, proto_reserved_range & extensions,
572 : proto_comment && comment ) -> bool
573 : {
574 : //- extensions = "extensions" ranges ";"
575 1293 : if( !stream.consume( "extensions" ) )
576 : {
577 1278 : return false;
578 : }
579 :
580 15 : parse_reserved_ranges( stream, extensions, std::move( comment ) );
581 15 : return true;
582 : }
583 :
584 1667 : [[nodiscard]] auto parse_reserved( spb::char_stream & stream, proto_reserved & reserved,
585 : proto_comment && comment ) -> bool
586 : {
587 : //- reserved = "reserved" ( ranges | strFieldNames ) ";"
588 1667 : if( !stream.consume( "reserved" ) )
589 : {
590 1657 : return false;
591 : }
592 :
593 10 : auto c = stream.current_char( );
594 10 : if( c == '\'' || c == '"' )
595 : {
596 0 : parse_reserved_names( stream, reserved.reserved_name, std::move( comment ) );
597 0 : return true;
598 : }
599 10 : parse_reserved_ranges( stream, reserved.reserved_range, std::move( comment ) );
600 10 : return true;
601 : }
602 :
603 1720 : [[nodiscard]] auto parse_field_options( spb::char_stream & stream ) -> proto_options
604 : {
605 1720 : auto options = proto_options{ };
606 1720 : if( stream.consume( '[' ) )
607 : {
608 145 : auto first = true;
609 306 : while( !stream.consume( ']' ) )
610 : {
611 161 : if( !first )
612 : {
613 16 : consume_or_fail( stream, ',' );
614 : }
615 161 : parse_option_body( stream, options );
616 161 : first = false;
617 : }
618 : }
619 1720 : return options;
620 0 : }
621 :
622 445 : void parse_enum_field( spb::char_stream & stream, proto_enum & new_enum, proto_comment && comment )
623 : {
624 : //- enumField = ident "=" [ "-" ] intLit [ "[" enumValueOption { "," enumValueOption } "]" ]";"
625 : //- enumValueOption = optionName "=" constant
626 : auto field =
627 445 : proto_base{ .name = parse_ident( stream ),
628 445 : .number = ( consume_or_fail( stream, '=' ),
629 445 : parse_number< decltype( proto_field::number ) >( stream ) ),
630 : .options = parse_field_options( stream ),
631 445 : .comment = std::move( comment ) };
632 :
633 445 : consume_statement_end( stream, field.comment );
634 445 : new_enum.fields.push_back( field );
635 445 : }
636 :
637 85 : [[nodiscard]] auto parse_enum_body( spb::char_stream & stream, proto_comment && enum_comment )
638 : -> proto_enum
639 : {
640 : //- enumBody = "{" { option | enumField | emptyStatement | reserved } "}"
641 :
642 85 : auto new_enum = proto_enum{
643 : proto_base{
644 85 : .name = parse_ident( stream ),
645 85 : .comment = std::move( enum_comment ),
646 : },
647 85 : };
648 85 : consume_or_fail( stream, '{' );
649 :
650 85 : parse_options_from_comments( stream, new_enum.options, new_enum.comment );
651 :
652 534 : while( !stream.consume( '}' ) )
653 : {
654 450 : auto comment = parse_comment( stream );
655 450 : if( stream.consume( '}' ) )
656 : {
657 1 : break;
658 : }
659 :
660 449 : if( !parse_option( stream, new_enum.options, std::move( comment ) ) &&
661 894 : !parse_reserved( stream, new_enum.reserved, std::move( comment ) ) &&
662 445 : !parse_empty_statement( stream ) )
663 : {
664 445 : parse_enum_field( stream, new_enum, std::move( comment ) );
665 : }
666 450 : }
667 85 : return new_enum;
668 0 : }
669 :
670 1480 : [[nodiscard]] auto parse_enum( spb::char_stream & stream, proto_enums & enums,
671 : proto_comment && comment ) -> bool
672 : {
673 : //- enum = "enum" enumName enumBody
674 1480 : if( !stream.consume( "enum" ) )
675 : {
676 1395 : return false;
677 : }
678 85 : enums.push_back( parse_enum_body( stream, std::move( comment ) ) );
679 85 : return true;
680 : }
681 :
682 1212 : [[nodiscard]] auto parse_field_label( spb::char_stream & stream ) -> proto_field::Label
683 : {
684 1212 : if( stream.consume( "optional" ) )
685 : {
686 701 : return proto_field::Label::OPTIONAL;
687 : }
688 511 : if( stream.consume( "repeated" ) )
689 : {
690 245 : return proto_field::Label::REPEATED;
691 : }
692 266 : if( stream.consume( "required" ) )
693 : {
694 235 : return proto_field::Label::NONE;
695 : }
696 :
697 31 : return proto_field::Label::OPTIONAL;
698 : // stream.throw_parse_error( "expecting label" );
699 : }
700 :
701 1212 : void parse_field( spb::char_stream & stream, proto_fields & fields, proto_comment && comment )
702 : {
703 : //- field = label type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
704 : //- fieldOptions = fieldOption { "," fieldOption }
705 : //- fieldOption = optionName "=" constant
706 1212 : auto new_field = proto_field{
707 1212 : .label = parse_field_label( stream ),
708 1212 : .type_name = parse_full_ident( stream ),
709 1212 : };
710 :
711 1212 : new_field.name = parse_ident( stream );
712 1212 : new_field.number = ( consume_or_fail( stream, '=' ),
713 1212 : parse_number< decltype( proto_field::number ) >( stream ) );
714 1212 : new_field.options = parse_field_options( stream );
715 1212 : new_field.comment = std::move( comment );
716 1212 : consume_statement_end( stream, new_field.comment );
717 1212 : parse_options_from_comments( stream, new_field.options, new_field.comment );
718 1212 : fields.push_back( new_field );
719 1212 : }
720 :
721 : //[[nodiscard]] auto parse_extend( spb::char_stream & stream, proto_ast & ) -> bool;
722 : //[[nodiscard]] auto parse_extensions( spb::char_stream & stream, proto_fields & ) -> bool;
723 : //[[nodiscard]] auto parse_oneof( spb::char_stream & stream, proto_ast & ) -> bool;
724 :
725 52 : auto parse_map_key_type( spb::char_stream & stream ) -> std::string_view
726 : {
727 : //- keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | "fixed32" |
728 : //"fixed64" | "sfixed32" | "sfixed64" | "bool" | "string"
729 52 : constexpr auto key_types =
730 : std::array< std::string_view, 12 >{ { "int32", "int64", "uint32", "uint64", "sint32",
731 : "sint64", "fixed32", "fixed64", "sfixed32",
732 : "sfixed64", "bool", "string" } };
733 327 : for( auto key_type : key_types )
734 : {
735 327 : if( stream.consume( key_type ) )
736 : {
737 52 : return key_type;
738 : }
739 : }
740 0 : stream.throw_parse_error( "expecting map key type" );
741 : }
742 :
743 52 : auto parse_map_body( spb::char_stream & stream, proto_comment && comment ) -> proto_map
744 : {
745 : //- "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
746 :
747 52 : auto new_map = proto_map{
748 52 : .key = proto_field{ .type_name = parse_map_key_type( stream ) },
749 : .value = proto_field{ .type_name =
750 52 : ( consume_or_fail( stream, ',' ), parse_full_ident( stream ) ) },
751 52 : };
752 52 : new_map.name = ( consume_or_fail( stream, '>' ), parse_ident( stream ) );
753 52 : new_map.number =
754 52 : ( consume_or_fail( stream, '=' ), parse_number< decltype( proto_map::number ) >( stream ) );
755 52 : new_map.options = parse_field_options( stream );
756 52 : new_map.comment = std::move( comment );
757 52 : consume_statement_end( stream, new_map.comment );
758 52 : return new_map;
759 0 : }
760 :
761 1274 : [[nodiscard]] auto parse_map_field( spb::char_stream & stream, proto_maps & maps,
762 : proto_comment && comment ) -> bool
763 : {
764 : //- "map" "<"
765 1274 : if( !stream.consume( "map" ) )
766 : {
767 1222 : return false;
768 : }
769 52 : consume_or_fail( stream, '<' );
770 52 : maps.push_back( parse_map_body( stream, std::move( comment ) ) );
771 52 : return true;
772 : }
773 :
774 11 : void parse_oneof_field( spb::char_stream & stream, proto_fields & fields, proto_comment && comment )
775 : {
776 : //- oneofField = type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
777 11 : auto new_field = proto_field{ .type_name = parse_full_ident( stream ) };
778 :
779 11 : new_field.name = parse_ident( stream );
780 11 : new_field.number = ( consume_or_fail( stream, '=' ),
781 11 : parse_number< decltype( proto_field::number ) >( stream ) );
782 11 : new_field.options = parse_field_options( stream );
783 11 : new_field.comment = std::move( comment );
784 11 : consume_statement_end( stream, new_field.comment );
785 11 : fields.push_back( new_field );
786 11 : }
787 :
788 4 : [[nodiscard]] auto parse_oneof_body( spb::char_stream & stream, proto_comment && oneof_comment )
789 : -> proto_oneof
790 : {
791 : //- oneof = "oneof" oneofName "{" { option | oneofField } "}"
792 4 : auto new_oneof = proto_oneof{ proto_base{
793 4 : .name = parse_ident( stream ),
794 4 : .comment = std::move( oneof_comment ),
795 4 : } };
796 4 : consume_or_fail( stream, '{' );
797 15 : while( !stream.consume( '}' ) )
798 : {
799 11 : auto comment = parse_comment( stream );
800 11 : if( stream.consume( '}' ) )
801 : {
802 0 : break;
803 : }
804 :
805 11 : if( !parse_option( stream, new_oneof.options, std::move( comment ) ) )
806 : {
807 11 : parse_oneof_field( stream, new_oneof.fields, std::move( comment ) );
808 : }
809 11 : }
810 4 : return new_oneof;
811 0 : }
812 :
813 1278 : [[nodiscard]] auto parse_oneof( spb::char_stream & stream, proto_oneofs & oneofs,
814 : proto_comment && comment ) -> bool
815 : {
816 : //- oneof = "oneof" oneofName "{" { option | oneofField } "}"
817 1278 : if( !stream.consume( "oneof" ) )
818 : {
819 1274 : return false;
820 : }
821 4 : oneofs.push_back( parse_oneof_body( stream, std::move( comment ) ) );
822 4 : return true;
823 : }
824 :
825 : [[nodiscard]] auto parse_message( spb::char_stream & stream, proto_messages & messages,
826 : proto_comment && comment ) -> bool;
827 :
828 452 : void parse_message_body( spb::char_stream & stream, proto_messages & messages,
829 : proto_comment && message_comment )
830 : {
831 : //- messageBody = messageName "{" { field | enum | message | extend | extensions | group |
832 : // option | oneof | mapField | reserved | emptyStatement } "}"
833 452 : auto new_message = proto_message{ proto_base{
834 452 : .name = parse_ident( stream ),
835 452 : .comment = std::move( message_comment ),
836 452 : } };
837 :
838 452 : consume_or_fail( stream, '{' );
839 452 : parse_options_from_comments( stream, new_message.options, new_message.comment );
840 :
841 1921 : while( !stream.consume( '}' ) )
842 : {
843 1478 : auto comment = parse_comment( stream );
844 1478 : if( stream.consume( '}' ) )
845 : {
846 9 : break;
847 : }
848 :
849 1469 : if( !parse_empty_statement( stream ) &&
850 1469 : !parse_enum( stream, new_message.enums, std::move( comment ) ) &&
851 1395 : !parse_message( stream, new_message.messages, std::move( comment ) ) &&
852 : //! parse_extend( stream, new_message.extends ) &&
853 1293 : !parse_extensions( stream, new_message.extensions, std::move( comment ) ) &&
854 1278 : !parse_oneof( stream, new_message.oneofs, std::move( comment ) ) &&
855 1274 : !parse_map_field( stream, new_message.maps, std::move( comment ) ) &&
856 4150 : !parse_reserved( stream, new_message.reserved, std::move( comment ) ) &&
857 1212 : !parse_option( stream, new_message.options, std::move( comment ) ) )
858 : {
859 1212 : parse_field( stream, new_message.fields, std::move( comment ) );
860 : }
861 1478 : }
862 452 : messages.push_back( new_message );
863 452 : }
864 :
865 1745 : [[nodiscard]] auto parse_message( spb::char_stream & stream, proto_messages & messages,
866 : proto_comment && comment ) -> bool
867 : {
868 : //- "message" messageName messageBody
869 1745 : if( !stream.consume( "message" ) )
870 : {
871 1293 : return false;
872 : }
873 :
874 452 : parse_message_body( stream, messages, std::move( comment ) );
875 452 : return true;
876 : }
877 :
878 12 : void parse_top_level_option( spb::char_stream & stream, proto_options & options,
879 : proto_comment && comment )
880 : {
881 12 : parse_or_throw( parse_option( stream, options, std::move( comment ) ), stream,
882 : "expecting option" );
883 12 : }
884 :
885 350 : void parse_top_level_message( spb::char_stream & stream, proto_messages & messages,
886 : proto_comment && comment )
887 : {
888 350 : parse_or_throw( parse_message( stream, messages, std::move( comment ) ), stream,
889 : "expecting message" );
890 350 : }
891 :
892 11 : void parse_top_level_enum( spb::char_stream & stream, proto_enums & enums,
893 : proto_comment && comment )
894 : {
895 11 : parse_or_throw( parse_enum( stream, enums, std::move( comment ) ), stream, "expecting enum" );
896 11 : }
897 :
898 406 : void parse_top_level( spb::char_stream & stream, proto_file & file, proto_comment && comment )
899 : {
900 406 : switch( stream.current_char( ) )
901 : {
902 0 : case '\0':
903 0 : return;
904 14 : case 's':
905 14 : return parse_top_level_syntax_or_service( stream, file, std::move( comment ) );
906 2 : case 'i':
907 2 : return parse_top_level_import( stream, file.imports, std::move( comment ) );
908 14 : case 'p':
909 14 : return parse_top_level_package( stream, file.package, std::move( comment ) );
910 12 : case 'o':
911 12 : return parse_top_level_option( stream, file.options, std::move( comment ) );
912 350 : case 'm':
913 350 : return parse_top_level_message( stream, file.package.messages, std::move( comment ) );
914 11 : case 'e':
915 11 : return parse_top_level_enum( stream, file.package.enums, std::move( comment ) );
916 3 : case ';':
917 3 : return ( void ) parse_empty_statement( stream );
918 :
919 0 : default:
920 0 : return stream.throw_parse_error( "expecting top level definition" );
921 : }
922 : }
923 :
924 14 : void set_default_options( proto_file & file )
925 : {
926 14 : file.options[ option_optional_type ] = "std::optional<$>";
927 14 : file.options[ option_optional_include ] = "<optional>";
928 :
929 14 : file.options[ option_repeated_type ] = "std::vector<$>";
930 14 : file.options[ option_repeated_include ] = "<vector>";
931 :
932 14 : file.options[ option_string_type ] = "std::string";
933 14 : file.options[ option_string_include ] = "<string>";
934 :
935 14 : file.options[ option_bytes_type ] = "std::vector<$>";
936 14 : file.options[ option_bytes_include ] = "<vector>";
937 :
938 14 : file.options[ option_pointer_type ] = "std::unique_ptr<$>";
939 14 : file.options[ option_pointer_include ] = "<memory>";
940 :
941 14 : file.options[ option_enum_type ] = "int32";
942 14 : }
943 :
944 14 : [[nodiscard]] auto parse_proto_file( fs::path file, parsed_files & already_parsed,
945 : std::span< const fs::path > import_paths,
946 : const fs::path & base_dir ) -> proto_file
947 : {
948 : try
949 : {
950 14 : file = find_file_in_paths( file, import_paths, base_dir );
951 :
952 14 : auto result = proto_file{
953 : .path = file,
954 : .content = load_file( file ),
955 14 : };
956 :
957 14 : parse_proto_file_content( result );
958 14 : already_parsed.insert( file.string( ) );
959 : result.file_imports =
960 14 : parse_all_imports( result, already_parsed, import_paths, file.parent_path( ) );
961 14 : resolve_messages( result );
962 28 : return result;
963 0 : }
964 0 : catch( const std::exception & e )
965 : {
966 0 : throw std::runtime_error( file.string( ) + ":" + e.what( ) );
967 0 : }
968 : }
969 :
970 : }// namespace
971 :
972 14 : void parse_proto_file_content( proto_file & file )
973 : {
974 14 : set_default_options( file );
975 :
976 14 : auto stream = spb::char_stream( file.content );
977 :
978 420 : while( !stream.empty( ) )
979 : {
980 406 : auto comment = parse_comment( stream );
981 406 : parse_options_from_comments( stream, file.options, comment );
982 406 : parse_top_level( stream, file, std::move( comment ) );
983 406 : }
984 14 : }
985 :
986 12 : auto parse_proto_file( const fs::path & file, std::span< const fs::path > import_paths,
987 : const fs::path & base_dir ) -> proto_file
988 : {
989 12 : auto already_parsed = parsed_files( );
990 24 : return parse_proto_file( file, already_parsed, import_paths, base_dir );
991 12 : }
992 :
993 26 : [[nodiscard]] auto cpp_file_name_from_proto( const fs::path & proto_file_path,
994 : std::string_view extension ) -> fs::path
995 : {
996 26 : return proto_file_path.stem( ).concat( extension );
997 : }
|