diff --git a/base/components/constants.hpp b/base/components/constants.hpp index 0b4cd62..87110dd 100644 --- a/base/components/constants.hpp +++ b/base/components/constants.hpp @@ -6,6 +6,8 @@ #pragma region Constants // Predefined typename codes. Are set to readonly and are setup during gen::init() +GEN_API extern Token NullToken; + GEN_API extern Macro enum_underlying_macro; GEN_API extern Code access_public; diff --git a/base/components/interface.parsing.cpp b/base/components/interface.parsing.cpp index 51755f3..95baae1 100644 --- a/base/components/interface.parsing.cpp +++ b/base/components/interface.parsing.cpp @@ -12,12 +12,16 @@ ParseInfo wip_parse_str(LexedInfo lexed, ParseOpts* opts) { // TODO(Ed): Lift this. Context* ctx = _ctx; + ParseInfo info = struct_zero(ParseInfo); if (lexed.tokens.num == 0 && lexed.tokens.ptr == nullptr) { - check_parse_args(lexed.text); + ctx->parser = struct_zero(ParseContext); + if (check_parse_args(lexed.text) == false) { + info.messages = ctx->parser.messages; + goto done; + } lexed = lex(ctx, lexed.text); } - ParseInfo info = struct_zero(ParseInfo); info.lexed = lexed; // TODO(Ed): ParseInfo should be set to the parser context. @@ -25,12 +29,16 @@ ParseInfo wip_parse_str(LexedInfo lexed, ParseOpts* opts) ctx->parser = struct_zero(ParseContext); ctx->parser.tokens = lexed.tokens; + { ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); CodeBody result = parse_global_nspace(ctx,CT_Global_Body); + (void)result; parser_pop(& ctx->parser); + } +done: return info; } @@ -38,20 +46,30 @@ CodeClass parse_class( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeClass result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } + { ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeClass result = (CodeClass) parse_class_struct( ctx, Tok_Decl_Class, parser_not_inplace_def ); + result = (CodeClass) parse_class_struct( ctx, Tok_Decl_Class, parser_not_inplace_def ); parser_pop(& ctx->parser); + } +done: return result; } @@ -59,16 +77,24 @@ CodeConstructor parse_constructor(Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeConstructor result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } + { ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); @@ -100,7 +126,7 @@ CodeConstructor parse_constructor(Str def ) default : log_failure( "Invalid specifier %s for variable\n%S", spec_to_str( spec ), parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp) ); parser_pop(& ctx->parser); - return InvalidCode; + goto done; } // Every specifier after would be considered part of the type type signature @@ -117,8 +143,10 @@ CodeConstructor parse_constructor(Str def ) // ... } - CodeConstructor result = parser_parse_constructor(ctx, specifiers); + result = parser_parse_constructor(ctx, specifiers); parser_pop(& ctx->parser); + } +done: return result; } @@ -126,20 +154,30 @@ CodeDefine parse_define( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeDefine result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } + { ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeDefine result = parser_parse_define(ctx); + result = parser_parse_define(ctx); parser_pop(& ctx->parser); + } +done: return result; } @@ -147,20 +185,28 @@ CodeDestructor parse_destructor( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeDestructor result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } // TODO(Ed): Destructors can have prefix attributes // TODO(Ed): Destructors can have virtual - CodeDestructor result = parser_parse_destructor(ctx, NullCode); + result = parser_parse_destructor(ctx, NullCode); +done: return result; } @@ -168,106 +214,160 @@ CodeEnum parse_enum( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeEnum result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) { - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; } - return parser_parse_enum(ctx, parser_not_inplace_def); + result = parser_parse_enum(ctx, parser_not_inplace_def); +done: + return result; } CodeBody parse_export_body( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeBody result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_export_body(ctx); + result = parser_parse_export_body(ctx); +done: + return result; } CodeExtern parse_extern_link( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeExtern result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_extern_link(ctx); + result = parser_parse_extern_link(ctx); +done: + return result; } CodeFriend parse_friend( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeFriend result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_friend(ctx); + result = parser_parse_friend(ctx); +done: + return result; } CodeFn parse_function( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeFn result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return (CodeFn) parser_parse_function(ctx); + result = (CodeFn) parser_parse_function(ctx); +done: + return result; } CodeBody parse_global_body( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeBody result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } + { ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeBody result = parse_global_nspace(ctx, CT_Global_Body ); + result = parse_global_nspace(ctx, CT_Global_Body ); parser_pop(& ctx->parser); + } +done: return result; } @@ -275,71 +375,108 @@ CodeNS parse_namespace( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeNS result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_namespace(ctx); + result = parser_parse_namespace(ctx); +done: + return result; } CodeOperator parse_operator( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeOperator result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return (CodeOperator) parser_parse_operator(ctx); + result = (CodeOperator) parser_parse_operator(ctx); +done: + return result; } CodeOpCast parse_operator_cast( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeOpCast result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_operator_cast(ctx, NullCode); + result = parser_parse_operator_cast(ctx, NullCode); +done: + return result; } CodeStruct parse_struct( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeStruct result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } + { ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeStruct result = (CodeStruct) parse_class_struct( ctx, Tok_Decl_Struct, parser_not_inplace_def ); + result = (CodeStruct) parse_class_struct( ctx, Tok_Decl_Struct, parser_not_inplace_def ); parser_pop(& ctx->parser); + } +done: return result; } @@ -347,102 +484,160 @@ CodeTemplate parse_template( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeTemplate result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_template(ctx); + result = parser_parse_template(ctx); +done: + return result; } CodeTypename parse_type( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeTypename result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_type( ctx, parser_not_from_template, nullptr); + result = parser_parse_type( ctx, parser_not_from_template, nullptr); +done: + return result; } CodeTypedef parse_typedef( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeTypedef result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_typedef(ctx); + result = parser_parse_typedef(ctx); +done: + return result; } CodeUnion parse_union( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeUnion result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_union(ctx, parser_not_inplace_def); + result = parser_parse_union(ctx, parser_not_inplace_def); +done: + return result; } CodeUsing parse_using( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeUsing result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } - return parser_parse_using(ctx); + result = parser_parse_using(ctx); +done: + return result; } CodeVar parse_variable( Str def ) { // TODO(Ed): Lift this. Context* ctx = _ctx; - - check_parse_args( def ); + CodeVar result = InvalidCode; + LexedInfo lexed = struct_zero(LexedInfo); ctx->parser = struct_zero(ParseContext); + if (check_parse_args(def) == false) + goto done; - LexedInfo lexed = lex(ctx, def); + lexed = lex(ctx, def); ctx->parser.tokens = lexed.tokens; - if ( ctx->parser.tokens.ptr == nullptr ) - return InvalidCode; + if (ctx->parser.tokens.ptr == nullptr) { + if (lexed.messages != nullptr && lexed.messages->content.Ptr != nullptr) + parser_record_failure(ctx, lexed.messages->content); + else + parser_record_failure(ctx, txt("parse: lex produced no tokens")); + goto done; + } + if (parser_slice_is_formatting_only(&ctx->parser)) { + parser_record_failure(ctx, txt("parse_variable: only formatting tokens")); + goto done; + } - return parser_parse_variable(ctx); + result = parser_parse_variable(ctx); +done: + return result; } // Undef helper macros diff --git a/base/components/lexer.cpp b/base/components/lexer.cpp index 6c74241..3c400da 100644 --- a/base/components/lexer.cpp +++ b/base/components/lexer.cpp @@ -4,11 +4,9 @@ #include "gen/etoktype.hpp" #endif -StrBuilder tok_to_strbuilder(AllocatorInfo ainfo, Token tok) -{ +StrBuilder tok_to_strbuilder(AllocatorInfo ainfo, Token tok) { StrBuilder result = strbuilder_make_reserve( ainfo, kilobytes(4) ); Str type_str = toktype_to_str( tok.Type ); - strbuilder_append_fmt( & result, "Line: %d Column: %d, Type: %.*s Content: %.*s" , tok.Line, tok.Column , type_str.Len, type_str.Ptr @@ -19,57 +17,68 @@ StrBuilder tok_to_strbuilder(AllocatorInfo ainfo, Token tok) bool lex__eat(Context* ctx, ParseContext* self, TokType type ); -Token* lex_current(ParseContext* self, bool skip_formatting ) -{ - if ( skip_formatting ) - { - while ( self->tokens.ptr[self->token_id].Type == Tok_NewLine || self->tokens.ptr[self->token_id].Type == Tok_Comment ) - self->token_id++; - } - return & self->tokens.ptr[self->token_id]; +forceinline +b32 lex_index_in_range(ParseContext const* self, s32 idx) { + return self->tokens.ptr != nullptr + && idx >= 0 + && idx < self->tokens.num; } -Token* lex_peek(ParseContext const* self, bool skip_formatting) -{ - s32 idx = self->token_id; - if ( skip_formatting ) - { - while ( self->tokens.ptr[idx].Type == Tok_NewLine ) - idx++; +forceinline +Token* lex_token_at(ParseContext const* self, s32 idx) { + Token* result = & NullToken; + if (lex_index_in_range(self, idx)) result = & self->tokens.ptr[idx]; + return result; +} - return & self->tokens.ptr[idx]; +Token* lex_current(ParseContext* self, bool skip_formatting) { + if (skip_formatting) while (lex_index_in_range(self, self->token_id)) { + TokType type = self->tokens.ptr[self->token_id].Type; + if (type != Tok_NewLine && type != Tok_Comment) break; + ++ self->token_id; } - return & self->tokens.ptr[idx]; + return lex_token_at(self, self->token_id); +} + +Token* lex_peek(ParseContext const* self, bool skip_formatting) { + s32 idx = self->token_id; + if (skip_formatting) { + while (lex_index_in_range(self, idx) && self->tokens.ptr[idx].Type == Tok_NewLine) + ++ idx; + } + return lex_token_at(self, idx); } Token* lex_previous(ParseContext const* self, bool skip_formatting) { - s32 idx = self->token_id; - if ( skip_formatting ) + Token* result = & NullToken; + s32 idx = self->token_id; + if (skip_formatting) { - while ( self->tokens.ptr[idx].Type == Tok_NewLine ) - idx --; - - return & self->tokens.ptr[idx]; + if (self->tokens.num > 0 && idx >= self->tokens.num) + idx = self->tokens.num - 1; + while (lex_index_in_range(self, idx) && self->tokens.ptr[idx].Type == Tok_NewLine && idx > 0) + -- idx; + if (lex_index_in_range(self, idx) && self->tokens.ptr[idx].Type != Tok_NewLine) + result = &self->tokens.ptr[idx]; + goto done; } - return & self->tokens.ptr[idx - 1]; + if (self->tokens.num > 0 && idx >= self->tokens.num) result = lex_token_at(self, self->tokens.num - 1); + else result = lex_token_at(self, idx - 1); +done: + return result; } -Token* lex_next(ParseContext const* self, bool skip_formatting) -{ +Token* lex_next(ParseContext const* self, bool skip_formatting) { s32 idx = self->token_id; - if ( skip_formatting ) - { - while ( self->tokens.ptr[idx].Type == Tok_NewLine ) - idx++; - - return & self->tokens.ptr[idx + 1]; + if (skip_formatting) { + while (lex_index_in_range(self, idx) && self->tokens.ptr[idx].Type == Tok_NewLine) + -- idx; } - return & self->tokens.ptr[idx + 1]; + return lex_token_at(self, idx + 1); } -enum -{ +enum { Lex_Continue, Lex_ReturnNull, }; @@ -90,19 +99,16 @@ void lexer_move_forward( LexContext* ctx ) #define move_forward() lexer_move_forward(ctx) forceinline -void lexer_skip_whitespace( LexContext* ctx ) -{ +void lexer_skip_whitespace( LexContext* ctx ) { while ( ctx->left && char_is_space( * ctx->scanner ) ) move_forward(); } #define skip_whitespace() lexer_skip_whitespace(ctx) forceinline -void lexer_end_line( LexContext* ctx ) -{ +void lexer_end_line( LexContext* ctx ) { while ( ctx->left && (* ctx->scanner) == ' ' ) move_forward(); - if ( ctx->left && (* ctx->scanner) == '\r' ) { move_forward(); move_forward(); @@ -113,7 +119,9 @@ void lexer_end_line( LexContext* ctx ) #define end_line() lexer_end_line(ctx) // TODO(Ed): We need to to attempt to recover from a lex failure? -s32 lex_preprocessor_define( LexContext* ctx ) +internal void lexer_record_failure_fmt(Context* lib_ctx, LexContext* ctx, char const* fmt, ...); + +s32 lex_preprocessor_define(Context* lib_ctx, LexContext* ctx) { Token name = { { ctx->scanner, 1 }, Tok_Identifier, ctx->line, ctx->column, TF_Preprocess }; move_forward(); @@ -178,7 +186,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) } if (* ctx->scanner != ')' ) { - log_failure("lex_preprocessor_define(%d, %d): Expected a ')' after '...' (varaidc macro param) %S\n" + lexer_record_failure_fmt(lib_ctx, ctx, "lex_preprocessor_define(%d, %d): Expected a ')' after '...' (varaidc macro param) %S\n" , ctx->line , ctx->column , name.Text @@ -207,7 +215,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) last_parameter = parameter; } else { - log_failure("lex_preprocessor_define(%d, %d): Expected a '_' or alpha character for a parameter name for %S\n" + lexer_record_failure_fmt(lib_ctx, ctx, "lex_preprocessor_define(%d, %d): Expected a '_' or alpha character for a parameter name for %S\n" , ctx->line , ctx->column , name.Text @@ -220,7 +228,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) // There should be a comma if ( * ctx->scanner != ',' ) { - log_failure("lex_preprocessor_define(%d, %d): Expected a comma after parameter %S for %S\n" + lexer_record_failure_fmt(lib_ctx, ctx, "lex_preprocessor_define(%d, %d): Expected a comma after parameter %S for %S\n" , ctx->line , ctx->column , last_parameter.Text @@ -234,7 +242,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) } if ( * ctx->scanner != ')' ) { - log_failure("lex_preprocessor_define(%d, %d): Expected a ')' after last_parameter %S for %S (ran out of characters...)\n" + lexer_record_failure_fmt(lib_ctx, ctx, "lex_preprocessor_define(%d, %d): Expected a ')' after last_parameter %S for %S (ran out of characters...)\n" , ctx->line , ctx->column , last_parameter.Text @@ -264,7 +272,7 @@ s32 lex_preprocessor_define( LexContext* ctx ) } // TODO(Ed): We need to to attempt to recover from a lex failure? -s32 lex_preprocessor_directive( LexContext* ctx ) +s32 lex_preprocessor_directive(Context* lib_ctx, LexContext* ctx) { char const* hash = ctx->scanner; Token hash_tok = { { hash, 1 }, Tok_Preprocess_Hash, ctx->line, ctx->column, TF_Preprocess }; @@ -317,11 +325,11 @@ s32 lex_preprocessor_directive( LexContext* ctx ) } else { - log_failure( "gen::Parser::lex: Invalid escape sequence '\\%c' (%d, %d)" + lexer_record_failure_fmt(lib_ctx, ctx, "gen::Parser::lex: Invalid escape sequence '\\%c' (%d, %d)" " in preprocessor directive (%d, %d)\n%.100s" , (* ctx->scanner), ctx->line, ctx->column , ctx->token.Line, ctx->token.Column, ctx->token.Text ); - break; + return Lex_ReturnNull; } } @@ -366,7 +374,7 @@ s32 lex_preprocessor_directive( LexContext* ctx ) if ( ctx->token.Type == Tok_Preprocess_Define ) { - u32 result = lex_preprocessor_define(ctx); // handles: #define ( ) - define's content handled later on within this scope. + u32 result = lex_preprocessor_define(lib_ctx, ctx); // handles: #define ( ) - define's content handled later on within this scope. if (result != Lex_Continue) return Lex_ReturnNull; } @@ -381,7 +389,7 @@ s32 lex_preprocessor_directive( LexContext* ctx ) { StrBuilder directive_str = strbuilder_fmt_buf( ctx->allocator_temp, "%.*s", min( 80, ctx->left + preprocess_content.Text.Len ), ctx->token.Text.Ptr ); - log_failure( "gen::Parser::lex: Expected '\"' or '<' after #include, not '%c' (%d, %d)\n%s" + lexer_record_failure_fmt(lib_ctx, ctx, "gen::Parser::lex: Expected '\"' or '<' after #include, not '%c' (%d, %d)\n%s" , (* ctx->scanner) , preprocess_content.Line , preprocess_content.Column @@ -449,13 +457,12 @@ s32 lex_preprocessor_directive( LexContext* ctx ) StrBuilder directive_str = strbuilder_make_length( ctx->allocator_temp, ctx->token.Text.Ptr, ctx->token.Text.Len ); StrBuilder content_str = strbuilder_fmt_buf( ctx->allocator_temp, "%.*s", min( 400, ctx->left + preprocess_content.Text.Len ), preprocess_content.Text.Ptr ); - log_failure( "gen::Parser::lex: Invalid escape sequence '\\%c' (%d, %d)" + lexer_record_failure_fmt(lib_ctx, ctx, "gen::Parser::lex: Invalid escape sequence '\\%c' (%d, %d)" " in preprocessor directive '%s' (%d, %d)\n%s" , (* ctx->scanner), ctx->line, ctx->column , directive_str, preprocess_content.Line, preprocess_content.Column , content_str ); return Lex_ReturnNull; - break; } } @@ -568,6 +575,29 @@ void lex_found_token( LexContext* ctx ) // TODO(Ed): We need to to attempt to recover from a lex failure? +internal +void lexer_record_failure(Context* lib_ctx, LexContext* ctx, Str text) { + LexerMessage* msg = rcast(LexerMessage*, alloc(lib_ctx->Allocator_DyanmicContainers, size_of(LexerMessage))); + if (msg == nullptr) GEN_FATAL("lexer_record_failure: allocation failed"); + msg->next = ctx->messages; + msg->content = cache_str(text); + msg->level = LL_Error; + ctx->messages = msg; +} + +internal +void lexer_record_failure_fmt(Context* lib_ctx, LexContext* ctx, char const* fmt, ...) { + va_list va; + char* buf; + Str text; + va_start(va, fmt); + buf = c_str_fmt_buf_va(fmt, va); + va_end(va); + text.Ptr = buf; + text.Len = c_str_len(buf); + lexer_record_failure(lib_ctx, ctx, text); +} + neverinline LexedInfo lex(Context* lib_ctx, Str content) { @@ -586,14 +616,15 @@ LexedInfo lex(Context* lib_ctx, Str content) // 1. Ability to continue on error // 2. Return a lexed info. + b32 preprocess_args = true; + skip_whitespace(); if ( c.left <= 0 ) { - log_failure( "gen::lex: no tokens found (only whitespace provided)" ); - return info; + lexer_record_failure(lib_ctx, ctx, txt("gen::lex: no tokens found (only whitespace provided)")); + info.messages = c.messages; + goto Lex_Done; } - b32 preprocess_args = true; - while (c.left ) { c.token = struct_init(Token) { { c.scanner, 0 }, Tok_Invalid, c.line, c.column, TF_Null }; @@ -630,7 +661,7 @@ LexedInfo lex(Context* lib_ctx, Str content) { case '#': { - s32 result = lex_preprocessor_directive( ctx ); + s32 result = lex_preprocessor_directive(lib_ctx, ctx); switch ( result ) { case Lex_Continue: @@ -662,6 +693,7 @@ LexedInfo lex(Context* lib_ctx, Str content) case Lex_ReturnNull: { + info.messages = c.messages; return info; } } @@ -691,7 +723,9 @@ LexedInfo lex(Context* lib_ctx, Str content) { StrBuilder context_str = strbuilder_fmt_buf( lib_ctx->Allocator_Temp, "%s", c.scanner, min( 100, c.left ) ); - log_failure( "gen::lex: invalid varadic argument, expected '...' got '..%c' (%d, %d)\n%s", (* ctx->scanner), c.line, c.column, context_str ); + lexer_record_failure_fmt(lib_ctx, ctx, "gen::lex: invalid varadic argument, expected '...' got '..%c' (%d, %d)\n%s", (* ctx->scanner), c.line, c.column, context_str ); + info.messages = c.messages; + return info; } } @@ -1245,13 +1279,10 @@ LexedInfo lex(Context* lib_ctx, Str content) ); } - StrBuilder context_str = strbuilder_fmt_buf( _ctx->Allocator_Temp, "%.*s", min( 100, c.left ), c.scanner ); - log_failure( "Failed to lex token '%c' (%d, %d)\n%s", (* ctx->scanner), c.line, c.column, context_str ); - - // Skip to next whitespace since we can't know if anything else is valid until then. - while ( c.left && ! char_is_space( (* ctx->scanner) ) ) { - move_forward(); - } + StrBuilder context_str = strbuilder_fmt_buf( lib_ctx->Allocator_Temp, "%.*s", min( 100, c.left ), c.scanner ); + lexer_record_failure_fmt(lib_ctx, ctx, "Failed to lex token '%c' (%d, %d)\n%s", (* ctx->scanner), c.line, c.column, context_str ); + info.messages = c.messages; + return info; } FoundToken: @@ -1280,13 +1311,15 @@ LexedInfo lex(Context* lib_ctx, Str content) } if ( array_num(c.tokens) == 0 ) { - log_failure( "Failed to lex any tokens" ); - return info; + lexer_record_failure(lib_ctx, ctx, txt("gen::lex: failed to lex any tokens")); + info.messages = c.messages; + goto Lex_Done; } info.messages = c.messages; info.text = content; info.tokens = struct_init(TokenSlice) { pcast(Token*, c.tokens), scast(s32, array_num(c.tokens)) }; +Lex_Done: return info; } diff --git a/base/components/parser.cpp b/base/components/parser.cpp index 254353e..bcb88c4 100644 --- a/base/components/parser.cpp +++ b/base/components/parser.cpp @@ -31,70 +31,96 @@ void parser_pop(ParseContext* ctx) StrBuilder parser_to_strbuilder(ParseContext const* ctx, AllocatorInfo temp) { - StrBuilder result = strbuilder_make_reserve( temp, kilobytes(4) ); - - Token scope_start = * ctx->scope->start; - Token last_valid = (ctx->token_id >= ctx->tokens.num) ? ctx->tokens.ptr[ctx->tokens.num -1] : (* lex_peek(ctx, true)); - - sptr length = scope_start.Text.Len; - char const* current = scope_start.Text.Ptr + length; - while ( current <= ctx->tokens.ptr[ctx->tokens.num - 1].Text.Ptr && (* current) != '\n' && length < 74 ) - { - current++; - length++; + StrBuilder result; + b32 empty_slice = ctx->tokens.ptr == nullptr || ctx->tokens.num <= 0; + if (empty_slice == false) + empty_slice = ctx->scope->start == nullptr || ctx->scope->start->Text.Ptr == nullptr; + // empty_scope + if (empty_slice) { + result = strbuilder_make_reserve(temp, 64); + strbuilder_append_fmt(&result, "\tScope : \n"); + goto done; } - - Str scope_str = { scope_start.Text.Ptr, length }; - StrBuilder line = strbuilder_make_str( temp, scope_str ); - strbuilder_append_fmt( & result, "\tScope : %s\n", line ); - strbuilder_free(& line); - - sptr dist = (sptr)last_valid.Text.Ptr - (sptr)scope_start.Text.Ptr + 2; - sptr length_from_err = dist; - - Str err_str = { last_valid.Text.Ptr, length_from_err }; - StrBuilder line_from_err = strbuilder_make_str( temp, err_str ); - - if ( length_from_err < 100 ) - strbuilder_append_fmt(& result, "\t(%d, %d):%*c\n", last_valid.Line, last_valid.Column, length_from_err, '^' ); - else - strbuilder_append_fmt(& result, "\t(%d, %d)\n", last_valid.Line, last_valid.Column ); - - ParseStackNode* curr_scope = ctx->scope; - s32 level = 0; - do + // non_empty { - if ( curr_scope->name.Ptr ) { - strbuilder_append_fmt(& result, "\t%d: %S, AST Name: %S\n", level, curr_scope->proc_name, curr_scope->name ); - } - else { - strbuilder_append_fmt(& result, "\t%d: %S\n", level, curr_scope->proc_name ); + result = strbuilder_make_reserve( temp, kilobytes(4) ); + + Token scope_start = * ctx->scope->start; + Token last_valid = (ctx->token_id >= ctx->tokens.num) + ? ctx->tokens.ptr[ctx->tokens.num - 1] + : (*lex_peek(ctx, true)); + + sptr length = scope_start.Text.Len; + char const* current = scope_start.Text.Ptr + length; + while ( current <= ctx->tokens.ptr[ctx->tokens.num - 1].Text.Ptr && (* current) != '\n' && length < 74 ) + { + current++; + length++; } - curr_scope = curr_scope->prev; - level++; + Str scope_str = { scope_start.Text.Ptr, length }; + StrBuilder line = strbuilder_make_str( temp, scope_str ); + strbuilder_append_fmt( & result, "\tScope : %s\n", line ); + strbuilder_free(& line); + + sptr dist = (sptr)last_valid.Text.Ptr - (sptr)scope_start.Text.Ptr + 2; + sptr length_from_err = dist; + + Str err_str = { last_valid.Text.Ptr, length_from_err }; + StrBuilder line_from_err = strbuilder_make_str( temp, err_str ); + + if ( length_from_err < 100 ) + strbuilder_append_fmt(& result, "\t(%d, %d):%*c\n", last_valid.Line, last_valid.Column, length_from_err, '^' ); + else + strbuilder_append_fmt(& result, "\t(%d, %d)\n", last_valid.Line, last_valid.Column ); + + ParseStackNode* curr_scope = ctx->scope; + s32 level = 0; + do + { + if ( curr_scope->name.Ptr ) { + strbuilder_append_fmt(& result, "\t%d: %S, AST Name: %S\n", level, curr_scope->proc_name, curr_scope->name ); + } + else { + strbuilder_append_fmt(& result, "\t%d: %S\n", level, curr_scope->proc_name ); + } + + curr_scope = curr_scope->prev; + level++; + } + while ( curr_scope ); } - while ( curr_scope ); +done: return result; } +void parser_record_failure(Context* ctx, Str text) { + ParseMessage* msg = rcast(ParseMessage*, alloc(ctx->Allocator_DyanmicContainers, size_of(ParseMessage))); + if (msg == nullptr) GEN_FATAL("parser_record_failure: allocation failed"); + msg->Next = ctx->parser.messages; + msg->Scope = nullptr; + msg->Content = cache_str(text); + msg->Level = LL_Error; + ctx->parser.messages = msg; +} + bool lex__eat(Context* ctx, ParseContext* parser, TokType type) { - if ( parser->tokens.num - parser->token_id <= 0 ) { - log_failure( "No tokens left.\n%SB", parser_to_strbuilder(parser, ctx->Allocator_Temp) ); - return false; - } - - Token at_idx = parser->tokens.ptr[ parser->token_id ]; + b32 ate = false; + Token at_idx; + b32 not_accepted; + b32 is_identifier; + if (parser->tokens.num - parser->token_id <= 0) + goto done; + at_idx = parser->tokens.ptr[ parser->token_id ]; if ( ( at_idx.Type == Tok_NewLine && type != Tok_NewLine ) - || ( at_idx.Type == Tok_Comment && type != Tok_Comment ) ) - { + || ( at_idx.Type == Tok_Comment && type != Tok_Comment ) ) { parser->token_id ++; } - b32 not_accepted = at_idx.Type != type; - b32 is_identifier = at_idx.Type == Tok_Identifier; + not_accepted = at_idx.Type != type; + is_identifier = at_idx.Type == Tok_Identifier; if ( not_accepted ) { Macro* macro = lookup_macro(at_idx.Text); @@ -112,7 +138,7 @@ bool lex__eat(Context* ctx, ParseContext* parser, TokType type) , parser_to_strbuilder(parser, ctx->Allocator_Temp) ); GEN_DEBUG_TRAP(); - return false; + goto done; } #if 0 && GEN_BUILD_DEBUG @@ -120,7 +146,9 @@ bool lex__eat(Context* ctx, ParseContext* parser, TokType type) #endif parser->token_id ++; - return true; + ate = true; +done: + return ate; } internal @@ -135,22 +163,40 @@ void parser_deinit(Context* ctx) #pragma region Helper Macros -#define check_parse_args( def ) _check_parse_args(& ctx->parser, def, stringize(_func_) ) -bool _check_parse_args(ParseContext* parser, Str def, char const* func_name ) +b32 parser_slice_is_formatting_only(ParseContext const* parser) { - if ( def.Len <= 0 ) + b32 formatting_only = true; + if (parser->tokens.ptr == nullptr || parser->tokens.num <= 0) + goto done; + for (s32 idx = 0; idx < parser->tokens.num; ++idx) { - log_failure( c_str_fmt_buf("gen::%s: length must greater than 0", func_name) ); - parser_pop(parser); - return false; + TokType type = parser->tokens.ptr[idx].Type; + if (type != Tok_NewLine && type != Tok_Comment) { + formatting_only = false; + goto done; + } } - if ( def.Ptr == nullptr ) - { - log_failure( c_str_fmt_buf("gen::%s: def was null", func_name) ); - parser_pop(parser); - return false; +done: + return formatting_only; +} + +#define check_parse_args( def ) _check_parse_args(ctx, def, __func__) +bool _check_parse_args(Context* ctx, Str def, char const* func_name ) +{ + b32 ok = true; + char const* text; + if ( def.Len <= 0 ) { + text = c_str_fmt_buf("gen::%s: length must greater than 0", func_name); + parser_record_failure(ctx, to_str_from_c_str(text)); + ok = false; goto done; } - return true; + if ( def.Ptr == nullptr ) { + text = c_str_fmt_buf("gen::%s: def was null", func_name); + parser_record_failure(ctx, to_str_from_c_str(text)); + ok = false; goto done; + } +done: + return ok; } # define currtok_noskip (* lex_current( & ctx->parser, lex_dont_skip_formatting )) @@ -2455,9 +2501,12 @@ CodeOperator parse_operator_after_ret_type(Context* ctx was_new_or_delete = true; s32 idx = ctx->parser.token_id + 1; - { - while ( ctx->parser.tokens.ptr[ idx ].Type == Tok_NewLine ) - idx++; + while (idx < ctx->parser.tokens.num && ctx->parser.tokens.ptr[idx].Type == Tok_NewLine) + idx++; + if (idx >= ctx->parser.tokens.num) { + parser_record_failure(ctx, txt("parse: operator new/delete ended at slice end")); + parser_pop(&ctx->parser); + return InvalidCode; } Token next = ctx->parser.tokens.ptr[idx]; if ( currtok.Type == Tok_Operator && c_str_compare_len(currtok.Text.Ptr, "[]", 2) == 0) @@ -2479,9 +2528,12 @@ CodeOperator parse_operator_after_ret_type(Context* ctx was_new_or_delete = true; s32 idx = ctx->parser.token_id + 1; - { - while ( ctx->parser.tokens.ptr[ idx ].Type == Tok_NewLine ) - idx++; + while (idx < ctx->parser.tokens.num && ctx->parser.tokens.ptr[idx].Type == Tok_NewLine) + idx++; + if (idx >= ctx->parser.tokens.num) { + parser_record_failure(ctx, txt("parse: operator new/delete ended at slice end")); + parser_pop(&ctx->parser); + return InvalidCode; } Token next = ctx->parser.tokens.ptr[idx]; if ( currtok.Type == Tok_Operator && c_str_compare_len(currtok.Text.Ptr, "[]", 2) == 0) @@ -2673,7 +2725,9 @@ Code parse_operator_function_or_variable(Context* ctx, bool expects_function, Co // ( 350.0f , <--- Could be the scenario // Example : // idx +1 +2 - bool detected_comma = ctx->parser.tokens.ptr[ ctx->parser.token_id + 2 ].Type == Tok_Comma; + b32 detected_comma = false; + if (ctx->parser.token_id + 2 < ctx->parser.tokens.num) + detected_comma = ctx->parser.tokens.ptr[ctx->parser.token_id + 2].Type == Tok_Comma; b32 detected_non_varadic_unpaired_param = detected_comma && nexttok.Type != Tok_Varadic_Argument; if (! detected_non_varadic_unpaired_param && nexttok.Type == Tok_Preprocess_Macro_Expr) for( s32 break_scope = 0; break_scope == 0; ++ break_scope) @@ -2701,7 +2755,7 @@ Code parse_operator_function_or_variable(Context* ctx, bool expects_function, Co } ++ idx; // Will incremnt to possible comma position - if ( ctx->parser.tokens.ptr[ idx ].Type != Tok_Comma ) + if (idx >= ctx->parser.tokens.num || ctx->parser.tokens.ptr[idx].Type != Tok_Comma) break; detected_non_varadic_unpaired_param = true; diff --git a/base/components/parser_types.hpp b/base/components/parser_types.hpp index 492310c..6f50264 100644 --- a/base/components/parser_types.hpp +++ b/base/components/parser_types.hpp @@ -37,8 +37,6 @@ struct Token u32 Flags; }; -constexpr Token NullToken { {}, Tok_Invalid, 0, 0, TF_Null }; - forceinline AccessSpec tok_to_access_specifier(Token tok) { return scast(AccessSpec, tok.Type); diff --git a/base/components/static_data.cpp b/base/components/static_data.cpp index 81c21a6..3da313e 100644 --- a/base/components/static_data.cpp +++ b/base/components/static_data.cpp @@ -8,6 +8,8 @@ GEN_API global Context* _ctx; GEN_API global u32 context_counter; #pragma region Constants +GEN_API read_only global Token NullToken = { { 0, 0 }, Tok_Invalid, 0, 0, TF_Null }; + GEN_API global Macro enum_underlying_macro; GEN_API global Code Code_Global; diff --git a/base/dependencies/macros.hpp b/base/dependencies/macros.hpp index bbba7c9..53c0ca4 100644 --- a/base/dependencies/macros.hpp +++ b/base/dependencies/macros.hpp @@ -38,6 +38,16 @@ #ifndef local_persist #define local_persist static // Local Persisting variables #endif +#ifndef read_only +# if GEN_COMPILER_MSVC || ( defined(GEN_COMPILER_CLANG) && GEN_COMPILER_CLANG && GEN_SYSTEM_WINDOWS ) +# pragma section(".rdata$", read) +# define read_only __declspec(allocate(".rdata$")) +# elif defined(GEN_COMPILER_CLANG) && GEN_COMPILER_CLANG +# define read_only __attribute__((section(".rodata"))) +# else +# define read_only +# endif +#endif #ifndef bit #define bit( Value ) ( 1 << Value ) @@ -98,9 +108,9 @@ #define src_line_str stringize(__LINE__) #ifndef do_once -#define do_once() \ - local_persist int __do_once_counter_##src_line_str = 0; \ - for(; __do_once_counter_##src_line_str != 1; __do_once_counter_##src_line_str = 1 ) \ +#define do_once() \ + local_persist int __do_once_counter_##src_line_str = 0; \ + for(; __do_once_counter_##src_line_str != 1; __do_once_counter_##src_line_str = 1 ) \ #define do_once_defer( expression ) \ local_persist int __do_once_counter_##src_line_str = 0; \ diff --git a/gen_c_library/c_library.cpp b/gen_c_library/c_library.cpp index 0d0b4e5..34aa724 100644 --- a/gen_c_library/c_library.cpp +++ b/gen_c_library/c_library.cpp @@ -767,20 +767,7 @@ do \ break; case CT_Variable: - { - CodeVar var = cast(CodeVar, entry); - if (var->Specs && var->Specs.has(Spec_Constexpr)) { - Code define_ver = untyped_str(token_fmt( - "name", var->Name - , "value", var->Value->Content - , "type", var->ValueType.to_strbuilder().to_str() - , "#define () \n" - )); - parser_types.append(define_ver); - continue; - } parser_types.append(entry); - } break; default: diff --git a/gen_c_library/c_library.refactor b/gen_c_library/c_library.refactor index 4c3da39..102e284 100644 --- a/gen_c_library/c_library.refactor +++ b/gen_c_library/c_library.refactor @@ -20,6 +20,7 @@ word global, gen_global word internal, gen_internal word local_persist, gen_local_persist +word read_only, gen_read_only word bit, gen_bit word bitfield_is_set, gen_bitfield_is_set word cast, gen_cast diff --git a/scripts/Readme.md b/scripts/Readme.md index 00a79dd..e62cd33 100644 --- a/scripts/Readme.md +++ b/scripts/Readme.md @@ -25,6 +25,9 @@ args: segmented singleheader unreal + test + parser_bounds + lexer_failures clang msvc : By default this project builds with clang, specifying msvc will build with MSVC. debug diff --git a/scripts/build.ci.ps1 b/scripts/build.ci.ps1 index 3198f9e..e95f0d0 100644 --- a/scripts/build.ci.ps1 +++ b/scripts/build.ci.ps1 @@ -74,8 +74,10 @@ Push-Location $path_root [bool] $c_lib = $false [bool] $c_lib_static = $false [bool] $c_lib_dyn = $false -[bool] $unreal = $false -[bool] $test = $false +[bool] $unreal = $false +[bool] $test = $false +[bool] $parser_bounds = $false +[bool] $lexer_failures = $false [array] $vendors = @( "clang", "msvc" ) @@ -93,8 +95,10 @@ if ( $args ) { $args | ForEach-Object { "c_lib" { $c_lib = $true } "c_lib_static" { $c_lib_static = $true } "c_lib_dyn" { $c_lib_dyn = $true } - "unreal" { $unreal = $true } - "test" { $test = $true } + "unreal" { $unreal = $true } + "test" { $test = $true } + "parser_bounds" { $parser_bounds = $true } + "lexer_failures" { $lexer_failures = $true } } }} #endregion Arguments @@ -114,6 +118,7 @@ if ( $vendor -eq $null ) { if ( $release -eq $null ) { write-host "No build type specified, assuming debug" $release = $false + $debug = $true } elseif ( $release -eq $false ) { $debug = $true @@ -129,7 +134,9 @@ $cannot_build = $cannot_build -and $c_lib -eq $false $cannot_build = $cannot_build -and $c_lib_static -eq $false $cannot_build = $cannot_build -and $c_lib_dyn -eq $false $cannot_build = $cannot_build -and $unreal -eq $false -$cannot_build = $cannot_build -and $test -eq $false +$cannot_build = $cannot_build -and $test -eq $false +$cannot_build = $cannot_build -and $parser_bounds -eq $false +$cannot_build = $cannot_build -and $lexer_failures -eq $false if ( $cannot_build ) { Stop-stage "No build target specified" } @@ -362,7 +369,6 @@ if ( $test -and $true ) $compiler_args += $flag_all_c $compiler_args += $flag_updated_cpp_macro $compiler_args += $flag_c11 - $compiler_args += "-H" $linker_args = @( $flag_link_win_subsystem_console @@ -421,10 +427,9 @@ if ($test) $compiler_args = @() $compiler_args += ( $flag_define + 'GEN_TIME' ) - $compiler_args += @("-x", "c++") - $compiler_args += "-std=c++17" + $compiler_args += $flag_all_cpp + $compiler_args += $flag_cpp17 $compiler_args += ($flag_define + "GEN_BUILD_DEBUG=1") - $compiler_args += "-H" $linker_args = @( $flag_link_win_subsystem_console @@ -442,6 +447,54 @@ if ($test) { } } } + +if ($parser_bounds) +{ + $path_probe = join-path $path_test parser_bounds + $path_build = join-path $path_probe build + if ( -not(Test-Path($path_build) )) { + new-item -ItemType Directory -Path $path_build | Out-Null + } + + $includes = @( $path_base ) + $unit = join-path $path_probe "test.cpp" + $executable = join-path $path_build "parser_bounds.exe" + + $compiler_args = @() + $compiler_args += ( $flag_define + 'GEN_TIME' ) + $compiler_args += $flag_cpp17 + + $linker_args = @( + $flag_link_win_subsystem_console + ) + + $result = build-simple $path_build $includes $compiler_args $linker_args $unit $executable + invoke-stageTool -stage "parser_bounds" -compiled $result -executable $executable -path_work $path_probe +} + +if ($lexer_failures) +{ + $path_probe = join-path $path_test lexer_failures + $path_build = join-path $path_probe build + if ( -not(Test-Path($path_build) )) { + new-item -ItemType Directory -Path $path_build | Out-Null + } + + $includes = @( $path_base ) + $unit = join-path $path_probe "test.cpp" + $executable = join-path $path_build "lexer_failures.exe" + + $compiler_args = @() + $compiler_args += ( $flag_define + 'GEN_TIME' ) + $compiler_args += $flag_cpp17 + + $linker_args = @( + $flag_link_win_subsystem_console + ) + + $result = build-simple $path_build $includes $compiler_args $linker_args $unit $executable + invoke-stageTool -stage "lexer_failures" -compiled $result -executable $executable -path_work $path_probe +} #endregion Building pop-location # $path_root diff --git a/scripts/clean.ps1 b/scripts/clean.ps1 index 9e3dee5..69515a3 100644 --- a/scripts/clean.ps1 +++ b/scripts/clean.ps1 @@ -19,6 +19,8 @@ $path_unreal_gen = Join-Path $path_unreal gen $path_test = Join-Path $path_root test $path_test_build = Join-Path $path_test build $path_test_gen = Join-Path $path_test gen +$path_parser_bounds_build = Join-Path $path_test "parser_bounds\build" +$path_lexer_failures_build = Join-Path $path_test "lexer_failures\build" $path_x64 = Join-Path $path_root x64 $path_release = Join-Path $path_root release @@ -58,6 +60,12 @@ if ( Test-Path $path_test_build ) { if ( Test-Path $path_test_gen ) { Remove-Item $path_test_gen -Recurse -Verbose } +if ( Test-Path $path_parser_bounds_build ) { + Remove-Item $path_parser_bounds_build -Recurse -Verbose +} +if ( Test-Path $path_lexer_failures_build ) { + Remove-Item $path_lexer_failures_build -Recurse -Verbose +} if ( Test-Path $path_x64) { Remove-Item $path_x64 -Recurse -Verbose } diff --git a/scripts/helpers/vendor_toolchain.ps1 b/scripts/helpers/vendor_toolchain.ps1 index 7df5259..7bd2b08 100644 --- a/scripts/helpers/vendor_toolchain.ps1 +++ b/scripts/helpers/vendor_toolchain.ps1 @@ -149,7 +149,8 @@ if ( $vendor -match "clang" ) # https://clang.llvm.org/docs/ClangCommandLineReference.html $flag_all_c = @('-x', 'c') $flag_c11 = '-std=c11' - $flag_all_cpp = '-x c++' + $flag_cpp17 = '-std=c++17' + $flag_all_cpp = @('-x', 'c++') $flag_charset_utf8 = '-utf-8' $flag_compile = '-c' $flag_color_diagnostics = '-fcolor-diagnostics' @@ -389,6 +390,7 @@ if ( $vendor -match "msvc" ) # https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170 $flag_all_c = '/TC' $flag_c11 = '/std:c11' + $flag_cpp17 = '/std:c++17' $flag_all_cpp = '/TP' $flag_charset_utf8 = '/utf-8' $flag_compile = '/c' diff --git a/test/Readme.md b/test/Readme.md index d2002cb..ca7ea20 100644 --- a/test/Readme.md +++ b/test/Readme.md @@ -13,3 +13,27 @@ The first command generates base component tables, segmented C++, single-header It compiles and runs the C++17 and C11 single-header host smokes. The second command reuses the same mirror. The verification wrapper copies them into the disposable mirror; it does not embed C or C++ source text. + +## Parser bounds probe + +`test/parser_bounds/test.cpp` is a GEN_TIME program. It includes `base/gen.cpp`. It is not part of `verify_baseline.ps1`. + +From the project root: + +```powershell +.\scripts\build.ps1 clang parser_bounds +``` + +The script builds and runs the probe. A pass prints `parser bounds passed` and `PASSED: parser_bounds`. +The probe covers helper positions, formatting skip at the slice end, empty and whitespace `parse_variable`, and two direct end-of-slice reads. +It does not prove in-slice syntax errors return. + +## Lexer failure probe + +```powershell +.\scripts\build.ps1 clang lexer_failures +``` + +A pass prints `lexer failures passed` and `PASSED: lexer_failures`. +The probe covers nine lexer failures, six successful sibling inputs, and a baseline variable after each failure. +It does not prove in-slice parser errors return. diff --git a/test/lexer_failures/test.cpp b/test/lexer_failures/test.cpp new file mode 100644 index 0000000..bbf3d12 --- /dev/null +++ b/test/lexer_failures/test.cpp @@ -0,0 +1,81 @@ +#define GEN_TIME +#define GEN_DEFINE_LIBRARY_CODE_CONSTANTS +#define GEN_ENFORCE_STRONG_CODE_TYPES +#include "gen.cpp" + +using namespace gen; + +global int g_failures = 0; + +internal void expect(int condition, Str label) { + if (condition == false) { log_fmt("FAIL %S\n", label); ++g_failures; } +} + +internal void expect_public(Str def, Str needle, Str label) +{ + Context ctx = {}; init(&ctx); + CodeVar parsed = parse_variable(def); expect(cast(Code, parsed) == Code_Invalid, label); + expect(_ctx->parser.messages != nullptr, label); + expect(_ctx->parser.messages->Scope == nullptr, label); + expect(_ctx->parser.messages->Content.Ptr != nullptr, label); + expect(str_contains(_ctx->parser.messages->Content, needle), label); + + CodeVar again = parse_variable(txt("char const* baseline_message = \"GENCPP_BASELINE\";")); expect(again.ast != nullptr, label); + if (again.ast != nullptr) { + expect(again->Type == CT_Variable, label); + expect(str_contains(again->Name, txt("baseline_message")), label); + } + deinit(&ctx); +} + +internal void expect_lex_fail(Str def, Str needle, Str label) { + Context ctx = {}; init(&ctx); + LexedInfo lexed = lex(&ctx, def); + expect(lexed.tokens.ptr == nullptr, label); + expect(lexed.messages != nullptr, label); + if (lexed.messages != nullptr) expect(str_contains(lexed.messages->content, needle), label); + deinit(&ctx); +} + +internal void expect_lex_ok(Str def, Str label) { + Context ctx = {}; init(&ctx); + LexedInfo lexed = lex(&ctx, def); + expect(lexed.tokens.ptr != nullptr, label); + expect(lexed.tokens.num > 0, label); + expect(lexed.messages == nullptr, label); + deinit(&ctx); +} + +int main() +{ + expect_public(txt("#define FOO(... x)"), txt("Expected a ')' after '...'"), txt("L01-public")); + expect_public(txt("#define FOO(1)"), txt("Expected a '_' or alpha character"), txt("L02-public")); + expect_public(txt("#define FOO(a b)"), txt("Expected a comma after parameter"), txt("L03-public")); + expect_public(txt("#define FOO(a,"), txt("Expected a ')' after last_parameter"), txt("L04-public")); + expect_public(txt("#foo \\q"), txt("in preprocessor directive ("), txt("L05-public")); + expect_public(txt("#include foo"), txt("after #include"), txt("L06-public")); + expect_public(txt("#pragma \\q"), txt("in preprocessor directive '"), txt("L07-public")); + expect_public(txt("..x"), txt("invalid varadic argument"), txt("L08-public")); + expect_public(txt("@"), txt("Failed to lex token"), txt("L09-public")); + + expect_lex_fail(txt("#define FOO(... x)"), txt("Expected a ')' after '...'"), txt("L01-lex")); + expect_lex_fail(txt("#define FOO(1)"), txt("Expected a '_' or alpha character"), txt("L02-lex")); + expect_lex_fail(txt("#define FOO(a b)"), txt("Expected a comma after parameter"), txt("L03-lex")); + expect_lex_fail(txt("#define FOO(a,"), txt("Expected a ')' after last_parameter"), txt("L04-lex")); + expect_lex_fail(txt("#foo \\q"), txt("in preprocessor directive ("), txt("L05-lex")); + expect_lex_fail(txt("#include foo"), txt("after #include"), txt("L06-lex")); + expect_lex_fail(txt("#pragma \\q"), txt("in preprocessor directive '"), txt("L07-lex")); + expect_lex_fail(txt("..x"), txt("invalid varadic argument"), txt("L08-lex")); + expect_lex_fail(txt("@"), txt("Failed to lex token"), txt("L09-lex")); + + expect_lex_ok(txt("#define FOO(a, b) a"), txt("S01")); + expect_lex_ok(txt("#define FOO(...)"), txt("S02")); + expect_lex_ok(txt("#include \"a.h\""), txt("S03")); + expect_lex_ok(txt("#pragma once"), txt("S04")); + expect_lex_ok(txt("#foo bar"), txt("S05")); + expect_lex_ok(txt("..."), txt("S06")); + + if (g_failures != 0) { log_fmt("lexer failures failed: %d\n", g_failures); return 1; } + log_fmt("lexer failures passed\n"); + return 0; +} diff --git a/test/parser_bounds/test.cpp b/test/parser_bounds/test.cpp new file mode 100644 index 0000000..84bb971 --- /dev/null +++ b/test/parser_bounds/test.cpp @@ -0,0 +1,238 @@ +#define GEN_TIME +#define GEN_DEFINE_LIBRARY_CODE_CONSTANTS +#define GEN_ENFORCE_STRONG_CODE_TYPES +#include "gen.cpp" + +using namespace gen; + +global s32 g_failures = 0; + +internal inline void expect(int condition, Str label) { + if (condition == false) { log_fmt("FAIL %S\n", label); ++ g_failures; } +} + +internal inline Token canary_at(Str text) { + Token tok = NullToken; + tok.Type = Tok_Comma; + tok.Text = text; + tok.Line = 99; + tok.Column = 99; + return tok; +} + +internal inline Token ident(Str text) { + Token tok = NullToken; + tok.Type = Tok_Identifier; + tok.Text = text; + tok.Line = 1; + tok.Column = 1; + return tok; +} + +internal b32 canaries_intact(Token* storage, s32 logical) { + Token expected = canary_at(txt("CANARY")); + for (s32 id = 0; id < 3; ++id) { + Token got = storage[logical + id]; + if (got.Type != expected.Type || got.Text.Ptr != expected.Text.Ptr || got.Line != 99) + return false; + } + return true; +} + +internal inline b32 points_at_canary(Token* got, Token* storage, s32 logical) { + return got == & storage[logical] || got == & storage[logical + 1] || got == & storage[logical + 2]; +} + +internal void plant(Token* storage, s32 logical, Token* src, s32 src_num) { + for (s32 id = 0; id < src_num; ++id) storage[id] = src[id]; + for (s32 id = 0; id < 3; ++id) storage[logical + id] = canary_at(txt("CANARY")); +} + +internal void bind(ParseContext* parser, Token* storage, s32 logical, s32 token_id) { + parser->tokens.ptr = storage; + parser->tokens.num = logical; + parser->token_id = token_id; + parser->scope = nullptr; + parser->messages = nullptr; +} + +internal void expect_helper(ParseContext* parser, Token* storage, s32 logical, Str label) { + s32 before = parser->token_id; + Token* cur = lex_current (parser, lex_dont_skip_formatting); + Token* peek = lex_peek (parser, lex_dont_skip_formatting); + Token* next = lex_next (parser, lex_dont_skip_formatting); + Token* prev = lex_previous(parser, lex_dont_skip_formatting); + expect(parser->token_id == before, label); + expect(canaries_intact(storage, logical), label); + expect(points_at_canary(cur, storage, logical) == false, label); + expect(points_at_canary(peek, storage, logical) == false, label); + expect(points_at_canary(next, storage, logical) == false, label); + expect(points_at_canary(prev, storage, logical) == false, label); + expect(cur != nullptr && peek != nullptr && next != nullptr && prev != nullptr, label); +} + +internal void expect_row_empty(Str label) { + Token storage[3] = {}; plant(storage, 0, nullptr, 0); + ParseContext parser = {}; bind(& parser, storage, 0, 0); + expect_helper(& parser, storage, 0, label); + expect(lex_current (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_peek (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_next (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_previous(& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); +} + +internal void expect_row_single(Str label) { + Token src [1] = { ident(txt("only")) }; + Token storage[4] = {}; plant(storage, 1, src, 1); + ParseContext parser = {}; bind(& parser, storage, 1, 0); + expect_helper(& parser, storage, 1, label); + expect(lex_current (& parser, lex_dont_skip_formatting)->Text.Ptr == src[0].Text.Ptr, label); + expect(lex_peek (& parser, lex_dont_skip_formatting)->Text.Ptr == src[0].Text.Ptr, label); + expect(lex_next (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_previous(& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); +} + +internal void expect_row_ends(Str label) { + Token src [2] = { ident(txt("first")), ident(txt("last")) }; + Token storage[5] = {}; plant(storage, 2, src, 2); + + ParseContext parser = {}; + bind(& parser, storage, 2, 0); + expect_helper(& parser, storage, 2, label); + expect(lex_current (& parser, lex_dont_skip_formatting)->Text.Ptr == src[0].Text.Ptr, label); + expect(lex_next (& parser, lex_dont_skip_formatting)->Text.Ptr == src[1].Text.Ptr, label); + expect(lex_previous(& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + + bind(& parser, storage, 2, 1); + expect_helper(& parser, storage, 2, label); + expect(lex_current (& parser, lex_dont_skip_formatting)->Text.Ptr == src[1].Text.Ptr, label); + expect(lex_next (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_previous(& parser, lex_dont_skip_formatting)->Text.Ptr == src[0].Text.Ptr, label); + + bind(& parser, storage, 2, 2); + expect_helper(& parser, storage, 2, label); + expect(lex_current (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_peek (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_next (& parser, lex_dont_skip_formatting)->Type == Tok_Invalid, label); + expect(lex_previous(& parser, lex_dont_skip_formatting)->Text.Ptr == src[1].Text.Ptr, label); +} + +internal void expect_skip_end(Str label) +{ + Token newlines[2] = { NullToken, NullToken }; + newlines[0].Type = Tok_NewLine; newlines[0].Text = txt("\n"); + newlines[1].Type = Tok_NewLine; newlines[1].Text = txt("\n"); + Token storage[5] = {}; plant(storage, 2, newlines, 2); + + ParseContext parser = {}; bind(& parser, storage, 2, 0); + Token* got = lex_current(&parser, lex_skip_formatting); + expect(got->Type == Tok_Invalid, label); + expect(parser.token_id == 2, label); + expect(canaries_intact(storage, 2), label); + expect(points_at_canary(got, storage, 2) == false, label); + + bind(& parser, storage, 2, 0); got = lex_peek(&parser, lex_skip_formatting); + expect(parser.token_id == 0, label); + expect(got->Type == Tok_Invalid, label); + expect(canaries_intact(storage, 2), label); + + Token comments[1] = { NullToken }; + comments[0].Type = Tok_Comment; comments[0].Text = txt("// note"); + plant(storage, 1, comments, 1); bind(& parser, storage, 1, 0); + got = lex_current(&parser, lex_skip_formatting); + expect(got->Type == Tok_Invalid, label); + expect(parser.token_id == 1, label); + expect(canaries_intact(storage, 1), label); + expect(points_at_canary(got, storage, 1) == false, label); +} + +internal void expect_boundary(Str def, Str label, Str mark) { + Context ctx = {}; + init(&ctx); + CodeVar parsed = parse_variable(def); + expect(cast(Code, parsed) == Code_Invalid, label); + expect(_ctx->parser.messages != nullptr, label); + expect(_ctx->parser.messages->Scope == nullptr, label); + expect(_ctx->parser.messages->Content.Ptr != nullptr && _ctx->parser.messages->Content.Len > 0, label); + expect(str_contains(_ctx->parser.messages->Content, mark), label); + CodeVar again = parse_variable(txt("char const* baseline_message = \"GENCPP_BASELINE\";")); + expect(again.ast != nullptr, label); + if (again.ast != nullptr) { + expect(again->Type == CT_Variable, label); + expect(str_contains(again->Name, txt("baseline_message")), label); + } + deinit(&ctx); +} + +internal void expect_eat_end(Str label) { + Context ctx = {}; init(&ctx); + Token src [1] = { ident(txt("x")) }; + Token storage[4] = {}; + plant(storage, 1, src, 1); bind(& ctx.parser, storage, 1, 1); + b32 ate = lex__eat(&ctx, & ctx.parser, Tok_Identifier); + expect(ate == false, label); + expect(ctx.parser.token_id <= ctx.parser.tokens.num, label); + expect(canaries_intact(storage, 1), label); + deinit(& ctx); +} + +internal void expect_direct(Str label) { + Context ctx = {}; init(& ctx); + Token src [2] = { ident(txt("int")), ident(txt("name")) }; + Token storage[5] = {}; plant(storage, 2, src, 2); + bind(& ctx.parser, storage, 2, 0); + Code got = parse_operator_function_or_variable(& ctx, false, NullCode, NullCode); + expect(canaries_intact(storage, 2), label); + (void)got; + + Token op[2] = {}; + op[0] = NullToken; + op[0].Type = Tok_Decl_Operator; + op[0].Text = txt("operator"); + op[0].Line = 1; + op[1] = ident(operator_to_str(Op_Delete)); + plant(storage, 2, op, 2); + ctx.parser = {}; bind(& ctx.parser, storage, 2, 0); + CodeOperator op_got = parse_operator_after_ret_type(& ctx, ModuleFlag_None, NullCode, NullCode, NullCode); + expect(cast(Code, op_got) == Code_Invalid, label); + expect(canaries_intact(storage, 2), label); + deinit(& ctx); +} + +internal void expect_builder(Str label) { + Token storage[3] = {}; plant(storage, 0, nullptr, 0); + ParseContext parser = {}; bind(& parser, storage, 0, 0); + StrBuilder built = parser_to_strbuilder(& parser, _ctx->Allocator_Temp); + Str text = strbuilder_to_str(built); + expect(str_contains(text, txt("empty")), label); + expect(canaries_intact(storage, 0), label); + strbuilder_free(& built); +} + +int main() +{ + using namespace gen; + Context ctx = {}; init(& ctx); + expect_row_empty (txt("empty")); + expect_row_single(txt("single")); + expect_row_ends (txt("ends")); + expect_skip_end (txt("skip")); + expect_builder (txt("builder")); + deinit(& ctx); + + Str null_def = { nullptr, 0 }; + Str null_ptr = { nullptr, 4 }; + expect_boundary(null_def, txt("null-len"), txt("gen::parse_variable: length must greater than 0")); + expect_boundary(null_ptr, txt("null-ptr"), txt("gen::parse_variable: def was null")); + expect_boundary(txt(""), txt("zero-len"), txt("gen::parse_variable: length must greater than 0")); + expect_boundary(txt(" \n\t"), txt("whitespace"), txt("no tokens found")); + expect_boundary(txt("// note\n"), txt("comment"), txt("only formatting tokens")); + expect_eat_end (txt("eat")); + expect_direct (txt("direct")); + + if (g_failures != 0) { + log_fmt("parser bounds failed: %d\n", g_failures); return 1; + } + log_fmt("parser bounds passed\n"); + return 0; +}