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 % 479 423
Test Date: 2025-05-23 14:18:14 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.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              : }
        

Generated by: LCOV version 2.0-1