LCOV - code coverage report
Current view: top level - spb-proto-compiler/parser - parser.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 88.3 % 477 421
Test Date: 2025-12-05 17:50:15 Functions: 96.5 % 57 55

            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              : }
        

Generated by: LCOV version 2.0-1