diff --git a/base/components/interface.hpp b/base/components/interface.hpp index 9351352..fbfb3dc 100644 --- a/base/components/interface.hpp +++ b/base/components/interface.hpp @@ -371,8 +371,7 @@ struct ParseStackNode // TODO(Ed): When an error occurs, the parse stack is not released and instead the scope is left dangling. }; -struct ParseOpts -{ +struct ParseOpts { AllocatorInfo backing_msgs; AllocatorInfo backing_tokens; AllocatorInfo backing_ast; @@ -380,34 +379,53 @@ struct ParseOpts ParseInfo wip_parse_str( LexedInfo lexed, ParseOpts* opts GEN_PARAM_DEFAULT ); -GEN_API CodeClass parse_class ( Str class_def ); -GEN_API CodeConstructor parse_constructor ( Str constructor_def ); -GEN_API CodeDefine parse_define ( Str define_def ); -GEN_API CodeDestructor parse_destructor ( Str destructor_def ); -GEN_API CodeEnum parse_enum ( Str enum_def ); -GEN_API CodeBody parse_export_body ( Str export_def ); -GEN_API CodeExtern parse_extern_link ( Str exten_link_def ); -GEN_API CodeFriend parse_friend ( Str friend_def ); -GEN_API CodeFn parse_function ( Str fn_def ); - -struct Opts_parse_global_body -{ +struct Opts_parse { Context* ctx; ParseInfo* info; }; -GEN_API void parse_global_body_base( Context* ctx, Str body_def, ParseInfo* info ); -GEN_API CodeBody parse_global_body( Str body_def, Opts_parse_global_body opts GEN_PARAM_DEFAULT ); +typedef Opts_parse Opts_parse_global_body; -GEN_API CodeNS parse_namespace ( Str namespace_def ); -GEN_API CodeOperator parse_operator ( Str operator_def ); -GEN_API CodeOpCast parse_operator_cast( Str operator_def ); -GEN_API CodeStruct parse_struct ( Str struct_def ); -GEN_API CodeTemplate parse_template ( Str template_def ); -GEN_API CodeTypename parse_type ( Str type_def ); -GEN_API CodeTypedef parse_typedef ( Str typedef_def ); -GEN_API CodeUnion parse_union ( Str union_def ); -GEN_API CodeUsing parse_using ( Str using_def ); -GEN_API CodeVar parse_variable ( Str var_def ); +GEN_API void parse_class_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_constructor_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_define_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_destructor_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_enum_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_export_body_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_extern_link_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_friend_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_function_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_global_body_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_namespace_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_operator_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_operator_cast_base(Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_struct_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_template_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_type_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_typedef_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_union_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_using_base (Context* ctx, Str def, ParseInfo* info); +GEN_API void parse_variable_base (Context* ctx, Str def, ParseInfo* info); + +GEN_API CodeClass parse_class (Str class_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeConstructor parse_constructor (Str constructor_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeDefine parse_define (Str define_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeDestructor parse_destructor (Str destructor_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeEnum parse_enum (Str enum_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeBody parse_export_body (Str export_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeExtern parse_extern_link (Str exten_link_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeFriend parse_friend (Str friend_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeFn parse_function (Str fn_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeBody parse_global_body (Str body_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeNS parse_namespace (Str namespace_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeOperator parse_operator (Str operator_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeOpCast parse_operator_cast(Str operator_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeStruct parse_struct (Str struct_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeTemplate parse_template (Str template_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeTypename parse_type (Str type_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeTypedef parse_typedef (Str typedef_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeUnion parse_union (Str union_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeUsing parse_using (Str using_def, Opts_parse opts GEN_PARAM_DEFAULT); +GEN_API CodeVar parse_variable (Str var_def, Opts_parse opts GEN_PARAM_DEFAULT); #pragma endregion Parsing diff --git a/base/components/interface.parsing.cpp b/base/components/interface.parsing.cpp index 09337ca..47f2594 100644 --- a/base/components/interface.parsing.cpp +++ b/base/components/interface.parsing.cpp @@ -11,8 +11,7 @@ ParseInfo wip_parse_str(LexedInfo lexed, ParseOpts* opts) { // TODO(Ed): Lift this. - Context* ctx = _ctx; - ParseInfo info = struct_zero(ParseInfo); + Context* ctx = _ctx; ParseInfo info = struct_zero(ParseInfo); if (lexed.tokens.num == 0 && lexed.tokens.ptr == nullptr) { ctx->parser = struct_zero(ParseContext); @@ -26,92 +25,74 @@ ParseInfo wip_parse_str(LexedInfo lexed, ParseOpts* opts) // TODO(Ed): ParseInfo should be set to the parser context. - 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); + ctx->parser = struct_zero(ParseContext); + ctx->parser.tokens = lexed.tokens; { + ctx->parser.lexed_messages = lexed.messages; + 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; } -CodeClass parse_class( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; - CodeClass result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); +internal b32 parse_base_begin(Context* ctx, Str def, ParseInfo* info, char const* function_name) { + if (ctx == nullptr || info == nullptr) GEN_FATAL("parse base: null context or info"); - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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; + info[0] = struct_zero(ParseInfo); + info->result = Code_Invalid; + ctx->parser = struct_zero(ParseContext); + if (_check_parse_args(ctx, def, function_name) == false) { + info->messages = ctx->parser.messages; + return false; } - { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope); - result = (CodeClass) parse_class_struct( ctx, Tok_Decl_Class, parser_not_inplace_def ); - parser_pop(& ctx->parser); + info->lexed = lex(ctx, def); + ctx->parser.lexed_messages = info->lexed.messages; + if (info->lexed.tokens.num == 0) { + info->messages = ctx->parser.messages; + return false; } -done: - return result; + + ctx->parser.tokens = info->lexed.tokens; + return true; } -CodeConstructor parse_constructor(Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; - CodeConstructor result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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; +void parse_class_base(Context* ctx, Str def, ParseInfo* info) { + CodeClass result = InvalidCode; + if (parse_base_begin(ctx, def, info, "parse_class") == false) return; + /*Parse*/ { + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); + result = (CodeClass) parse_class_struct( ctx, Tok_Decl_Class, parser_not_inplace_def ); + parser_pop(& ctx->parser); } + info->result = cast(Code, result); + info->messages = ctx->parser.messages; +} - { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope); +CodeClass parse_class(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_class: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_class_base(ctx, def, info); return cast(CodeClass, info->result); +} - // TODO(Ed): Constructors can have prefix attributes +void parse_constructor_base(Context* ctx, Str def, ParseInfo* info) +{ + CodeConstructor result = InvalidCode; + if (parse_base_begin(ctx, def, info, "parse_constructor") == false) return; + /*Parse*/ { + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); - CodeSpecifiers specifiers = NullCode; - Specifier specs_found[ 16 ] = { Spec_NumSpecifiers }; - s32 NumSpecifiers = 0; - - while ( left && tok_is_specifier(currtok) ) - { - Specifier spec = str_to_specifier( currtok.Text ); - - b32 ignore_spec = false; - - switch ( spec ) + // TODO(Ed): Constructors can have prefix attributes + CodeSpecifiers specifiers = NullCode; + Specifier specs_found[ 16 ] = { Spec_NumSpecifiers }; + s32 NumSpecifiers = 0; + while ( left && tok_is_specifier(currtok) ) { + b32 ignore_spec = false; + Specifier spec = str_to_specifier( currtok.Text ); + switch ( spec ) { case Spec_Constexpr : case Spec_Explicit: case Spec_Inline : @@ -126,227 +107,160 @@ 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); - goto done; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; + return; + } + + // Every specifier after would be considered part of the type type signature + if (ignore_spec) break; + + specs_found[ NumSpecifiers ] = spec; + ++ NumSpecifiers; + eat( currtok.Type ); } - // Every specifier after would be considered part of the type type signature - if (ignore_spec) - break; - - specs_found[ NumSpecifiers ] = spec; - NumSpecifiers++; - eat( currtok.Type ); + if ( NumSpecifiers ) { + specifiers = def_specifiers_arr( NumSpecifiers, specs_found ); + // ... + } + result = parser_parse_constructor(ctx, specifiers); + parser_pop(& ctx->parser); } - - if ( NumSpecifiers ) { - specifiers = def_specifiers_arr( NumSpecifiers, specs_found ); - // ... - } - - result = parser_parse_constructor(ctx, specifiers); - parser_pop(& ctx->parser); - } -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeDefine parse_define( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeConstructor parse_constructor(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_constructor: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_constructor_base(ctx, def, info); return cast(CodeConstructor, info->result); +} + +void parse_define_base(Context* ctx, Str def, ParseInfo* info) { CodeDefine result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_define") == false) return; + /*Parse*/ { + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope); + result = parser_parse_define(ctx); + parser_pop(& ctx->parser); } - - { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope); - result = parser_parse_define(ctx); - parser_pop(& ctx->parser); - } -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeDestructor parse_destructor( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeDefine parse_define(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_define: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_define_base(ctx, def, info); return cast(CodeDefine, info->result); +} + +void parse_destructor_base(Context* ctx, Str def, ParseInfo* info) { CodeDestructor result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_destructor") == false) return; // TODO(Ed): Destructors can have prefix attributes // TODO(Ed): Destructors can have virtual result = parser_parse_destructor(ctx, NullCode); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeEnum parse_enum( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeDestructor parse_destructor(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_destructor: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_destructor_base(ctx, def, info); return cast(CodeDestructor, info->result); +} + +void parse_enum_base(Context* ctx, Str def, ParseInfo* info) { CodeEnum result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_enum") == false) return; result = parser_parse_enum(ctx, parser_not_inplace_def); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeBody parse_export_body( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeEnum parse_enum(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_enum: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_enum_base(ctx, def, info); return cast(CodeEnum, info->result); +} + +void parse_export_body_base(Context* ctx, Str def, ParseInfo* info) { CodeBody result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_export_body") == false) return; result = parser_parse_export_body(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeExtern parse_extern_link( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeBody parse_export_body(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_export_body: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_export_body_base(ctx, def, info); return cast(CodeBody, info->result); +} + +void parse_extern_link_base(Context* ctx, Str def, ParseInfo* info) { CodeExtern result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_extern_link") == false) return; result = parser_parse_extern_link(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeFriend parse_friend( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeExtern parse_extern_link(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_extern_link: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_extern_link_base(ctx, def, info); return cast(CodeExtern, info->result); +} + +void parse_friend_base(Context* ctx, Str def, ParseInfo* info) { CodeFriend result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_friend") == false) return; result = parser_parse_friend(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeFn parse_function( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; - CodeFn result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); +CodeFriend parse_friend(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_friend: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_friend_base(ctx, def, info); return cast(CodeFriend, info->result); +} - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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; - } +void parse_function_base(Context* ctx, Str def, ParseInfo* info) { + CodeFn result = InvalidCode; + if (parse_base_begin(ctx, def, info, "parse_function") == false) return; result = (CodeFn) parser_parse_function(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; +} + +CodeFn parse_function(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); + Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_function: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = & ctx->parse_info; + parse_function_base(ctx, def, info); return cast(CodeFn, info->result); } void parse_global_body_base( Context* ctx, Str def, ParseInfo* info ) { - LexedInfo lexed; - CodeBody body; - - if ( ctx == nullptr || info == nullptr ) - GEN_FATAL( "parse_global_body_base: null context or info" ); + LexedInfo lexed; CodeBody body; + if ( ctx == nullptr || info == nullptr ) GEN_FATAL( "parse_global_body_base: null context or info" ); info[0] = struct_zero( ParseInfo ); lexed = struct_zero( LexedInfo ); @@ -362,6 +276,7 @@ void parse_global_body_base( Context* ctx, Str def, ParseInfo* info ) lexed = lex( ctx, def ); info->lexed = lexed; + ctx->parser.lexed_messages = lexed.messages; if ( lexed.tokens.num == 0 ) { info->result = cast( Code, body ); info->messages = ctx->parser.messages; @@ -378,286 +293,179 @@ void parse_global_body_base( Context* ctx, Str def, ParseInfo* info ) info->messages = ctx->parser.messages; } -CodeBody parse_global_body( Str def, Opts_parse_global_body opts ) { - Opts_parse_global_body resolved = get_optional( opts ); - Context* ctx = resolved.ctx; - ParseInfo* info; - - if ( ctx == nullptr ) ctx = _ctx; - if ( ctx == nullptr ) GEN_FATAL( "parse_global_body: no context" ); - info = resolved.info; if ( info == nullptr ) info = & ctx->parse_info; - - parse_global_body_base( ctx, def, info ); - return cast( CodeBody, info->result ); +CodeBody parse_global_body( Str def, Opts_parse opts ) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if ( ctx == nullptr ) ctx = _ctx; if ( ctx == nullptr ) GEN_FATAL( "parse_global_body: no context" ); + ParseInfo* info = resolved.info; if ( info == nullptr ) info = & ctx->parse_info; + parse_global_body_base( ctx, def, info ); return cast( CodeBody, info->result ); } -CodeNS parse_namespace( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; - CodeNS result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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; - } +void parse_namespace_base(Context* ctx, Str def, ParseInfo* info) { + CodeNS result = InvalidCode; + if (parse_base_begin(ctx, def, info, "parse_namespace") == false) return; result = parser_parse_namespace(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeOperator parse_operator( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeNS parse_namespace(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_namespace: no context"); + ParseInfo* info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_namespace_base(ctx, def, info); return cast(CodeNS, info->result); +} + +void parse_operator_base(Context* ctx, Str def, ParseInfo* info) { CodeOperator result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_operator") == false) return; result = (CodeOperator) parser_parse_operator(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeOpCast parse_operator_cast( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeOperator parse_operator(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_operator: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_operator_base(ctx, def, info); return cast(CodeOperator, info->result); +} + +void parse_operator_cast_base(Context* ctx, Str def, ParseInfo* info) { CodeOpCast result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_operator_cast") == false) return; result = parser_parse_operator_cast(ctx, NullCode); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeStruct parse_struct( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeOpCast parse_operator_cast(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_operator_cast: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_operator_cast_base(ctx, def, info); return cast(CodeOpCast, info->result); +} + +void parse_struct_base(Context* ctx, Str def, ParseInfo* info) { CodeStruct result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_struct") == false) return; + /*Parse*/ { + ParseStackNode scope = NullScope; + parser_push(& ctx->parser, & scope); + result = (CodeStruct) parse_class_struct( ctx, Tok_Decl_Struct, parser_not_inplace_def ); + parser_pop(& ctx->parser); } - - { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope); - result = (CodeStruct) parse_class_struct( ctx, Tok_Decl_Struct, parser_not_inplace_def ); - parser_pop(& ctx->parser); - } -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeTemplate parse_template( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeStruct parse_struct(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_struct: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_struct_base(ctx, def, info); return cast(CodeStruct, info->result); +} + +void parse_template_base(Context* ctx, Str def, ParseInfo* info) { CodeTemplate result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_template") == false) return; result = parser_parse_template(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeTypename parse_type( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeTemplate parse_template(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_template: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_template_base(ctx, def, info); return cast(CodeTemplate, info->result); +} + +void parse_type_base(Context* ctx, Str def, ParseInfo* info) { CodeTypename result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); + if (parse_base_begin(ctx, def, info, "parse_type") == false) return; - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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; - } - - result = parser_parse_type( ctx, parser_not_from_template, nullptr); -done: - return result; + result = parser_parse_type(ctx, parser_not_from_template, nullptr); + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeTypedef parse_typedef( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeTypename parse_type(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_type: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_type_base(ctx, def, info); return cast(CodeTypename, info->result); +} + +void parse_typedef_base(Context* ctx, Str def, ParseInfo* info) { CodeTypedef result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_typedef") == false) return; result = parser_parse_typedef(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeUnion parse_union( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeTypedef parse_typedef(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_typedef: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_typedef_base(ctx, def, info); return cast(CodeTypedef, info->result); +} + +void parse_union_base(Context* ctx, Str def, ParseInfo* info) { CodeUnion result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_union") == false) return; result = parser_parse_union(ctx, parser_not_inplace_def); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeUsing parse_using( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; +CodeUnion parse_union(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_union: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_union_base(ctx, def, info); return cast(CodeUnion, info->result); +} + +void parse_using_base(Context* ctx, Str def, ParseInfo* info) { CodeUsing result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); - - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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 (parse_base_begin(ctx, def, info, "parse_using") == false) return; result = parser_parse_using(ctx); -done: - return result; + info->result = cast(Code, result); + info->messages = ctx->parser.messages; } -CodeVar parse_variable( Str def ) -{ - // TODO(Ed): Lift this. - Context* ctx = _ctx; - CodeVar result = InvalidCode; - LexedInfo lexed = struct_zero(LexedInfo); +CodeUsing parse_using(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_using: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_using_base(ctx, def, info); return cast(CodeUsing, info->result); +} - ctx->parser = struct_zero(ParseContext); - if (check_parse_args(def) == false) - goto done; - - lexed = lex(ctx, def); - ctx->parser.tokens = lexed.tokens; - if (lexed.messages != nullptr || ctx->parser.tokens.num == 0) { - 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)) { +void parse_variable_base(Context* ctx, Str def, ParseInfo* info) { + CodeVar result = InvalidCode; + if (parse_base_begin(ctx, def, info, "parse_variable") == false) return; + if (parser_slice_is_formatting_only(& ctx->parser)) { parser_record_failure(ctx, txt("parse_variable: only formatting tokens")); - goto done; } + else { + result = parser_parse_variable(ctx); + } + info->result = cast(Code, result); + info->messages = ctx->parser.messages; +} - result = parser_parse_variable(ctx); -done: - return result; +CodeVar parse_variable(Str def, Opts_parse opts) { + Opts_parse resolved = get_optional(opts); Context* ctx = resolved.ctx; ParseInfo* info; + if (ctx == nullptr) ctx = _ctx; if (ctx == nullptr) GEN_FATAL("parse_variable: no context"); + info = resolved.info; if (info == nullptr) info = &ctx->parse_info; + parse_variable_base(ctx, def, info); return cast(CodeVar, info->result); } // Undef helper macros diff --git a/base/components/parser.cpp b/base/components/parser.cpp index f39cf3f..c63b6df 100644 --- a/base/components/parser.cpp +++ b/base/components/parser.cpp @@ -104,14 +104,49 @@ void parser_record_failure(Context* ctx, Str text) { ctx->parser.messages = msg; } +internal b32 parser_is_recoverable_body(ParseContext* parser) { + ParseStackNode* scope = parser->scope; + b32 in_function_body = false; + while ( scope ) + { + if ( scope->code_rel ) + { + CodeType type = scope->code_rel->Type; + if ( type == CT_Function_Body ) { + in_function_body = true; + scope = scope->prev; + continue; + } + if ( in_function_body && type != CT_Global_Body ) + return false; + return type == CT_Global_Body || type == CT_Namespace_Body || type == CT_Class_Body || type == CT_Struct_Body; + } + scope = scope->prev; + } + return false; +} + bool lex__eat(Context* ctx, ParseContext* parser, TokType type) { b32 ate = false; Token at_idx; b32 not_accepted; b32 is_identifier; - if (parser->tokens.num - parser->token_id <= 0) + s32 start_id = parser->token_id; + if ( parser->token_mismatch ) goto done; + if (parser->tokens.num - parser->token_id <= 0) + { + if ( parser_is_recoverable_body(parser) ) { + StrBuilder message = strbuilder_fmt_buf(ctx->Allocator_Temp, "Parse Error, TokArray::eat, Expected: ' %S ' at end of token slice\n%SB" + , toktype_to_str(type) + , parser_to_strbuilder(parser, ctx->Allocator_Temp) + ); + parser_record_failure(ctx, strbuilder_to_str(message)); + parser->token_mismatch = true; + } + goto done; + } at_idx = parser->tokens.ptr[ parser->token_id ]; if ( ( at_idx.Type == Tok_NewLine && type != Tok_NewLine ) @@ -129,15 +164,26 @@ bool lex__eat(Context* ctx, ParseContext* parser, TokType type) } if ( not_accepted ) { - Token tok = * lex_current( parser, lex_skip_formatting ); - log_failure( "Parse Error, TokArray::eat, Expected: ' %S ' not ' %S ' (%d, %d)`\n%SB" + parser->token_id = start_id; + Token tok = * lex_current( parser, lex_skip_formatting ); + StrBuilder message = strbuilder_fmt_buf(ctx->Allocator_Temp, "Parse Error, TokArray::eat, Expected: ' %S ' not ' %S ' (%d, %d)`\n%SB" , toktype_to_str(type) , at_idx.Text , tok.Line , tok.Column , parser_to_strbuilder(parser, ctx->Allocator_Temp) ); - GEN_DEBUG_TRAP(); + Str message_text = strbuilder_to_str(message); + if ( parser_is_recoverable_body(parser) ) { + parser_record_failure(ctx, message_text); + parser->token_mismatch = true; + } + else if ( parser->lexed_messages ) + parser_record_failure(ctx, message_text); + else { + log_failure("%S", message_text); + GEN_DEBUG_TRAP(); + } goto done; } @@ -206,7 +252,7 @@ done: # define nexttok (* lex_next( & ctx->parser, lex_skip_formatting )) # define nexttok_noskip (* lex_next( & ctx->parser, lex_dont_skip_formatting)) # define eat( Type_ ) lex__eat(ctx, & ctx->parser, Type_ ) -# define left ( ctx->parser.tokens.num - ctx->parser.token_id ) +# define left ( ! ctx->parser.token_mismatch && ctx->parser.tokens.num - ctx->parser.token_id ) #if GEN_COMPILER_CPP # define def_assign( ... ) { __VA_ARGS__ } @@ -233,6 +279,8 @@ done: // Procedure Forwards ( Entire parser internal parser interface ) +internal void parser_recover_body_member(Context* ctx, CodeBody body, Token member_start_token, s32 member_start_id, b32 allow_comma); + internal Code parse_array_decl (Context* ctx); internal CodeAttributes parse_attributes (Context* ctx); internal CodeComment parse_comment (Context* ctx); @@ -533,8 +581,7 @@ StrBuilder parser_strip_formatting(Context* ctx, Str raw_text, bool preserve_new return content; } -StrBuilder parser_strip_formatting_2(TokenSlice tokens) -{ +StrBuilder parser_strip_formatting_2(TokenSlice tokens) { // TODO(Ed): Use this to produce strings for validation purposes. We shouldn't serialize down from tokens once we start storing token slices for content. StrBuilder result = struct_zero(StrBuilder); return result; @@ -870,8 +917,14 @@ CodeBody parse_class_struct_body(Context* ctx, TokType which, Token name) else result->Type = CT_Struct_Body; + scope.code_rel = cast(Code, result); + while ( left && currtok_noskip.Type != Tok_BraceCurly_Close ) { + ParseMessage* messages_before_member = ctx->parser.messages; + s32 member_start_id = ctx->parser.token_id; + Token member_start_token = currtok_noskip; + Code member = Code_Invalid; CodeAttributes attributes = { nullptr }; CodeSpecifiers specifiers = { nullptr }; @@ -1180,11 +1233,26 @@ CodeBody parse_class_struct_body(Context* ctx, TokType which, Token name) break; } - if ( member == Code_Invalid ) + b32 member_is_nested_scope = member != Code_Invalid && (member->Type == CT_Class || member->Type == CT_Struct); + b32 member_failed = ctx->parser.token_mismatch || (ctx->parser.messages != messages_before_member && member_is_nested_scope == false); + if (member_failed) { + if ( ctx->parser.messages != messages_before_member ) ctx->parser.messages->Next = messages_before_member; + ctx->parser.token_id = member_start_id; + ctx->parser.token_mismatch = false; + member = Code_Invalid; + } + + if (member == Code_Invalid) { - log_failure( "Failed to parse member\n%SB", parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp) ); - parser_pop(& ctx->parser); - return InvalidCode; + if ( member_failed == false ) { + log_failure( "Failed to parse member\n%SB", parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp) ); + parser_pop(& ctx->parser); + return InvalidCode; + } + ctx->parser.token_id = member_start_id; + ctx->parser.token_mismatch = false; + parser_recover_body_member(ctx, result, member_start_token, member_start_id, false); + continue; } body_append(result, member ); } @@ -1196,10 +1264,8 @@ CodeBody parse_class_struct_body(Context* ctx, TokType which, Token name) } internal -CodeComment parse_comment(Context* ctx) -{ - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope ); +CodeComment parse_comment(Context* ctx) { + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope ); CodeComment result = (CodeComment) make_code(); @@ -1215,8 +1281,7 @@ CodeComment parse_comment(Context* ctx) internal Code parse_complicated_definition(Context* ctx, TokType which) { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope ); + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope ); b32 is_inplace = false; b32 is_fn_def = false; @@ -1399,8 +1464,7 @@ Code parse_complicated_definition(Context* ctx, TokType which) internal inline Code parse_assignment_expression(Context* ctx) { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope ); + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope ); Code expr = { nullptr }; @@ -1445,7 +1509,6 @@ internal inline Code parse_forward_or_definition(Context* ctx, TokType which, bool is_inplace ) { Code result = InvalidCode; - switch ( which ) { case Tok_Decl_Class: @@ -1483,8 +1546,7 @@ CodeFn parse_function_after_name(Context* ctx , Token name ) { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope ); + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope ); CodeParams params = parse_params(ctx, parser_use_parenthesis); // ( ) @@ -1639,14 +1701,14 @@ CodeFn parse_function_after_name(Context* ctx internal Code parse_function_body(Context* ctx) { - ParseStackNode scope = NullScope; - parser_push(& ctx->parser, & scope ); + ParseStackNode scope = NullScope; parser_push(& ctx->parser, & scope ); eat( Tok_BraceCurly_Open ); CodeBody result = (CodeBody) make_code(); result->Type = CT_Function_Body; + scope.code_rel = cast(Code, result); // TODO : Support actual parsing of function body Token start = currtok_noskip; @@ -1667,8 +1729,7 @@ Code parse_function_body(Context* ctx) s32 len = ( (sptr)prevtok.Text.Ptr + prevtok.Text.Len ) - (sptr)start.Text.Ptr; - if ( len > 0 ) - { + if ( len > 0 ) { Str str = { start.Text.Ptr, len }; body_append( result, cast(Code, def_execution( str )) ); } @@ -1679,6 +1740,294 @@ Code parse_function_body(Context* ctx) return cast(Code, result); } +internal +s32 parser_recovery_scope_close_id(ParseContext const* parser, s32 member_start_id, b32* nested_body, b32* has_scope_close) +{ + nested_body[0] = false; + has_scope_close[0] = false; + ParseStackNode* scope = parser->scope; + while ( scope != nullptr ) + { + b32 scope_has_code = scope->code_rel != nullptr; + if (scope_has_code) + { + CodeType type = scope->code_rel->Type; + + b32 is_nested_scope_body = false; + is_nested_scope_body |= type == CT_Namespace_Body; + is_nested_scope_body |= type == CT_Class_Body; + is_nested_scope_body |= type == CT_Struct_Body; + + // Function-body frames have their own brace owner; inspect the enclosing body frame. + if ( type == CT_Function_Body ) { scope = scope->prev; continue; } + + b32 nothing_to_recover = type == CT_Global_Body || is_nested_scope_body == false; + // Global recovery uses the full token slice. Other unsupported scope types have no nested boundary. + if (nothing_to_recover) return parser->tokens.num; // TODO(Ed): Drain to single return. + + // Nested recovery needs the active body's closing brace as a hard delimiter boundary. + nested_body[0] = true; + + s32 scope_start_id = parser->tokens.num; + b32 scope_has_start = scope->start != nullptr; + if (scope_has_start) scope_start_id = scast(s32, scope->start - parser->tokens.ptr); + + // Scope start may point at formatting before the body's opening brace. + s32 body_open_id = scope_start_id; + while ( body_open_id < parser->tokens.num ) { + b32 token_opens_body = parser->tokens.ptr[body_open_id].Type == Tok_BraceCurly_Open; + if (token_opens_body == true) break; + ++ body_open_id; + } + + b32 found_body_open = body_open_id < parser->tokens.num; + if (found_body_open == false) return parser->tokens.num; + + // A depth-zero close belongs to this body; nested closes only reduce depth. + s32 body_depth = 0; + for (s32 idx = body_open_id; idx < parser->tokens.num; ++ idx) + { + TokType token_type = parser->tokens.ptr[idx].Type; + if (token_type == Tok_BraceCurly_Open) + ++ body_depth; + else if (token_type == Tok_BraceCurly_Close) + { + if (body_depth > 0) // inside nested brace + { + -- body_depth; + if (body_depth == 0) { // found owner close + has_scope_close[0] = true; + // TODO(Ed): Drain to single return. + return idx; + } + } + } + } + + s32 fallback_close_id = parser->tokens.num; + if (type == CT_Class_Body || type == CT_Struct_Body) + { + // A malformed member opener can keep the brace-depth scan from reaching the owner close. + // Direct typed entries scan to their last candidate; nested bodies stop at the first candidate. + b32 has_parent_body = false; + for (ParseStackNode* parent = scope->prev; parent != nullptr; parent = parent->prev) + { + b32 parent_has_code = parent->code_rel != nullptr; + if (parent_has_code == false) continue; + + b32 parent_is_function_body = parent->code_rel->Type == CT_Function_Body; + if (parent_is_function_body) continue; + + CodeType parent_type = parent->code_rel->Type; + b32 parent_owns_body = false; + parent_owns_body |= parent_type == CT_Global_Body; + parent_owns_body |= parent_type == CT_Namespace_Body; + parent_owns_body |= parent_type == CT_Class_Body; + parent_owns_body |= parent_type == CT_Struct_Body; + has_parent_body = parent_owns_body; + break; + } + for (s32 idx = member_start_id; idx < parser->tokens.num; ++ idx) + { + if (parser->tokens.ptr[idx].Type != Tok_BraceCurly_Close) continue; + + s32 next_id = idx + 1; + while (next_id < parser->tokens.num) { + TokType next_type = parser->tokens.ptr[next_id].Type; + b32 next_is_formatting = next_type == Tok_NewLine || next_type == Tok_Comment; + if (next_is_formatting == false) break; + ++ next_id; + } + + b32 next_ends_statement = false; + b32 close_ends_declaration = false; + b32 next_in_slice = next_id < parser->tokens.num; + if (next_in_slice) { + next_ends_statement = parser->tokens.ptr[next_id].Type == Tok_Statement_End; + close_ends_declaration = next_ends_statement; + } + if (close_ends_declaration) { + fallback_close_id = idx; + if (has_parent_body) break; + } + } + } + else + { + // A namespace close follows a member terminator, unlike a nested type close followed by `;`. + for (s32 idx = member_start_id; idx < parser->tokens.num; ++ idx) + { + if (parser->tokens.ptr[idx].Type != Tok_BraceCurly_Close) continue; + + s32 prev_id = idx - 1; + while (prev_id >= body_open_id) { + TokType prev_type = parser->tokens.ptr[prev_id].Type; + b32 prev_is_formatting = prev_type == Tok_NewLine || prev_type == Tok_Comment; + if ( prev_is_formatting == false ) break; + -- prev_id; + } + + s32 next_id = idx + 1; + while (next_id < parser->tokens.num) { + TokType next_type = parser->tokens.ptr[next_id].Type; + b32 next_is_formatting = next_type == Tok_NewLine || next_type == Tok_Comment; + if ( next_is_formatting == false ) break; + ++ next_id; + } + + b32 prev_in_body = prev_id >= body_open_id; + b32 prev_ends_member = false; + if (prev_in_body) prev_ends_member = parser->tokens.ptr[prev_id].Type == Tok_Statement_End; + + b32 next_ends_declaration = false; + b32 next_past_slice = next_id >= parser->tokens.num; + if (next_past_slice == false) next_ends_declaration = parser->tokens.ptr[next_id].Type == Tok_Statement_End; + + b32 member_ended_before_close = false; + b32 close_starts_next_declaration = false; + b32 namespace_owner_close = false; + if (prev_in_body && prev_ends_member) member_ended_before_close = true; + if (next_past_slice == false && next_ends_declaration) close_starts_next_declaration = true; + if (member_ended_before_close && close_starts_next_declaration == false) namespace_owner_close = true; + if (namespace_owner_close) { + fallback_close_id = idx; + break; + } + } + } + + if (fallback_close_id < parser->tokens.num) { // found fallback close + has_scope_close[0] = true; + return fallback_close_id; + } + return parser->tokens.num; + } + scope = scope->prev; + } + return parser->tokens.num; +} + +internal +b32 parser_recovery_delimiter_is_balanced(ParseContext const* parser + , s32 start_id + , TokType open_type + , TokType close_type + , s32 scope_close_id + , b32 nested_body + , b32 has_scope_close +){ + // Inner delimiters shield semicolons only when their close precedes the owning body close. + if ( open_type == Tok_BraceCurly_Open && nested_body && has_scope_close == false ) return false; + s32 delimiter_depth = 0; + s32 curly_depth = 0; + for ( s32 idx = start_id; idx < scope_close_id; ++ idx ) + { + TokType type = parser->tokens.ptr[idx].Type; + if ( type == Tok_BraceCurly_Open ) + ++ curly_depth; + else if ( type == Tok_BraceCurly_Close ) + { + if ( curly_depth == 0 ) return false; + -- curly_depth; + } + + if ( type == open_type ) + ++ delimiter_depth; + else if ( type == close_type && delimiter_depth > 0 ) + { + -- delimiter_depth; + if ( delimiter_depth == 0 ) return true; + } + } + return false; +} + +internal +void parser_recover_body_member(Context* ctx, CodeBody body, Token member_start_token, s32 member_start_id, b32 allow_comma) +{ + // Keep the original member start for the untyped source span. + Token recovery_end_token = member_start_token; + b32 has_recovery_tokens = false; + s32 paren_depth = 0; + s32 square_depth = 0; + s32 curly_depth = 0; + b32 nested_body = false; + b32 has_scope_close = false; + // Bound delimiter matches before scanning the rejected member. + s32 scope_close_id = parser_recovery_scope_close_id(& ctx->parser, member_start_id, &nested_body, &has_scope_close); + while ( left ) + { + Token token = currtok_noskip; + // Leave the active body's close for its owner. + b32 is_scope_close = token.Type == Tok_BraceCurly_Close && curly_depth == 0; + if (is_scope_close) break; + + b32 at_member_start = ctx->parser.token_id == member_start_id; + b32 at_delimiter_depth_zero = paren_depth == 0 && square_depth == 0 && curly_depth == 0; + b32 sync_at_comma = allow_comma && at_member_start && at_delimiter_depth_zero && token.Type == Tok_Comma; + b32 sync_at_statement_end = token.Type == Tok_Statement_End && at_delimiter_depth_zero; + b32 sync_at_member_boundary = sync_at_comma || sync_at_statement_end; + // Semicolons sync at depth zero; only global recovery accepts a leading comma. + if (sync_at_member_boundary) + { + TokType sync_type = Tok_Statement_End; + if (sync_at_comma) sync_type = Tok_Comma; + if (eat( sync_type )) { + recovery_end_token = token; + has_recovery_tokens = true; + } + break; + } + + // Add delimiter depth only when its matching close precedes scope_close_id. + switch ( token.Type ) { default: break; + case Tok_Paren_Open: { + b32 res = parser_recovery_delimiter_is_balanced(& ctx->parser, ctx->parser.token_id + , Tok_Paren_Open, Tok_Paren_Close + , scope_close_id, nested_body, has_scope_close); + if (res) ++ paren_depth; + break; + } + + case Tok_Paren_Close: + if ( paren_depth > 0 ) -- paren_depth; + break; + + case Tok_BraceSquare_Open: { + b32 res = parser_recovery_delimiter_is_balanced(& ctx->parser, ctx->parser.token_id + , Tok_BraceSquare_Open, Tok_BraceSquare_Close + , scope_close_id, nested_body, has_scope_close); + if (res) ++ square_depth; + break; + } + + case Tok_BraceSquare_Close: + if ( square_depth > 0 ) -- square_depth; + break; + + case Tok_BraceCurly_Open: { + b32 res = parser_recovery_delimiter_is_balanced(& ctx->parser, ctx->parser.token_id + , Tok_BraceCurly_Open, Tok_BraceCurly_Close + , scope_close_id, nested_body, has_scope_close); + if (res) ++ curly_depth; + break; + } + + case Tok_BraceCurly_Close: + if ( curly_depth > 0 ) -- curly_depth; + break; + } + + if (eat( token.Type )) { + recovery_end_token = token; + has_recovery_tokens = true; + } + } + + // Retain the failed source through its synchronization token as untyped code. + if (has_recovery_tokens) body_append(body, untyped_str(token_range_to_str(member_start_token, recovery_end_token))); +} + internal neverinline CodeBody parse_global_nspace(Context* ctx, CodeType which) { @@ -1695,9 +2044,13 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) CodeBody result = (CodeBody) make_code(); result->Type = which; + scope.code_rel = cast(Code, result); while ( left && currtok_noskip.Type != Tok_BraceCurly_Close ) { + ParseMessage* messages_before_member = ctx->parser.messages; + s32 member_start_id = ctx->parser.token_id; + Token member_start_token = currtok_noskip; Code member = Code_Invalid; CodeAttributes attributes = { nullptr }; CodeSpecifiers specifiers = { nullptr }; @@ -1716,8 +2069,12 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) case Tok_Comma: { parser_record_failure(ctx, txt("Dangling comma")); - parser_pop( & ctx->parser); - return result; + if ( which != CT_Global_Body ) + { + parser_pop( & ctx->parser); + return result; + } + member = Code_Invalid; } break; case Tok_Statement_End: @@ -1849,6 +2206,12 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) case Tok_Module_Import: { // import ... + if ( which == CT_Global_Body ) + { + parser_record_failure(ctx, txt("Module import parsing is not implemented")); + member = Code_Invalid; + break; + } log_failure( "gen::%s: This function is not implemented" ); return InvalidCode; } @@ -1858,6 +2221,12 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) { if ( ! tok_is_attribute(currtok)) { + if ( which == CT_Global_Body ) + { + parser_record_failure(ctx, txt("Unbounded macro expression in global body")); + member = Code_Invalid; + break; + } log_failure("Unbounded macro expression residing in class/struct body\n%SB", parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp)); return InvalidCode; } @@ -1878,6 +2247,7 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) { Specifier specs_found[16] = { Spec_NumSpecifiers }; s32 NumSpecifiers = 0; + b32 invalid_global_specifier = false; while ( left && tok_is_specifier(currtok) ) { @@ -1900,11 +2270,22 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) default: Str spec_str = spec_to_str(spec); - + if ( which == CT_Global_Body ) + { + StrBuilder message = strbuilder_fmt_buf(ctx->Allocator_Temp, "Invalid specifier %S for variable\n%SB" + , spec_str + , parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp) + ); + parser_record_failure(ctx, strbuilder_to_str(message)); + invalid_global_specifier = true; + break; + } log_failure( "Invalid specifier %S for variable\n%S", spec_str, strbuilder_to_str( parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp)) ); parser_pop(& ctx->parser); return InvalidCode; } + if ( invalid_global_specifier ) + break; if (ignore_spec) break; @@ -1913,6 +2294,11 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) NumSpecifiers++; eat( currtok.Type ); } + if ( invalid_global_specifier ) + { + member = Code_Invalid; + break; + } if ( NumSpecifiers ) { @@ -1979,8 +2365,32 @@ CodeBody parse_global_nspace(Context* ctx, CodeType which) } } + b32 member_is_nested_scope = member != Code_Invalid && (member->Type == CT_Class || member->Type == CT_Struct || member->Type == CT_Namespace); + b32 member_failed = ctx->parser.token_mismatch || (ctx->parser.messages != messages_before_member && ! member_is_nested_scope); + b32 can_recover_member = which == CT_Global_Body || which == CT_Namespace_Body; + if (can_recover_member && member_failed) { + if (ctx->parser.messages != messages_before_member) ctx->parser.messages->Next = messages_before_member; + ctx->parser.token_id = member_start_id; + ctx->parser.token_mismatch = false; + member = Code_Invalid; + } + if ( member == Code_Invalid ) { + if ( which == CT_Global_Body || (which == CT_Namespace_Body && member_failed) ) + { + if ( ctx->parser.messages == messages_before_member ) + { + StrBuilder message = strbuilder_fmt_buf(ctx->Allocator_Temp, "Failed to parse member\nToken: %SB\nContext:\n%SB" + , tok_to_strbuilder(ctx->Allocator_Temp, currtok_noskip) + , parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp) + ); + parser_record_failure(ctx, strbuilder_to_str(message)); + } + parser_recover_body_member(ctx, result, member_start_token, member_start_id, which == CT_Global_Body); + continue; + } + log_failure( "Failed to parse member\nToken: %SB\nContext:\n%SB", tok_to_strbuilder(ctx->Allocator_Temp, currtok_noskip), parser_to_strbuilder(& ctx->parser, ctx->Allocator_Temp) ); parser_pop(& ctx->parser); return InvalidCode; diff --git a/base/components/parser_types.hpp b/base/components/parser_types.hpp index 6f50264..8a8628c 100644 --- a/base/components/parser_types.hpp +++ b/base/components/parser_types.hpp @@ -162,6 +162,8 @@ struct ParseContext // TokArray Tokens; TokenSlice tokens; s32 token_id; + LexerMessage* lexed_messages; + b32 token_mismatch; }; enum MacroType : u16 diff --git a/gen_c_library/c_library.cpp b/gen_c_library/c_library.cpp index a2e1869..3e9fcf5 100644 --- a/gen_c_library/c_library.cpp +++ b/gen_c_library/c_library.cpp @@ -57,6 +57,68 @@ Code refactor_and_format( Code code ) { return code_refactor_and_format(code, scratch_file, path_refactor_script, path_format_style ); } +internal Code rewrite_parse_wrapper_signature(Code source, Str function_name) +{ + Str scanned = source->Content; + Str needle = StrBuilder::fmt_buf(_ctx->Allocator_Temp, "%S(", function_name).to_str(); + Str option_param = txt("Opts_parse opts"); + ssize function_at = -1; + ssize option_at = -1; + ssize last_offset = scanned.Len - needle.Len; + for (ssize scan_offset = 0; scan_offset <= last_offset; ++ scan_offset) { + char const* scan_at = scanned.Ptr + scan_offset; + if (c_str_compare_len(scan_at, needle.Ptr, needle.Len) == 0) { + if (scan_offset > 0) { + ssize prev_at = scan_offset - 1; + if (scanned.Ptr[prev_at] == '_') continue; + } + function_at = scan_offset; + break; + } + } + if (function_at < 0) GEN_FATAL("c_library: parser wrapper signature missing"); + + for (ssize params_end = function_at + needle.Len; + params_end < scanned.Len && scanned.Ptr[params_end] != ')'; + ++ params_end + ) { + ssize option_fit_end = params_end + option_param.Len; + if (option_fit_end > scanned.Len) break; + char const* params_at = scanned.Ptr + params_end; + if (c_str_compare_len(params_at, option_param.Ptr, option_param.Len) == 0) { + option_at = params_end; + break; + } + } + if (option_at < 0) GEN_FATAL("c_library: parser wrapper options parameter missing for %S", function_name); + + Str new_name = StrBuilder::fmt_buf(_ctx->Allocator_Temp, "%S_opts", function_name).to_str(); + Str pointer_param = txt("Opts_parse* opts"); + + ssize name_end = function_at + function_name.Len; + char const* before_opts_ptr = scanned.Ptr + name_end; + ssize before_opts_len = option_at - name_end; + Str before_name = { scanned.Ptr, function_at }; + Str before_opts = { before_opts_ptr, before_opts_len }; + + ssize after_opts_at = option_at + option_param.Len; + char const* after_opts_ptr = scanned.Ptr + after_opts_at; + ssize after_opts_len = scanned.Len - after_opts_at; + Str after_opts = { after_opts_ptr, after_opts_len }; + + ssize opts_star_len = 1; + ssize rebuilt_cap = scanned.Len; + rebuilt_cap += new_name.Len; + rebuilt_cap += opts_star_len; + StrBuilder rebuilt = StrBuilder::make_reserve(_ctx->Allocator_Temp, rebuilt_cap); + strbuilder_append_str(& rebuilt, before_name); + strbuilder_append_str(& rebuilt, new_name); + strbuilder_append_str(& rebuilt, before_opts); + strbuilder_append_str(& rebuilt, pointer_param); + strbuilder_append_str(& rebuilt, after_opts); + return untyped_str(strbuilder_to_str(rebuilt)); +} + constexpr bool helper_use_c_definition = true; int gen_main() @@ -962,6 +1024,18 @@ R"(#define AST_ArrSpecs_Cap \ txt("CodeUsing"), txt("CodeVar"), }; + // append, entry, and validate_body. Code is the untyped handle. CodeBody is the list / body pod. + Str code_body_slots[] = { + txt("Code"), + txt("CodeBody"), + }; + // has_entries reads NumEntries. These pods keep that word as an entry count. + Str code_has_entries_slots[] = { + txt("Code"), + txt("CodeBody"), + txt("CodeParams"), + txt("CodeDefineParams"), + }; CodeBody parsed_code_types = parse_file( path_base "components/code_types.hpp" ); CodeBody code_types = def_body(CT_Global_Body); @@ -1034,9 +1108,18 @@ R"(#define ( code ) _Generic( (code), \ , tmpl_def_start )); - // Append slots - for(Str type : code_typenames ) { - generic_selector.append_fmt("%S : %S,\\\n", type, fn->Name ); + // Append slots. Restricted procedures omit static types that cannot call them. + if (actual_name.is_equal(txt("append")) || actual_name.is_equal(txt("entry")) || actual_name.is_equal(txt("validate_body"))) { + for (Str type : code_body_slots) + generic_selector.append_fmt("%S : %S,\\\n", type, fn->Name); + } + else if (actual_name.is_equal(txt("has_entries"))) { + for (Str type : code_has_entries_slots) + generic_selector.append_fmt("%S : %S,\\\n", type, fn->Name); + } + else { + for (Str type : code_typenames) + generic_selector.append_fmt("%S : %S,\\\n", type, fn->Name); } generic_selector.append(txt("default: gen_generic_selection_fail \\\n")); @@ -1152,19 +1235,22 @@ R"(#define ( code ) _Generic( (code), \ for ( CodeParams opt_param : fn->Params ) if (opt_param->ValueType->Name.starts_with(txt("Opts_"))) { // Convert the definition to use a default struct: https://vxtwitter.com/vkrajacic/status/1749816169736073295 - if ( fn->Name.is_equal( txt("parse_global_body") ) ) + if (fn->Name.starts_with(txt("parse_"))) { - Str new_name = txt("parse_global_body_opts"); - char const* tmpl_fn_macro = "#define ( ... ) ( & () { __VA_ARGS__ } )\n"; - Code fn_macro; + Str new_name = StrBuilder::fmt_buf (_ctx->Allocator_Temp, "%S_opts", fn->Name).to_str(); + StrBuilder params_str = StrBuilder::make_reserve(_ctx->Allocator_Temp, 32); + for (CodeParams other_param : fn->Params) { + if (other_param == opt_param) break; + params_str.append_fmt("%S, ", other_param->Name); + } opt_param->ValueType->Specs = def_specifier( Spec_Ptr ); - fn_macro = untyped_str( token_fmt( + Code fn_macro = untyped_str( token_fmt( "def_name", fn->Name , "def__name", new_name - , "params", txt("def, ") + , "params", params_str.to_str() , "opts_type", opt_param->ValueType->Name - , tmpl_fn_macro + , "#define ( ... ) ( & () { __VA_ARGS__ } )\n" )); fn->Name = cache_str( new_name ); interface.append( fn ); @@ -1441,27 +1527,30 @@ R"(#define ( code ) _Generic( (code), \ Code src_parsing_interface = scan_file( path_base "components/interface.parsing.cpp" ); { - Str needle = txt("CodeBody parse_global_body( Str def, Opts_parse_global_body opts )"); - Str scanned = src_parsing_interface->Content; - ssize signature_at = -1; - ssize scan_offset; - ssize last_offset = scanned.Len - needle.Len; - for ( scan_offset = 0; scan_offset <= last_offset; ++ scan_offset ) - { - if ( c_str_compare_len( scanned.Ptr + scan_offset, needle.Ptr, needle.Len ) == 0 ) { - signature_at = scan_offset; - break; - } - } - if ( signature_at < 0 ) GEN_FATAL( "c_library: parse_global_body signature missing from scanned interface.parsing.cpp" ); - /*Resolved replacement*/ { - Str replacement = txt("CodeBody parse_global_body_opts( Str def, Opts_parse_global_body* opts )"); - StrBuilder rebuilt = strbuilder_make_reserve( _ctx->Allocator_Temp, scanned.Len + replacement.Len ); - strbuilder_append_str( & rebuilt, Str { scanned.Ptr, signature_at } ); - strbuilder_append_str( & rebuilt, replacement ); - strbuilder_append_str( & rebuilt, Str { scanned.Ptr + signature_at + needle.Len, scanned.Len - signature_at - needle.Len } ); - src_parsing_interface = untyped_str( strbuilder_to_str( rebuilt ) ); - } + Str parser_names[] = { + txt("parse_class"), + txt("parse_constructor"), + txt("parse_define"), + txt("parse_destructor"), + txt("parse_enum"), + txt("parse_export_body"), + txt("parse_extern_link"), + txt("parse_friend"), + txt("parse_function"), + txt("parse_global_body"), + txt("parse_namespace"), + txt("parse_operator"), + txt("parse_operator_cast"), + txt("parse_struct"), + txt("parse_template"), + txt("parse_type"), + txt("parse_typedef"), + txt("parse_union"), + txt("parse_using"), + txt("parse_variable"), + }; + for (s32 idx = 0; idx < (s32)(sizeof(parser_names) / sizeof(parser_names[0])); ++idx) + src_parsing_interface = rewrite_parse_wrapper_signature(src_parsing_interface, parser_names[idx]); } Code src_untyped = scan_file( path_base "components/interface.untyped.cpp" ); Code src_parser_case_macros = scan_file( path_base "components/parser_case_macros.cpp" ); diff --git a/test/c_library/test.c b/test/c_library/test.c index fb46b26..54209ed 100644 --- a/test/c_library/test.c +++ b/test/c_library/test.c @@ -34,6 +34,14 @@ gen_internal gen_Code_POD code_pod(gen_Code code) { return result; } +gen_internal void expect_parse_info(void* result, ParseInfo* info, gen_Str label) { + expect(result != 0, label); + void* info_result = info->result; + expect(info_result == result, label); + expect(info->lexed.tokens.num > 0, label); + expect(info->lexed.messages == 0 && info->messages == 0, label); +} + int main() { gen_Context ctx = {0}; gen_init(&ctx); @@ -475,24 +483,574 @@ int main() expect(untyped != 0, gen_txt("c11 untyped")); gen_Str untyped_text = gen_strbuilder_to_str(gen_code_to_strbuilder(untyped)); - expect_fragments(untyped_text, gen_txt("c11 untyped content")); + expect_fragments(untyped_text, gen_txt("c11 untyped content")); expect_variable(gen_parse_variable(untyped_text), gen_txt("c11 untyped reparsed")); gen_CodeVar parsed = gen_parse_variable(code(char const* baseline_message = "GENCPP_BASELINE"; )); - expect_variable(parsed, gen_txt("c11 parsed")); + expect_variable(parsed, gen_txt("c11 parsed")); expect_fragments(gen_strbuilder_to_str(gen_code_to_strbuilder(parsed)), gen_txt("c11 parsed serialized")); + gen_CodeClass parser_class = gen_parse_class(gen_txt("class ParseClass {};")); + gen_CodeConstructor parser_constructor = gen_parse_constructor(gen_txt("ParseClass() {}")); + gen_CodeDefine parser_define = gen_parse_define(gen_txt("#define PARSE_DEFINE 1")); + gen_CodeDestructor parser_destructor = gen_parse_destructor(gen_txt("~ParseClass();")); + gen_CodeEnum parser_enum = gen_parse_enum(gen_txt("enum ParseEnum { ParseValue };")); + gen_CodeBody parser_export_body = gen_parse_export_body(gen_txt("{ int export_value; }")); + gen_CodeExtern parser_extern = gen_parse_extern_link(gen_txt("extern \"C\" { int extern_value; }")); + gen_CodeFriend parser_friend = gen_parse_friend(gen_txt("friend int parse_friend_function();")); + gen_CodeFn parser_function = gen_parse_function(gen_txt("int parse_function();")); + gen_CodeBody parser_global_body = gen_parse_global_body(gen_txt("struct ParseGlobal { int value; };")); + gen_CodeNS parser_namespace = gen_parse_namespace(gen_txt("namespace ParseNamespace {}")); + gen_CodeOperator parser_operator = gen_parse_operator(gen_txt("s32 operator +(s32 lhs, s32 rhs);")); + gen_CodeOpCast parser_operator_cast = gen_parse_operator_cast(gen_txt("operator s32() const;")); + gen_CodeStruct parser_struct = gen_parse_struct(gen_txt("struct ParseStruct {};")); + gen_CodeTemplate parser_template = gen_parse_template(gen_txt("template struct ParseTemplate {};")); + gen_CodeTypename parser_type = gen_parse_type(gen_txt("const s32*")); + gen_CodeTypedef parser_typedef = gen_parse_typedef(gen_txt("typedef s32 ParseTypedef;")); + gen_CodeUnion parser_union = gen_parse_union(gen_txt("union ParseUnion { s32 value; };")); + gen_CodeUsing parser_using = gen_parse_using(gen_txt("using ParseUsing = s32;")); + gen_CodeVar parser_variable = gen_parse_variable(gen_txt("s32 parse_variable;")); + + expect(parser_class != 0 && parser_class->Type == CT_Class, gen_txt("c11 parse class fixture")); + expect(parser_constructor != 0 && parser_constructor->Type == CT_Constructor, gen_txt("c11 parse constructor fixture")); + expect(parser_define != 0 && parser_define->Type == CT_Preprocess_Define, gen_txt("c11 parse define fixture")); + expect(parser_destructor != 0 && parser_destructor->Type == CT_Destructor_Fwd, gen_txt("c11 parse destructor fixture")); + expect(parser_enum != 0 && parser_enum->Type == CT_Enum, gen_txt("c11 parse enum fixture")); + expect(parser_export_body != 0 && parser_export_body->Type == CT_Export_Body, gen_txt("c11 parse export body fixture")); + expect(parser_extern != 0 && parser_extern->Type == CT_Extern_Linkage, gen_txt("c11 parse extern fixture")); + expect(parser_friend != 0 && parser_friend->Type == CT_Friend, gen_txt("c11 parse friend fixture")); + expect(parser_function != 0 && parser_function->Type == CT_Function_Fwd, gen_txt("c11 parse function fixture")); + expect(parser_global_body != 0 && parser_global_body->Type == CT_Global_Body, gen_txt("c11 parse global body fixture")); + expect(parser_namespace != 0 && parser_namespace->Type == CT_Namespace, gen_txt("c11 parse namespace fixture")); + expect(parser_operator != 0 && parser_operator->Type == CT_Operator_Fwd, gen_txt("c11 parse operator fixture")); + expect(parser_operator_cast != 0 && parser_operator_cast->Type == CT_Operator_Cast_Fwd, gen_txt("c11 parse operator cast fixture")); + expect(parser_struct != 0 && parser_struct->Type == CT_Struct, gen_txt("c11 parse struct fixture")); + expect(parser_template != 0 && parser_template->Type == CT_Template, gen_txt("c11 parse template fixture")); + expect(parser_type != 0 && parser_type->Type == CT_Typename, gen_txt("c11 parse type fixture")); + expect(parser_typedef != 0 && parser_typedef->Type == CT_Typedef, gen_txt("c11 parse typedef fixture")); + expect(parser_union != 0 && parser_union->Type == CT_Union, gen_txt("c11 parse union fixture")); + expect(parser_using != 0 && parser_using->Type == CT_Using, gen_txt("c11 parse using fixture")); + expect(parser_variable != 0 && parser_variable->Type == CT_Variable, gen_txt("c11 parse variable fixture")); + + gen_CodeBody parsed_global_options = gen_parse_global_body(gen_txt("struct ParseGlobalOptions { s32 value; };")); + expect(parsed_global_options != 0, gen_txt("c11 default parse global body options")); + void* default_result = parsed_global_options; + void* default_info_result = ctx.gen_parse_info.result; + expect(default_info_result == default_result, gen_txt("c11 default parse info result")); + expect(ctx.gen_parse_info.lexed.tokens.num > 0, gen_txt("c11 default parse info tokens")); + expect(ctx.gen_parse_info.lexed.messages == 0 && ctx.gen_parse_info.messages == 0, gen_txt("c11 default parse info messages")); + + ParseInfo caller_info = {0}; + gen_CodeBody parsed_explicit_global = gen_parse_global_body(gen_txt("struct ParseExplicitOptions { s32 value; };"), .ctx = & ctx, .info = & caller_info); + expect(parsed_explicit_global != 0, gen_txt("c11 explicit parse global body options")); + void* explicit_result = parsed_explicit_global; + void* explicit_info_result = caller_info.result; + expect(explicit_info_result == explicit_result, gen_txt("c11 explicit parse info result")); + expect(caller_info.lexed.tokens.num > 0, gen_txt("c11 explicit parse info tokens")); + + ParseInfo variable_info = {0}; + gen_CodeVar parsed_option_variable = gen_parse_variable(gen_txt("s32 parsed_option_variable;"), .ctx = & ctx, .info = & variable_info); + expect(parsed_option_variable != 0 && parsed_option_variable->Type == CT_Variable, gen_txt("c11 explicit parse variable options")); + expect(gen_str_contains(parsed_option_variable->Name, gen_txt("parsed_option_variable")), gen_txt("c11 explicit parse variable name")); + void* variable_result = parsed_option_variable; + void* variable_info_result = variable_info.result; + expect(variable_info_result == variable_result, gen_txt("c11 explicit parse variable info result")); + + ParseInfo lexer_info = {0}; + gen_CodeVar parsed_lexer_prefix = gen_parse_variable(gen_txt("s32 parsed_lexer_prefix;@"), .ctx = & ctx, .info = & lexer_info); + expect(parsed_lexer_prefix != 0 && parsed_lexer_prefix->Type == CT_Variable, gen_txt("c11 parse lexer prefix result")); + void* lexer_result = parsed_lexer_prefix; + void* lexer_info_result = lexer_info.result; + expect(lexer_info_result == lexer_result, gen_txt("c11 parse lexer prefix info result")); + expect(lexer_info.lexed.tokens.num > 0, gen_txt("c11 parse lexer prefix retained tokens")); + expect(lexer_info.lexed.messages != 0, gen_txt("c11 parse lexer prefix diagnostic")); + + ParseInfo matrix_info = {0}; + gen_Opts_parse_global_body legacy_parse_options = { .ctx = & ctx, .info = & matrix_info }; + gen_CodeBody matrix_global_body = gen_parse_global_body(gen_txt("struct ParseGlobal { int value; };"), .ctx = legacy_parse_options.ctx, .info = legacy_parse_options.info); + expect(matrix_global_body != 0 && matrix_global_body->Type == CT_Global_Body, gen_txt("c11 global body options matrix")); + expect_parse_info(matrix_global_body, & matrix_info, gen_txt("c11 global body ParseInfo matrix")); + + gen_CodeClass matrix_class = gen_parse_class(gen_txt("class ParseClass {};"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_class != 0 && matrix_class->Type == CT_Class, gen_txt("c11 class options matrix")); + expect_parse_info(matrix_class, & matrix_info, gen_txt("c11 class ParseInfo matrix")); + + gen_CodeConstructor matrix_constructor = gen_parse_constructor(gen_txt("ParseClass() {}"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_constructor != 0 && matrix_constructor->Type == CT_Constructor, gen_txt("c11 constructor options matrix")); + expect_parse_info(matrix_constructor, & matrix_info, gen_txt("c11 constructor ParseInfo matrix")); + + gen_CodeDefine matrix_define = gen_parse_define(gen_txt("#define PARSE_DEFINE 1"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_define != 0 && matrix_define->Type == CT_Preprocess_Define, gen_txt("c11 define options matrix")); + expect_parse_info(matrix_define, & matrix_info, gen_txt("c11 define ParseInfo matrix")); + + gen_CodeDestructor matrix_destructor = gen_parse_destructor(gen_txt("~ParseClass();"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_destructor != 0 && matrix_destructor->Type == CT_Destructor_Fwd, gen_txt("c11 destructor options matrix")); + expect_parse_info(matrix_destructor, & matrix_info, gen_txt("c11 destructor ParseInfo matrix")); + + gen_CodeEnum matrix_enum = gen_parse_enum(gen_txt("enum ParseEnum { ParseValue };"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_enum != 0 && matrix_enum->Type == CT_Enum, gen_txt("c11 enum options matrix")); + expect_parse_info(matrix_enum, & matrix_info, gen_txt("c11 enum ParseInfo matrix")); + + gen_CodeBody matrix_export_body = gen_parse_export_body(gen_txt("{ int export_value; }"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_export_body != 0 && matrix_export_body->Type == CT_Export_Body, gen_txt("c11 export body options matrix")); + expect_parse_info(matrix_export_body, & matrix_info, gen_txt("c11 export body ParseInfo matrix")); + + gen_CodeExtern matrix_extern = gen_parse_extern_link(gen_txt("extern \"C\" { int extern_value; }"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_extern != 0 && matrix_extern->Type == CT_Extern_Linkage, gen_txt("c11 extern options matrix")); + expect_parse_info(matrix_extern, & matrix_info, gen_txt("c11 extern ParseInfo matrix")); + + gen_CodeFriend matrix_friend = gen_parse_friend(gen_txt("friend int parse_friend_function();"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_friend != 0 && matrix_friend->Type == CT_Friend, gen_txt("c11 friend options matrix")); + expect_parse_info(matrix_friend, & matrix_info, gen_txt("c11 friend ParseInfo matrix")); + + gen_CodeFn matrix_function = gen_parse_function(gen_txt("int parse_function();"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_function != 0 && matrix_function->Type == CT_Function_Fwd, gen_txt("c11 function options matrix")); + expect_parse_info(matrix_function, & matrix_info, gen_txt("c11 function ParseInfo matrix")); + + gen_CodeNS matrix_namespace = gen_parse_namespace(gen_txt("namespace ParseNamespace {}"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_namespace != 0 && matrix_namespace->Type == CT_Namespace, gen_txt("c11 namespace options matrix")); + expect_parse_info(matrix_namespace, & matrix_info, gen_txt("c11 namespace ParseInfo matrix")); + + gen_CodeOperator matrix_operator = gen_parse_operator(gen_txt("s32 operator +(s32 lhs, s32 rhs);"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_operator != 0 && matrix_operator->Type == CT_Operator_Fwd, gen_txt("c11 operator options matrix")); + expect_parse_info(matrix_operator, & matrix_info, gen_txt("c11 operator ParseInfo matrix")); + + gen_CodeOpCast matrix_operator_cast = gen_parse_operator_cast(gen_txt("operator s32() const;"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_operator_cast != 0 && matrix_operator_cast->Type == CT_Operator_Cast_Fwd, gen_txt("c11 operator cast options matrix")); + expect_parse_info(matrix_operator_cast, & matrix_info, gen_txt("c11 operator cast ParseInfo matrix")); + + gen_CodeStruct matrix_struct = gen_parse_struct(gen_txt("struct ParseStruct {};"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_struct != 0 && matrix_struct->Type == CT_Struct, gen_txt("c11 struct options matrix")); + expect_parse_info(matrix_struct, & matrix_info, gen_txt("c11 struct ParseInfo matrix")); + + gen_CodeTemplate matrix_template = gen_parse_template(gen_txt("template struct ParseTemplate {};"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_template != 0 && matrix_template->Type == CT_Template, gen_txt("c11 template options matrix")); + expect_parse_info(matrix_template, & matrix_info, gen_txt("c11 template ParseInfo matrix")); + + gen_CodeTypename matrix_type = gen_parse_type(gen_txt("const s32*"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_type != 0 && matrix_type->Type == CT_Typename, gen_txt("c11 type options matrix")); + expect_parse_info(matrix_type, & matrix_info, gen_txt("c11 type ParseInfo matrix")); + + gen_CodeTypedef matrix_typedef = gen_parse_typedef(gen_txt("typedef s32 ParseTypedef;"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_typedef != 0 && matrix_typedef->Type == CT_Typedef, gen_txt("c11 typedef options matrix")); + expect_parse_info(matrix_typedef, & matrix_info, gen_txt("c11 typedef ParseInfo matrix")); + + gen_CodeUnion matrix_union = gen_parse_union(gen_txt("union ParseUnion { s32 value; };"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_union != 0 && matrix_union->Type == CT_Union, gen_txt("c11 union options matrix")); + expect_parse_info(matrix_union, & matrix_info, gen_txt("c11 union ParseInfo matrix")); + + gen_CodeUsing matrix_using = gen_parse_using(gen_txt("using ParseUsing = s32;"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_using != 0 && matrix_using->Type == CT_Using, gen_txt("c11 using options matrix")); + expect_parse_info(matrix_using, & matrix_info, gen_txt("c11 using ParseInfo matrix")); + + gen_CodeVar matrix_variable = gen_parse_variable(gen_txt("s32 parse_variable;"), .ctx = & ctx, .info = & matrix_info); + expect(matrix_variable != 0 && matrix_variable->Type == CT_Variable, gen_txt("c11 variable options matrix")); + expect_parse_info(matrix_variable, & matrix_info, gen_txt("c11 variable ParseInfo matrix")); + + ParseInfo comma_info = {0}; + gen_CodeBody parsed_comma_body = gen_parse_global_body(gen_txt("struct ParseComma { s32 value; };\n,"), .ctx = & ctx, .info = & comma_info); + expect(parsed_comma_body != 0 && parsed_comma_body->Type == CT_Global_Body, gen_txt("c11 comma parser result")); + void* comma_result = parsed_comma_body; + void* comma_info_result = comma_info.result; + expect(comma_info_result == comma_result, gen_txt("c11 comma ParseInfo result")); + expect(comma_info.lexed.tokens.num > 0 && comma_info.lexed.messages == 0, gen_txt("c11 comma retained tokens")); + expect(comma_info.messages != 0, gen_txt("c11 comma parser diagnostic")); + if (comma_info.messages != 0) + expect(gen_str_contains(comma_info.messages->Content, gen_txt("Dangling comma")), gen_txt("c11 comma diagnostic content")); + + ParseInfo recovery_info = {0}; + gen_CodeBody recovered_body = gen_parse_global_body(gen_txt("s32 before; s32 broken(s32 value; s32 after;"), .ctx = &ctx, .info = &recovery_info); + gen_Str recovery_text = {0}; + if (recovered_body != 0) recovery_text = gen_strbuilder_to_str(gen_code_to_strbuilder((gen_Code)recovered_body)); + expect(recovered_body != 0 && recovered_body->Type == CT_Global_Body, gen_txt("c11 file-scope recovery body")); + + void* recovered_result = recovered_body; + expect(recovery_info.result == recovered_result, gen_txt("c11 file-scope recovery ParseInfo result")); + expect(recovery_info.messages != 0, gen_txt("c11 file-scope recovery diagnostic")); + if (recovery_info.messages != 0) { + expect(gen_str_contains(recovery_info.messages->Content, gen_txt("Expected")), gen_txt("c11 file-scope recovery mismatch text")); + expect(recovery_info.messages->Next == 0, gen_txt("c11 file-scope recovery single diagnostic")); + } + expect(gen_str_contains(recovery_text, gen_txt("before")), gen_txt("c11 file-scope recovery keeps preceding member")); + expect(gen_str_contains(recovery_text, gen_txt("after")), gen_txt("c11 file-scope recovery keeps following member")); + gen_Code recovered_member = 0; + if (recovered_body != 0) recovered_member = recovered_body->Front; + gen_Code recovered_before = recovered_member; + expect(recovered_before != 0 && recovered_before->Type == CT_Variable, gen_txt("c11 mismatch recovery keeps preceding member typed")); + if (recovered_before != 0) + expect(gen_str_are_equal(recovered_before->Name, gen_txt("before")), gen_txt("c11 mismatch recovery preceding member name")); + if (recovered_member != 0) recovered_member = recovered_member->Next; + expect(recovered_member != 0 && recovered_member->Type == CT_Untyped, gen_txt("c11 file-scope recovery stores malformed member as untyped")); + if (recovered_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(recovered_member)), gen_txt("s32 broken(s32 value;")), gen_txt("c11 file-scope recovery retains exact mismatch span")); + gen_Code recovered_after = recovered_member != 0 ? recovered_member->Next : 0; + expect(recovered_after != 0 && recovered_after->Type == CT_Variable, gen_txt("c11 file-scope recovery keeps following member typed")); + if (recovered_after != 0) + expect(gen_str_are_equal(recovered_after->Name, gen_txt("after")), gen_txt("c11 file-scope recovery following member name")); + + ParseInfo invalid_member_info = {0}; + gen_CodeBody invalid_member_body = gen_parse_global_body(gen_txt("s32 before; = { 1; 2 }; s32 after;"), .ctx = &ctx, .info = &invalid_member_info); + gen_Str invalid_member_text = {0}; + if (invalid_member_body != 0) + invalid_member_text = gen_strbuilder_to_str(gen_code_to_strbuilder((gen_Code)invalid_member_body)); + expect(invalid_member_body != 0 && invalid_member_body->Type == CT_Global_Body, gen_txt("c11 invalid member recovery body")); + void* invalid_member_result = invalid_member_body; + expect(invalid_member_info.result == invalid_member_result, gen_txt("c11 invalid member recovery ParseInfo result")); + expect(invalid_member_info.messages != 0, gen_txt("c11 invalid member recovery diagnostic")); + if (invalid_member_info.messages != 0) { + expect(gen_str_contains(invalid_member_info.messages->Content, gen_txt("Failed to parse member")), gen_txt("c11 invalid member recovery diagnostic text")); + expect(invalid_member_info.messages->Next == 0, gen_txt("c11 invalid member recovery single diagnostic")); + } + expect(gen_str_contains(invalid_member_text, gen_txt("before")), gen_txt("c11 invalid member recovery keeps preceding member")); + expect(gen_str_contains(invalid_member_text, gen_txt("after")), gen_txt("c11 invalid member recovery keeps following member")); + gen_Code invalid_member_node = 0; + if (invalid_member_body != 0) invalid_member_node = invalid_member_body->Front; + gen_Code invalid_member_before = invalid_member_node; + expect(invalid_member_before != 0 && invalid_member_before->Type == CT_Variable, gen_txt("c11 invalid member keeps preceding member typed")); + if (invalid_member_before != 0) + expect(gen_str_are_equal(invalid_member_before->Name, gen_txt("before")), gen_txt("c11 invalid member preceding member name")); + if (invalid_member_node != 0) invalid_member_node = invalid_member_node->Next; + expect(invalid_member_node != 0 && invalid_member_node->Type == CT_Untyped, gen_txt("c11 invalid member recovery stores member as untyped")); + if (invalid_member_node != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(invalid_member_node)), gen_txt("= { 1; 2 };")), gen_txt("c11 invalid member recovery retains exact source span")); + gen_Code invalid_member_after = invalid_member_node != 0 ? invalid_member_node->Next : 0; + expect(invalid_member_after != 0 && invalid_member_after->Type == CT_Variable, gen_txt("c11 invalid member recovery keeps following member typed")); + if (invalid_member_after != 0) + expect(gen_str_are_equal(invalid_member_after->Name, gen_txt("after")), gen_txt("c11 invalid member recovery following member name")); + + ParseInfo import_recovery_info = {0}; + gen_Opts_parse import_recovery_options = { .ctx = &ctx, .info = &import_recovery_info }; + gen_CodeBody import_recovery_body = gen_parse_global_body(gen_txt("s32 before; import foo; s32 after;"), .ctx = import_recovery_options.ctx, .info = import_recovery_options.info); + gen_Code import_recovery_node = 0; + if (import_recovery_body != 0) import_recovery_node = import_recovery_body->Front; + gen_Code import_recovery_before = import_recovery_node; + expect(import_recovery_before != 0 && import_recovery_before->Type == CT_Variable, gen_txt("c11 import recovery keeps preceding member typed")); + if (import_recovery_before != 0) + expect(gen_str_are_equal(import_recovery_before->Name, gen_txt("before")), gen_txt("c11 import recovery preceding member name")); + if (import_recovery_node != 0) import_recovery_node = import_recovery_node->Next; + expect(import_recovery_body != 0 && import_recovery_body->Type == CT_Global_Body, gen_txt("c11 import recovery body")); + expect(import_recovery_info.messages != 0, gen_txt("c11 import recovery diagnostic")); + if (import_recovery_info.messages != 0) + expect(import_recovery_info.messages->Next == 0, gen_txt("c11 import recovery single diagnostic")); + expect(import_recovery_node != 0 && import_recovery_node->Type == CT_Untyped, gen_txt("c11 import recovery preserves member as untyped")); + if (import_recovery_node != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(import_recovery_node)), gen_txt("import foo;")), gen_txt("c11 import recovery exact source span")); + gen_Code import_recovery_after = import_recovery_node != 0 ? import_recovery_node->Next : 0; + expect(import_recovery_after != 0 && import_recovery_after->Type == CT_Variable, gen_txt("c11 import recovery keeps following member typed")); + if (import_recovery_after != 0) + expect(gen_str_are_equal(import_recovery_after->Name, gen_txt("after")), gen_txt("c11 import recovery following member name")); + + gen_Macro unbounded_macro = { gen_txt("UnboundedRecoveryMacro"), MT_Expression, MF_Null }; + gen_register_macro(unbounded_macro); + ParseInfo macro_recovery_info = {0}; + gen_Opts_parse macro_recovery_options = { .ctx = &ctx, .info = ¯o_recovery_info }; + gen_CodeBody macro_recovery_body = gen_parse_global_body(gen_txt("s32 before; UnboundedRecoveryMacro; s32 after;"), .ctx = macro_recovery_options.ctx, .info = macro_recovery_options.info); + gen_Code macro_recovery_node = 0; + if (macro_recovery_body != 0) macro_recovery_node = macro_recovery_body->Front; + gen_Code macro_recovery_before = macro_recovery_node; + expect(macro_recovery_before != 0 && macro_recovery_before->Type == CT_Variable, gen_txt("c11 macro recovery keeps preceding member typed")); + if (macro_recovery_before != 0) + expect(gen_str_are_equal(macro_recovery_before->Name, gen_txt("before")), gen_txt("c11 macro recovery preceding member name")); + if (macro_recovery_node != 0) macro_recovery_node = macro_recovery_node->Next; + expect(macro_recovery_body != 0 && macro_recovery_body->Type == CT_Global_Body, gen_txt("c11 unbounded macro recovery body")); + expect(macro_recovery_info.messages != 0, gen_txt("c11 unbounded macro recovery diagnostic")); + if (macro_recovery_info.messages != 0) + expect(macro_recovery_info.messages->Next == 0, gen_txt("c11 unbounded macro recovery single diagnostic")); + expect(macro_recovery_node != 0 && macro_recovery_node->Type == CT_Untyped, gen_txt("c11 unbounded macro recovery preserves member as untyped")); + if (macro_recovery_node != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(macro_recovery_node)), gen_txt("UnboundedRecoveryMacro;")), gen_txt("c11 unbounded macro recovery exact source span")); + gen_Code macro_recovery_after = macro_recovery_node != 0 ? macro_recovery_node->Next : 0; + expect(macro_recovery_after != 0 && macro_recovery_after->Type == CT_Variable, gen_txt("c11 unbounded macro recovery keeps following member typed")); + if (macro_recovery_after != 0) + expect(gen_str_are_equal(macro_recovery_after->Name, gen_txt("after")), gen_txt("c11 unbounded macro recovery following member name")); + + ParseInfo eof_recovery_info = {0}; + gen_CodeBody eof_recovery_body = gen_parse_global_body(gen_txt("s32 before; s32 broken(s32 value"), .ctx = &ctx, .info = &eof_recovery_info); + gen_Code eof_recovery_node = 0; + if (eof_recovery_body != 0) eof_recovery_node = eof_recovery_body->Front; + gen_Code eof_recovery_before = eof_recovery_node; + expect(eof_recovery_before != 0 && eof_recovery_before->Type == CT_Variable, gen_txt("c11 EOF recovery keeps preceding member typed")); + if (eof_recovery_before != 0) + expect(gen_str_are_equal(eof_recovery_before->Name, gen_txt("before")), gen_txt("c11 EOF recovery preceding member name")); + if (eof_recovery_node != 0) eof_recovery_node = eof_recovery_node->Next; + expect(eof_recovery_body != 0 && eof_recovery_body->Type == CT_Global_Body, gen_txt("c11 end-of-slice recovery body")); + expect(eof_recovery_info.messages != 0, gen_txt("c11 end-of-slice recovery diagnostic")); + if (eof_recovery_info.messages != 0) + expect(eof_recovery_info.messages->Next == 0, gen_txt("c11 end-of-slice recovery single diagnostic")); + expect(eof_recovery_node != 0 && eof_recovery_node->Type == CT_Untyped, gen_txt("c11 end-of-slice member is untyped")); + if (eof_recovery_node != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(eof_recovery_node)), gen_txt("s32 broken(s32 value")), gen_txt("c11 end-of-slice recovery exact source span")); + if (eof_recovery_node != 0) + expect(eof_recovery_node->Next == 0, gen_txt("c11 end-of-slice recovery has no following node")); + + ParseInfo comma_recovery_info = {0}; + gen_CodeBody comma_recovery_body = gen_parse_global_body(gen_txt("s32 before; , s32 after;"), .ctx = &ctx, .info = &comma_recovery_info); + gen_Code comma_recovery_node = 0; + if (comma_recovery_body != 0) comma_recovery_node = comma_recovery_body->Front; + gen_Code comma_recovery_before = comma_recovery_node; + expect(comma_recovery_before != 0 && comma_recovery_before->Type == CT_Variable, gen_txt("c11 comma recovery keeps preceding member typed")); + if (comma_recovery_before != 0) + expect(gen_str_are_equal(comma_recovery_before->Name, gen_txt("before")), gen_txt("c11 comma recovery preceding member name")); + if (comma_recovery_node != 0) comma_recovery_node = comma_recovery_node->Next; + expect(comma_recovery_body != 0 && comma_recovery_body->Type == CT_Global_Body, gen_txt("c11 comma recovery body")); + expect(comma_recovery_info.messages != 0, gen_txt("c11 comma recovery diagnostic")); + if (comma_recovery_info.messages != 0) { + expect(gen_str_contains(comma_recovery_info.messages->Content, gen_txt("Dangling comma")), gen_txt("c11 comma recovery diagnostic text")); + expect(comma_recovery_info.messages->Next == 0, gen_txt("c11 comma recovery single diagnostic")); + } + expect(comma_recovery_node != 0 && comma_recovery_node->Type == CT_Untyped, gen_txt("c11 comma recovery preserves token as untyped")); + if (comma_recovery_node != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(comma_recovery_node)), gen_txt(",")), gen_txt("c11 comma recovery exact source span")); + gen_Code comma_recovery_after = comma_recovery_node != 0 ? comma_recovery_node->Next : 0; + expect(comma_recovery_after != 0 && comma_recovery_after->Type == CT_Variable, gen_txt("c11 comma recovery keeps following member typed")); + if (comma_recovery_after != 0) + expect(gen_str_are_equal(comma_recovery_after->Name, gen_txt("after")), gen_txt("c11 comma recovery following member name")); + + gen_CodeBody recovery_reuse = gen_parse_global_body(gen_txt("s32 reused_after_recovery;"), .ctx = &ctx, .info = &recovery_info); + gen_Str recovery_reuse_text = {0}; + if (recovery_reuse != 0) + recovery_reuse_text = gen_strbuilder_to_str(gen_code_to_strbuilder((gen_Code)recovery_reuse)); + expect(recovery_reuse != 0 && recovery_reuse->Type == CT_Global_Body, gen_txt("c11 parser context reuse after recovery")); + expect(recovery_info.messages == 0, gen_txt("c11 parser context reuse clears prior diagnostics")); + expect(gen_str_contains(recovery_reuse_text, gen_txt("reused_after_recovery")), gen_txt("c11 parser context reuse parses later input")); + + ParseInfo nested_struct_info = {0}; + gen_Opts_parse nested_struct_options = { .ctx = &ctx, .info = &nested_struct_info }; + gen_CodeStruct nested_struct = gen_parse_struct(gen_txt("struct NestedRecovery { s32 before; s32 broken(s32 value; s32 after; };"), .ctx = nested_struct_options.ctx, .info = nested_struct_options.info); + expect(nested_struct != 0 && nested_struct->Type == CT_Struct, gen_txt("c11 nested struct recovery result")); + void* nested_struct_result = nested_struct; + void* nested_struct_info_result = nested_struct_info.result; + expect(nested_struct_info_result == nested_struct_result, gen_txt("c11 nested struct recovery ParseInfo result")); + expect(nested_struct_info.messages != 0, gen_txt("c11 nested struct recovery diagnostic")); + if (nested_struct_info.messages != 0) + expect(gen_str_contains(nested_struct_info.messages->Content, gen_txt("Expected")), gen_txt("c11 nested struct recovery diagnostic text")); + gen_Code nested_struct_member = nested_struct != 0 && nested_struct->Body != 0 ? nested_struct->Body->Front : 0; + expect(nested_struct_member != 0 && nested_struct_member->Type == CT_Variable, gen_txt("c11 nested struct recovery keeps preceding member typed")); + if (nested_struct_member != 0) + expect(gen_str_are_equal(nested_struct_member->Name, gen_txt("before")), gen_txt("c11 nested struct recovery preceding member name")); + if (nested_struct_member != 0) nested_struct_member = nested_struct_member->Next; + expect(nested_struct_member != 0 && nested_struct_member->Type == CT_Untyped, gen_txt("c11 nested struct recovery retains failed member")); + if (nested_struct_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(nested_struct_member)), gen_txt("s32 broken(s32 value;")), gen_txt("c11 nested struct recovery exact failed span")); + gen_Code nested_struct_after = nested_struct_member != 0 ? nested_struct_member->Next : 0; + expect(nested_struct_after != 0 && nested_struct_after->Type == CT_Variable, gen_txt("c11 nested struct recovery keeps following member typed")); + if (nested_struct_after != 0) + expect(gen_str_are_equal(nested_struct_after->Name, gen_txt("after")), gen_txt("c11 nested struct recovery following member name")); + gen_Str nested_struct_text = {0}; + if (nested_struct != 0) + nested_struct_text = gen_strbuilder_to_str(gen_code_to_strbuilder((gen_Code)nested_struct)); + expect(gen_str_contains(nested_struct_text, gen_txt("s32 after")), gen_txt("c11 nested struct recovery serializes following member")); + + ParseInfo nested_namespace_info = {0}; + gen_Opts_parse nested_namespace_options = { .ctx = &ctx, .info = &nested_namespace_info }; + gen_CodeNS nested_namespace = gen_parse_namespace(gen_txt("namespace NestedRecovery { s32 before; s32 broken(s32 value; s32 after; }"), .ctx = nested_namespace_options.ctx, .info = nested_namespace_options.info); + expect(nested_namespace != 0 && nested_namespace->Type == CT_Namespace, gen_txt("c11 nested namespace recovery result")); + void* nested_namespace_result = nested_namespace; + void* nested_namespace_info_result = nested_namespace_info.result; + expect(nested_namespace_info_result == nested_namespace_result, gen_txt("c11 nested namespace recovery ParseInfo result")); + expect(nested_namespace_info.messages != 0, gen_txt("c11 nested namespace recovery diagnostic")); + if (nested_namespace_info.messages != 0) + expect(gen_str_contains(nested_namespace_info.messages->Content, gen_txt("Expected")), gen_txt("c11 nested namespace recovery diagnostic text")); + gen_Code nested_namespace_member = nested_namespace != 0 && nested_namespace->Body != 0 ? nested_namespace->Body->Front : 0; + expect(nested_namespace_member != 0 && nested_namespace_member->Type == CT_Variable, gen_txt("c11 nested namespace recovery keeps preceding member typed")); + if (nested_namespace_member != 0) + expect(gen_str_are_equal(nested_namespace_member->Name, gen_txt("before")), gen_txt("c11 nested namespace recovery preceding member name")); + if (nested_namespace_member != 0) nested_namespace_member = nested_namespace_member->Next; + expect(nested_namespace_member != 0 && nested_namespace_member->Type == CT_Untyped, gen_txt("c11 nested namespace recovery retains failed member")); + if (nested_namespace_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(nested_namespace_member)), gen_txt("s32 broken(s32 value;")), gen_txt("c11 nested namespace recovery exact failed span")); + gen_Code nested_namespace_after = nested_namespace_member != 0 ? nested_namespace_member->Next : 0; + expect(nested_namespace_after != 0 && nested_namespace_after->Type == CT_Variable, gen_txt("c11 nested namespace recovery keeps following member typed")); + if (nested_namespace_after != 0) + expect(gen_str_are_equal(nested_namespace_after->Name, gen_txt("after")), gen_txt("c11 nested namespace recovery following member name")); + gen_Str nested_namespace_text = {0}; + if (nested_namespace != 0) + nested_namespace_text = gen_strbuilder_to_str(gen_code_to_strbuilder((gen_Code)nested_namespace)); + expect(gen_str_contains(nested_namespace_text, gen_txt("s32 after")), gen_txt("c11 nested namespace recovery serializes following member")); + + ParseInfo nested_brace_info = {0}; + gen_Opts_parse nested_brace_options = { .ctx = &ctx, .info = &nested_brace_info }; + gen_CodeStruct nested_brace_struct = gen_parse_struct(gen_txt("struct NestedBraceRecovery { s32 before; s32 broken(s32 { 1; s32 after; };"), .ctx = nested_brace_options.ctx, .info = nested_brace_options.info); + expect(nested_brace_struct != 0 && nested_brace_struct->Type == CT_Struct, gen_txt("c11 nested brace recovery result")); + void* nested_brace_result = nested_brace_struct; + void* nested_brace_info_result = nested_brace_info.result; + expect(nested_brace_info_result == nested_brace_result, gen_txt("c11 nested brace recovery ParseInfo result")); + expect(nested_brace_info.messages != 0, gen_txt("c11 nested brace recovery diagnostic")); + gen_Code nested_brace_member = nested_brace_struct != 0 && nested_brace_struct->Body != 0 ? nested_brace_struct->Body->Front : 0; + expect(nested_brace_member != 0 && nested_brace_member->Type == CT_Variable, gen_txt("c11 nested brace recovery keeps preceding member typed")); + if (nested_brace_member != 0) nested_brace_member = nested_brace_member->Next; + expect(nested_brace_member != 0 && nested_brace_member->Type == CT_Untyped, gen_txt("c11 nested brace recovery retains failed member")); + if (nested_brace_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(nested_brace_member)), gen_txt("s32 broken(s32 { 1;")), gen_txt("c11 nested brace recovery exact failed span")); + gen_Code nested_brace_after = nested_brace_member != 0 ? nested_brace_member->Next : 0; + expect(nested_brace_after != 0 && nested_brace_after->Type == CT_Variable, gen_txt("c11 nested brace recovery keeps following member typed")); + if (nested_brace_after != 0) + expect(gen_str_are_equal(nested_brace_after->Name, gen_txt("after")), gen_txt("c11 nested brace recovery following member name")); + gen_Str nested_brace_text = {0}; + if (nested_brace_struct != 0) + nested_brace_text = gen_strbuilder_to_str(gen_code_to_strbuilder((gen_Code)nested_brace_struct)); + expect(gen_str_contains(nested_brace_text, gen_txt("s32 after")), gen_txt("c11 nested brace recovery serializes following member")); + + ParseInfo nested_namespace_brace_info = {0}; + gen_Opts_parse nested_namespace_brace_options = { .ctx = &ctx, .info = &nested_namespace_brace_info }; + gen_CodeNS nested_namespace_brace = gen_parse_namespace(gen_txt("namespace NestedBraceRecovery { s32 before; s32 broken(s32 { 1; s32 after; }"), .ctx = nested_namespace_brace_options.ctx, .info = nested_namespace_brace_options.info); + expect(nested_namespace_brace != 0 && nested_namespace_brace->Type == CT_Namespace, gen_txt("c11 nested namespace brace recovery result")); + void* nested_namespace_brace_result = nested_namespace_brace; + void* nested_namespace_brace_info_result = nested_namespace_brace_info.result; + expect(nested_namespace_brace_info_result == nested_namespace_brace_result, gen_txt("c11 nested namespace brace recovery ParseInfo result")); + gen_Code nested_namespace_brace_member = nested_namespace_brace != 0 && nested_namespace_brace->Body != 0 ? nested_namespace_brace->Body->Front : 0; + expect(nested_namespace_brace_member != 0 && nested_namespace_brace_member->Type == CT_Variable, gen_txt("c11 nested namespace brace recovery keeps preceding member typed")); + if (nested_namespace_brace_member != 0) nested_namespace_brace_member = nested_namespace_brace_member->Next; + expect(nested_namespace_brace_member != 0 && nested_namespace_brace_member->Type == CT_Untyped, gen_txt("c11 nested namespace brace recovery retains failed member")); + if (nested_namespace_brace_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(nested_namespace_brace_member)), gen_txt("s32 broken(s32 { 1;")), gen_txt("c11 nested namespace brace recovery exact failed span")); + gen_Code nested_namespace_brace_after = nested_namespace_brace_member != 0 ? nested_namespace_brace_member->Next : 0; + expect(nested_namespace_brace_after != 0 && nested_namespace_brace_after->Type == CT_Variable, gen_txt("c11 nested namespace brace recovery keeps following member typed")); + if (nested_namespace_brace_after != 0) + expect(gen_str_are_equal(nested_namespace_brace_after->Name, gen_txt("after")), gen_txt("c11 nested namespace brace recovery following member name")); + gen_Str nested_namespace_brace_text = {0}; + if (nested_namespace_brace != 0) + nested_namespace_brace_text = gen_strbuilder_to_str(gen_code_to_strbuilder((gen_Code)nested_namespace_brace)); + expect(gen_str_contains(nested_namespace_brace_text, gen_txt("s32 after")), gen_txt("c11 nested namespace brace recovery serializes following member")); + + ParseInfo nested_brace_tail_info = {0}; + gen_Opts_parse nested_brace_tail_options = { .ctx = &ctx, .info = &nested_brace_tail_info }; + gen_CodeStruct nested_brace_tail_struct = gen_parse_struct(gen_txt("struct NestedBraceTailRecovery { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; };"), .ctx = nested_brace_tail_options.ctx, .info = nested_brace_tail_options.info); + expect(nested_brace_tail_struct != 0 && nested_brace_tail_struct->Type == CT_Struct, gen_txt("c11 nested brace tail recovery result")); + gen_Code nested_brace_tail_member = nested_brace_tail_struct != 0 && nested_brace_tail_struct->Body != 0 ? nested_brace_tail_struct->Body->Front : 0; + if (nested_brace_tail_member != 0) nested_brace_tail_member = nested_brace_tail_member->Next; + expect(nested_brace_tail_member != 0 && nested_brace_tail_member->Type == CT_Untyped, gen_txt("c11 nested brace tail recovery retains failed member")); + if (nested_brace_tail_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(nested_brace_tail_member)), gen_txt("s32 broken(s32 { 1; 2 } foo{ 3;")), gen_txt("c11 nested brace tail recovery exact failed span")); + gen_Code nested_brace_tail_after = nested_brace_tail_member != 0 ? nested_brace_tail_member->Next : 0; + expect(nested_brace_tail_after != 0 && nested_brace_tail_after->Type == CT_Variable, gen_txt("c11 nested brace tail recovery keeps following member typed")); + if (nested_brace_tail_after != 0) + expect(gen_str_are_equal(nested_brace_tail_after->Name, gen_txt("after")), gen_txt("c11 nested brace tail recovery following member name")); + + ParseInfo nested_namespace_tail_info = {0}; + gen_Opts_parse nested_namespace_tail_options = { .ctx = &ctx, .info = &nested_namespace_tail_info }; + gen_CodeNS nested_namespace_tail = gen_parse_namespace(gen_txt("namespace NestedBraceTailRecovery { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; }"), .ctx = nested_namespace_tail_options.ctx, .info = nested_namespace_tail_options.info); + expect(nested_namespace_tail != 0 && nested_namespace_tail->Type == CT_Namespace, gen_txt("c11 nested namespace brace tail recovery result")); + void* nested_namespace_tail_result = nested_namespace_tail; + void* nested_namespace_tail_info_result = nested_namespace_tail_info.result; + expect(nested_namespace_tail_info_result == nested_namespace_tail_result, gen_txt("c11 nested namespace brace tail recovery ParseInfo result")); + gen_Code nested_namespace_tail_member = nested_namespace_tail != 0 && nested_namespace_tail->Body != 0 ? nested_namespace_tail->Body->Front : 0; + if (nested_namespace_tail_member != 0) nested_namespace_tail_member = nested_namespace_tail_member->Next; + expect(nested_namespace_tail_member != 0 && nested_namespace_tail_member->Type == CT_Untyped, gen_txt("c11 nested namespace brace tail recovery retains failed member")); + if (nested_namespace_tail_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(nested_namespace_tail_member)), gen_txt("s32 broken(s32 { 1; 2 } foo{ 3;")), gen_txt("c11 nested namespace brace tail recovery exact failed span")); + gen_Code nested_namespace_tail_after = nested_namespace_tail_member != 0 ? nested_namespace_tail_member->Next : 0; + expect(nested_namespace_tail_after != 0 && nested_namespace_tail_after->Type == CT_Variable, gen_txt("c11 nested namespace brace tail recovery keeps following member typed")); + if (nested_namespace_tail_after != 0) + expect(gen_str_are_equal(nested_namespace_tail_after->Name, gen_txt("after")), gen_txt("c11 nested namespace brace tail recovery following member name")); + + ParseInfo nested_inner_close_info = {0}; + gen_Opts_parse nested_inner_close_options = { .ctx = &ctx, .info = &nested_inner_close_info }; + gen_CodeStruct nested_inner_close_struct = gen_parse_struct(gen_txt("struct NestedInnerCloseRecovery { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; struct Inner { s32 x; }; s32 tail; };"), .ctx = nested_inner_close_options.ctx, .info = nested_inner_close_options.info); + expect(nested_inner_close_struct != 0 && nested_inner_close_struct->Type == CT_Struct, gen_txt("c11 nested inner-close recovery result")); + gen_Code nested_inner_close_member = nested_inner_close_struct != 0 && nested_inner_close_struct->Body != 0 ? nested_inner_close_struct->Body->Front : 0; + if (nested_inner_close_member != 0) nested_inner_close_member = nested_inner_close_member->Next; + expect(nested_inner_close_member != 0 && nested_inner_close_member->Type == CT_Untyped, gen_txt("c11 nested inner-close recovery retains failed member")); + if (nested_inner_close_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(nested_inner_close_member)), gen_txt("s32 broken(s32 { 1; 2 } foo{ 3;")), gen_txt("c11 nested inner-close recovery exact failed span")); + gen_Code nested_inner_close_after = nested_inner_close_member != 0 ? nested_inner_close_member->Next : 0; + expect(nested_inner_close_after != 0 && nested_inner_close_after->Type == CT_Variable, gen_txt("c11 nested inner-close recovery keeps following member typed")); + if (nested_inner_close_after != 0) + expect(gen_str_are_equal(nested_inner_close_after->Name, gen_txt("after")), gen_txt("c11 nested inner-close recovery following member name")); + gen_Code nested_inner_close_inner = nested_inner_close_after != 0 ? nested_inner_close_after->Next : 0; + expect(nested_inner_close_inner != 0 && nested_inner_close_inner->Type == CT_Struct, gen_txt("c11 nested inner-close recovery parses nested struct")); + gen_Code nested_inner_close_tail = nested_inner_close_inner != 0 ? nested_inner_close_inner->Next : 0; + expect(nested_inner_close_tail != 0 && nested_inner_close_tail->Type == CT_Variable, gen_txt("c11 nested inner-close recovery parses trailing member")); + + ParseInfo inner_before_sync_info = {0}; + gen_Opts_parse inner_before_sync_options = { .ctx = &ctx, .info = &inner_before_sync_info }; + gen_CodeStruct inner_before_sync = gen_parse_struct(gen_txt("struct NestedInnerBeforeSync { s32 before; s32 broken(foo{ struct Inner { s32 x; }; s32 after; };"), .ctx = inner_before_sync_options.ctx, .info = inner_before_sync_options.info); + expect(inner_before_sync != 0 && inner_before_sync->Type == CT_Struct, gen_txt("c11 inner-before-sync recovery result")); + gen_Code inner_before_sync_member = inner_before_sync != 0 && inner_before_sync->Body != 0 ? inner_before_sync->Body->Front : 0; + expect(inner_before_sync_member != 0 && inner_before_sync_member->Type == CT_Variable, gen_txt("c11 inner-before-sync recovery keeps preceding member typed")); + if (inner_before_sync_member != 0) inner_before_sync_member = inner_before_sync_member->Next; + expect(inner_before_sync_member != 0 && inner_before_sync_member->Type == CT_Untyped, gen_txt("c11 inner-before-sync recovery retains failed member")); + if (inner_before_sync_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(inner_before_sync_member)), gen_txt("s32 broken(foo{ struct Inner { s32 x; };")), gen_txt("c11 inner-before-sync recovery exact failed span")); + gen_Code inner_before_sync_after = inner_before_sync_member != 0 ? inner_before_sync_member->Next : 0; + expect(inner_before_sync_after != 0 && inner_before_sync_after->Type == CT_Variable, gen_txt("c11 inner-before-sync recovery keeps following member typed")); + if (inner_before_sync_after != 0) + expect(gen_str_are_equal(inner_before_sync_after->Name, gen_txt("after")), gen_txt("c11 inner-before-sync recovery following member name")); + + ParseInfo global_namespace_recovery_info = {0}; + gen_Opts_parse global_namespace_recovery_options = { .ctx = &ctx, .info = &global_namespace_recovery_info }; + gen_CodeBody global_namespace_recovery = gen_parse_global_body(gen_txt("namespace NestedScopeInGlobal { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; } struct Later { s32 value; };"), .ctx = global_namespace_recovery_options.ctx, .info = global_namespace_recovery_options.info); + expect(global_namespace_recovery != 0 && global_namespace_recovery->Type == CT_Global_Body, gen_txt("c11 global namespace recovery body")); + void* global_namespace_recovery_result = global_namespace_recovery; + void* global_namespace_recovery_info_result = global_namespace_recovery_info.result; + expect(global_namespace_recovery_info_result == global_namespace_recovery_result, gen_txt("c11 global namespace recovery ParseInfo result")); + gen_Code global_namespace_node = global_namespace_recovery != 0 ? global_namespace_recovery->Front : 0; + expect(global_namespace_node != 0 && global_namespace_node->Type == CT_Namespace, gen_txt("c11 global namespace recovery preserves namespace")); + gen_Code global_namespace_member = global_namespace_node != 0 && global_namespace_node->Body != 0 ? global_namespace_node->Body->Front : 0; + if (global_namespace_member != 0) global_namespace_member = global_namespace_member->Next; + expect(global_namespace_member != 0 && global_namespace_member->Type == CT_Untyped, gen_txt("c11 global namespace recovery retains failed member")); + if (global_namespace_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(global_namespace_member)), gen_txt("s32 broken(s32 { 1; 2 } foo{ 3;")), gen_txt("c11 global namespace recovery exact failed span")); + gen_Code global_namespace_after = global_namespace_member != 0 ? global_namespace_member->Next : 0; + expect(global_namespace_after != 0 && global_namespace_after->Type == CT_Variable, gen_txt("c11 global namespace recovery keeps following member typed")); + if (global_namespace_after != 0) + expect(gen_str_are_equal(global_namespace_after->Name, gen_txt("after")), gen_txt("c11 global namespace recovery following member name")); + gen_Code global_later_struct = global_namespace_node != 0 ? global_namespace_node->Next : 0; + expect(global_later_struct != 0 && global_later_struct->Type == CT_Struct, gen_txt("c11 global namespace recovery preserves following global struct")); + if (global_later_struct != 0) + expect(gen_str_are_equal(global_later_struct->Name, gen_txt("Later")), gen_txt("c11 global namespace recovery following global name")); + + ParseInfo global_struct_recovery_info = {0}; + gen_Opts_parse global_struct_recovery_options = { .ctx = &ctx, .info = &global_struct_recovery_info }; + gen_CodeBody global_struct_recovery = gen_parse_global_body(gen_txt("struct GlobalNestedRecovery { s32 before; s32 broken(s32 value; s32 after; }; struct LaterGlobal { s32 value; };"), .ctx = global_struct_recovery_options.ctx, .info = global_struct_recovery_options.info); + expect(global_struct_recovery != 0 && global_struct_recovery->Type == CT_Global_Body, gen_txt("c11 global struct recovery body")); + gen_Code global_struct_node = global_struct_recovery != 0 ? global_struct_recovery->Front : 0; + gen_Code global_struct_before = global_struct_node != 0 && global_struct_node->Body != 0 ? global_struct_node->Body->Front : 0; + gen_Code global_struct_member = global_struct_before != 0 ? global_struct_before->Next : 0; + gen_Code global_struct_after = global_struct_member != 0 ? global_struct_member->Next : 0; + gen_Code global_later_class = global_struct_node != 0 ? global_struct_node->Next : 0; + expect(global_struct_node != 0 && global_struct_node->Type == CT_Struct, gen_txt("c11 global struct recovery preserves partial struct")); + expect(global_struct_member != 0 && global_struct_member->Type == CT_Untyped, gen_txt("c11 global struct recovery retains failed member")); + if (global_struct_member != 0) + expect(gen_str_are_equal(gen_strbuilder_to_str(gen_code_to_strbuilder(global_struct_member)), gen_txt("s32 broken(s32 value;")), gen_txt("c11 global struct recovery exact failed span")); + expect(global_struct_after != 0 && global_struct_after->Type == CT_Variable, gen_txt("c11 global struct recovery keeps following member")); + if (global_struct_after != 0) + expect(gen_str_are_equal(global_struct_after->Name, gen_txt("after")), gen_txt("c11 global struct recovery following member name")); + expect(global_later_class != 0 && global_later_class->Type == CT_Struct, gen_txt("c11 global struct recovery keeps following global struct")); + + ParseInfo nested_owner_info = {0}; + gen_Opts_parse nested_owner_options = { .ctx = & ctx, .info = & nested_owner_info }; + gen_CodeStruct nested_owner = gen_parse_struct(gen_txt("struct NestedOwner { s32 before; struct InnerRecovery { s32 inner_before; s32 broken(s32 value; s32 inner_after; }; s32 after; };"), .ctx = nested_owner_options.ctx, .info = nested_owner_options.info); + expect(nested_owner != 0 && nested_owner->Type == CT_Struct, gen_txt("c11 nested owner recovery result")); + void* nested_owner_result = nested_owner; + void* nested_owner_info_result = nested_owner_info.result; + expect(nested_owner_info_result == nested_owner_result, gen_txt("c11 nested owner recovery ParseInfo result")); + expect(nested_owner_info.messages != 0, gen_txt("c11 nested owner recovery diagnostic")); + + gen_Code nested_owner_before = nested_owner != 0 && nested_owner->Body != 0 ? nested_owner->Body->Front : 0; + gen_Code nested_owner_inner = nested_owner_before != 0 ? nested_owner_before->Next : 0; + gen_Code nested_owner_after = nested_owner_inner != 0 ? nested_owner_inner->Next : 0; + gen_Code nested_owner_inner_before = nested_owner_inner != 0 && nested_owner_inner->Body != 0 ? nested_owner_inner->Body->Front : 0; + gen_Code nested_owner_inner_failed = nested_owner_inner_before != 0 ? nested_owner_inner_before->Next : 0; + gen_Code nested_owner_inner_after = nested_owner_inner_failed != 0 ? nested_owner_inner_failed->Next : 0; + expect(nested_owner_before != 0 && nested_owner_before->Type == CT_Variable, gen_txt("c11 nested owner keeps preceding member typed")); + expect(nested_owner_inner != 0 && nested_owner_inner->Type == CT_Struct, gen_txt("c11 nested owner retains inner struct AST")); + expect(nested_owner_inner_before != 0 && nested_owner_inner_before->Type == CT_Variable, gen_txt("c11 nested inner recovery keeps preceding member typed")); + expect(nested_owner_inner_failed != 0 && nested_owner_inner_failed->Type == CT_Untyped, gen_txt("c11 nested inner recovery retains failed member")); + expect(nested_owner_inner_after != 0 && nested_owner_inner_after->Type == CT_Variable, gen_txt("c11 nested inner recovery keeps following member typed")); + expect(nested_owner_after != 0 && nested_owner_after->Type == CT_Variable, gen_txt("c11 nested owner keeps following member typed")); + #if ! GEN_BUILD_DEBUG - gen_CodeVar wrong_array_value = gen_def_variable(gen_def_type(name(u32)), name(invalid_array_expr)); + gen_CodeVar wrong_array_value = gen_def_variable(gen_def_type(name(u32)), name(invalid_array_expr)); gen_CodeTypename invalid_array_type = gen_def_type(name(InvalidArrayOption), .gen_array_expr = (gen_Code)wrong_array_value); expect((gen_Code)invalid_array_type == gen_Code_Invalid, gen_txt("c11 wrong type invalid code")); - gen_CodeBody missing_union_body = 0; - gen_CodeUnion missing_union = gen_def_union(name(MissingUnionBody), missing_union_body); + gen_CodeBody missing_union_body = 0; + gen_CodeUnion missing_union = gen_def_union(name(MissingUnionBody), missing_union_body); expect((gen_Code)missing_union == gen_Code_Invalid, gen_txt("c11 missing body invalid code")); #endif - gen_deinit(&ctx); + gen_deinit(& ctx); if (g_failures != 0) { gen_log_fmt("c11 baseline smoke failed: %d\n", g_failures); return 1; } gen_log_fmt("c11 baseline smoke passed\n"); return 0; diff --git a/test/cpp_library/test.cpp b/test/cpp_library/test.cpp index c06801b..2c78e86 100644 --- a/test/cpp_library/test.cpp +++ b/test/cpp_library/test.cpp @@ -30,6 +30,14 @@ internal void expect_variable(gen::CodeVar var, gen::Str label) { } } +internal void expect_parse_result(gen::Code result, gen::ParseInfo* info, gen::CodeType expected, gen::Str label) { + expect(result.ast != nullptr, label); + if (result.ast != nullptr) expect(result->Type == expected, label); + expect(info->result.ast == result.ast, label); + expect(info->lexed.tokens.num > 0, label); + expect(info->lexed.messages == nullptr && info->messages == nullptr, label); +} + int main() { using namespace gen; @@ -401,10 +409,10 @@ int main() CodeTypename operator_type = def_type(name(OperatorOptionType)); CodeTypename operator_return = def_type(name(OperatorOptionReturn)); - CodeParams operator_lhs = def_param(operator_type, name(operator_option_lhs)); - CodeParams operator_rhs = def_param(operator_type, name(operator_option_rhs)); - CodeParams operator_params = def_params(args(operator_lhs, operator_rhs)); - CodeBody operator_body = def_function_body(args(untyped_str(txt("return operator_option_lhs;")))); + CodeParams operator_lhs = def_param(operator_type, name(operator_option_lhs)); + CodeParams operator_rhs = def_param(operator_type, name(operator_option_rhs)); + CodeParams operator_params = def_params(args(operator_lhs, operator_rhs)); + CodeBody operator_body = def_function_body(args(untyped_str(txt("return operator_option_lhs;")))); Opts_def_operator operator_options = {}; operator_options.params = operator_params; operator_options.ret_type = operator_return; @@ -452,19 +460,491 @@ int main() expect(str_contains(cast_text, txt("return 0")), txt("cpp operator cast body serialization")); } - Code untyped = code_str(char const* baseline_message = "GENCPP_BASELINE";); expect(untyped.ast != nullptr, txt("cpp untyped")); - Str untyped_text = strbuilder_to_str(code_to_strbuilder(untyped)); expect_fragments(untyped_text, txt("cpp untyped content")); - CodeVar parsed_untyped = parse_variable(untyped_text); expect_variable(parsed_untyped, txt("cpp untyped reparsed")); + Code untyped = code_str(char const* baseline_message = "GENCPP_BASELINE";); + Str untyped_text = strbuilder_to_str(code_to_strbuilder(untyped)); + CodeVar parsed_untyped = parse_variable(untyped_text); + expect(untyped.ast != nullptr, txt("cpp untyped")); + expect_fragments(untyped_text, txt("cpp untyped content")); + expect_variable(parsed_untyped, txt("cpp untyped reparsed")); CodeVar parsed = parse_variable(code(char const* baseline_message = "GENCPP_BASELINE"; )); expect_variable(parsed, txt("cpp parsed")); expect_fragments(strbuilder_to_str(code_to_strbuilder((Code)parsed)), txt("cpp parsed serialized")); + CodeClass parser_class = parse_class(txt("class ParseClass {};")); + CodeConstructor parser_constructor = parse_constructor(txt("ParseClass() {}")); + CodeDefine parser_define = parse_define(txt("#define PARSE_DEFINE 1")); + CodeDestructor parser_destructor = parse_destructor(txt("~ParseClass();")); + CodeEnum parser_enum = parse_enum(txt("enum ParseEnum { ParseValue };")); + CodeBody parser_export_body = parse_export_body(txt("{ int export_value; }")); + CodeExtern parser_extern = parse_extern_link(txt("extern \"C\" { int extern_value; }")); + CodeFriend parser_friend = parse_friend(txt("friend int parse_friend_function();")); + CodeFn parser_function = parse_function(txt("int parse_function();")); + CodeBody parser_global_body = parse_global_body(txt("struct ParseGlobal { int value; };")); + CodeNS parser_namespace = parse_namespace(txt("namespace ParseNamespace {}")); + CodeOperator parser_operator = parse_operator(txt("s32 operator +(s32 lhs, s32 rhs);")); + CodeOpCast parser_operator_cast = parse_operator_cast(txt("operator s32() const;")); + CodeStruct parser_struct = parse_struct(txt("struct ParseStruct {};")); + CodeTemplate parser_template = parse_template(txt("template struct ParseTemplate {};")); + CodeTypename parser_type = parse_type(txt("const s32*")); + CodeTypedef parser_typedef = parse_typedef(txt("typedef s32 ParseTypedef;")); + CodeUnion parser_union = parse_union(txt("union ParseUnion { s32 value; };")); + CodeUsing parser_using = parse_using(txt("using ParseUsing = s32;")); + CodeVar parser_variable = parse_variable(txt("s32 parse_variable;")); + + expect(parser_class.ast != nullptr && parser_class->Type == CT_Class, txt("cpp parse class fixture")); + expect(parser_constructor.ast != nullptr && parser_constructor->Type == CT_Constructor, txt("cpp parse constructor fixture")); + expect(parser_define.ast != nullptr && parser_define->Type == CT_Preprocess_Define, txt("cpp parse define fixture")); + expect(parser_destructor.ast != nullptr && parser_destructor->Type == CT_Destructor_Fwd, txt("cpp parse destructor fixture")); + expect(parser_enum.ast != nullptr && parser_enum->Type == CT_Enum, txt("cpp parse enum fixture")); + expect(parser_export_body.ast != nullptr && parser_export_body->Type == CT_Export_Body, txt("cpp parse export body fixture")); + expect(parser_extern.ast != nullptr && parser_extern->Type == CT_Extern_Linkage, txt("cpp parse extern fixture")); + expect(parser_friend.ast != nullptr && parser_friend->Type == CT_Friend, txt("cpp parse friend fixture")); + expect(parser_function.ast != nullptr && parser_function->Type == CT_Function_Fwd, txt("cpp parse function fixture")); + expect(parser_global_body.ast != nullptr && parser_global_body->Type == CT_Global_Body, txt("cpp parse global body fixture")); + expect(parser_namespace.ast != nullptr && parser_namespace->Type == CT_Namespace, txt("cpp parse namespace fixture")); + expect(parser_operator.ast != nullptr && parser_operator->Type == CT_Operator_Fwd, txt("cpp parse operator fixture")); + expect(parser_operator_cast.ast != nullptr && parser_operator_cast->Type == CT_Operator_Cast_Fwd, txt("cpp parse operator cast fixture")); + expect(parser_struct.ast != nullptr && parser_struct->Type == CT_Struct, txt("cpp parse struct fixture")); + expect(parser_template.ast != nullptr && parser_template->Type == CT_Template, txt("cpp parse template fixture")); + expect(parser_type.ast != nullptr && parser_type->Type == CT_Typename, txt("cpp parse type fixture")); + expect(parser_typedef.ast != nullptr && parser_typedef->Type == CT_Typedef, txt("cpp parse typedef fixture")); + expect(parser_union.ast != nullptr && parser_union->Type == CT_Union, txt("cpp parse union fixture")); + expect(parser_using.ast != nullptr && parser_using->Type == CT_Using, txt("cpp parse using fixture")); + expect(parser_variable.ast != nullptr && parser_variable->Type == CT_Variable, txt("cpp parse variable fixture")); + + CodeBody parsed_global_options = parse_global_body(txt("struct ParseGlobalOptions { s32 value; };")); + Code global_options_code = parsed_global_options; + expect(parsed_global_options.ast != nullptr, txt("cpp parse global body options")); + expect(ctx.parse_info.result.ast == global_options_code.ast, txt("cpp default parse info result")); + expect(ctx.parse_info.lexed.tokens.num > 0, txt("cpp default parse info tokens")); + expect(ctx.parse_info.lexed.messages == nullptr && ctx.parse_info.messages == nullptr, txt("cpp default parse info messages")); + + ParseInfo caller_info = {}; + Opts_parse parse_options = { & ctx, & caller_info }; + CodeVar parsed_option_variable = parse_variable(txt("s32 parsed_option_variable;"), parse_options); + Code parsed_option_code = parsed_option_variable; + expect(parsed_option_variable.ast != nullptr && parsed_option_variable->Type == CT_Variable, txt("cpp explicit parse variable options")); + expect(str_contains(parsed_option_variable->Name, txt("parsed_option_variable")), txt("cpp explicit parse variable name")); + expect(caller_info.result.ast == parsed_option_code.ast, txt("cpp explicit parse info result")); + expect(caller_info.lexed.tokens.num > 0, txt("cpp explicit parse info tokens")); + + ParseInfo lexer_info = {}; + Opts_parse lexer_options = { & ctx, & lexer_info }; + CodeVar parsed_lexer_prefix = parse_variable(txt("s32 parsed_lexer_prefix;@"), lexer_options); + Code parsed_lexer_code = parsed_lexer_prefix; + expect(parsed_lexer_prefix.ast != nullptr && parsed_lexer_prefix->Type == CT_Variable, txt("cpp parse lexer prefix result")); + expect(lexer_info.result.ast == parsed_lexer_code.ast, txt("cpp parse lexer prefix info result")); + expect(lexer_info.lexed.tokens.num > 0, txt("cpp parse lexer prefix retained tokens")); + expect(lexer_info.lexed.messages != nullptr, txt("cpp parse lexer prefix diagnostic")); + + expect_parse_result(parse_class (txt("class ParseClass {};"), parse_options), & caller_info, CT_Class, txt("cpp class options matrix")); + expect_parse_result(parse_constructor (txt("ParseClass() {}"), parse_options), & caller_info, CT_Constructor, txt("cpp constructor options matrix")); + expect_parse_result(parse_define (txt("#define PARSE_DEFINE 1"), parse_options), & caller_info, CT_Preprocess_Define, txt("cpp define options matrix")); + expect_parse_result(parse_destructor (txt("~ParseClass();"), parse_options), & caller_info, CT_Destructor_Fwd, txt("cpp destructor options matrix")); + expect_parse_result(parse_enum (txt("enum ParseEnum { ParseValue };"), parse_options), & caller_info, CT_Enum, txt("cpp enum options matrix")); + expect_parse_result(parse_export_body (txt("{ int export_value; }"), parse_options), & caller_info, CT_Export_Body, txt("cpp export body options matrix")); + expect_parse_result(parse_extern_link (txt("extern \"C\" { int extern_value; }"), parse_options), & caller_info, CT_Extern_Linkage, txt("cpp extern options matrix")); + expect_parse_result(parse_friend (txt("friend int parse_friend_function();"), parse_options), & caller_info, CT_Friend, txt("cpp friend options matrix")); + expect_parse_result(parse_function (txt("int parse_function();"), parse_options), & caller_info, CT_Function_Fwd, txt("cpp function options matrix")); + expect_parse_result(parse_global_body (txt("struct ParseGlobal { int value; };"), parse_options), & caller_info, CT_Global_Body, txt("cpp global body options matrix")); + expect_parse_result(parse_namespace (txt("namespace ParseNamespace {}"), parse_options), & caller_info, CT_Namespace, txt("cpp namespace options matrix")); + expect_parse_result(parse_operator (txt("s32 operator +(s32 lhs, s32 rhs);"), parse_options), & caller_info, CT_Operator_Fwd, txt("cpp operator options matrix")); + expect_parse_result(parse_operator_cast(txt("operator s32() const;"), parse_options), & caller_info, CT_Operator_Cast_Fwd, txt("cpp operator cast options matrix")); + expect_parse_result(parse_struct (txt("struct ParseStruct {};"), parse_options), & caller_info, CT_Struct, txt("cpp struct options matrix")); + expect_parse_result(parse_template (txt("template struct ParseTemplate {};"), parse_options), & caller_info, CT_Template, txt("cpp template options matrix")); + expect_parse_result(parse_type (txt("const s32*"), parse_options), & caller_info, CT_Typename, txt("cpp type options matrix")); + expect_parse_result(parse_typedef (txt("typedef s32 ParseTypedef;"), parse_options), & caller_info, CT_Typedef, txt("cpp typedef options matrix")); + expect_parse_result(parse_union (txt("union ParseUnion { s32 value; };"), parse_options), & caller_info, CT_Union, txt("cpp union options matrix")); + expect_parse_result(parse_using (txt("using ParseUsing = s32;"), parse_options), & caller_info, CT_Using, txt("cpp using options matrix")); + expect_parse_result(parse_variable (txt("s32 parse_variable;"), parse_options), & caller_info, CT_Variable, txt("cpp variable options matrix")); + + ParseInfo comma_info = {}; + Opts_parse comma_options = { &ctx, &comma_info }; + CodeBody parsed_comma_body = parse_global_body(txt("struct ParseComma { s32 value; };\n,"), comma_options); + Code parsed_comma_code = parsed_comma_body; + expect(parsed_comma_body.ast != nullptr && parsed_comma_body->Type == CT_Global_Body, txt("cpp comma parser result")); + expect(comma_info.result.ast == parsed_comma_code.ast, txt("cpp comma ParseInfo result")); + expect(comma_info.lexed.tokens.num > 0 && comma_info.lexed.messages == nullptr, txt("cpp comma retained tokens")); + expect(comma_info.messages != nullptr, txt("cpp comma parser diagnostic")); + if (comma_info.messages != nullptr) + expect(str_contains(comma_info.messages->Content, txt("Dangling comma")), txt("cpp comma diagnostic content")); + + ParseInfo recovery_info = {}; + Opts_parse recovery_options = { &ctx, &recovery_info }; + CodeBody recovered_body = parse_global_body(txt("s32 before; s32 broken(s32 value; s32 after;"), recovery_options); + Str recovery_text = { nullptr, 0 }; + if (recovered_body.ast != nullptr) + recovery_text = strbuilder_to_str(code_to_strbuilder((Code)recovered_body)); + expect(recovered_body.ast != nullptr && recovered_body->Type == CT_Global_Body, txt("cpp file-scope recovery body")); + expect(recovery_info.result.ast == ((Code)recovered_body).ast, txt("cpp file-scope recovery ParseInfo result")); + expect(recovery_info.messages != nullptr, txt("cpp file-scope recovery diagnostic")); + if (recovery_info.messages != nullptr) + expect(str_contains(recovery_info.messages->Content, txt("Expected")), txt("cpp file-scope recovery mismatch text")); + if (recovery_info.messages != nullptr) + expect(recovery_info.messages->Next == nullptr, txt("cpp file-scope recovery single diagnostic")); + expect(str_contains(recovery_text, txt("before")), txt("cpp file-scope recovery keeps preceding member")); + expect(str_contains(recovery_text, txt("after")), txt("cpp file-scope recovery keeps following member")); + Code recovered_member = { nullptr }; + if (recovered_body.ast != nullptr) recovered_member = recovered_body->Front; + Code recovered_before = recovered_member; + expect(recovered_before != nullptr && recovered_before->Type == CT_Variable, txt("cpp mismatch recovery keeps preceding member typed")); + if (recovered_before) + expect(str_are_equal(recovered_before->Name, txt("before")), txt("cpp mismatch recovery preceding member name")); + if (recovered_member) recovered_member = recovered_member->Next; + expect(recovered_member != nullptr && recovered_member->Type == CT_Untyped, txt("cpp file-scope recovery stores malformed member as untyped")); + if (recovered_member) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(recovered_member)), txt("s32 broken(s32 value;")), txt("cpp file-scope recovery retains exact mismatch span")); + Code recovered_after = recovered_member ? recovered_member->Next : Code{ nullptr }; + expect(recovered_after != nullptr && recovered_after->Type == CT_Variable, txt("cpp file-scope recovery keeps following member typed")); + if (recovered_after) + expect(str_are_equal(recovered_after->Name, txt("after")), txt("cpp file-scope recovery following member name")); + + ParseInfo invalid_member_info = {}; + Opts_parse invalid_member_options = { &ctx, &invalid_member_info }; + CodeBody invalid_member_body = parse_global_body(txt("s32 before; = { 1; 2 }; s32 after;"), invalid_member_options); + Str invalid_member_text = { nullptr, 0 }; + if (invalid_member_body.ast != nullptr) + invalid_member_text = strbuilder_to_str(code_to_strbuilder((Code)invalid_member_body)); + expect(invalid_member_body.ast != nullptr && invalid_member_body->Type == CT_Global_Body, txt("cpp invalid member recovery body")); + expect(invalid_member_info.result.ast == ((Code)invalid_member_body).ast, txt("cpp invalid member recovery ParseInfo result")); + expect(invalid_member_info.messages != nullptr, txt("cpp invalid member recovery diagnostic")); + if (invalid_member_info.messages != nullptr) { + expect(str_contains(invalid_member_info.messages->Content, txt("Failed to parse member")), txt("cpp invalid member recovery diagnostic text")); + expect(invalid_member_info.messages->Next == nullptr, txt("cpp invalid member recovery single diagnostic")); + } + expect(str_contains(invalid_member_text, txt("before")), txt("cpp invalid member recovery keeps preceding member")); + expect(str_contains(invalid_member_text, txt("after")), txt("cpp invalid member recovery keeps following member")); + Code invalid_member_node = { nullptr }; + if (invalid_member_body.ast != nullptr) invalid_member_node = invalid_member_body->Front; + Code invalid_member_before = invalid_member_node; + expect(invalid_member_before != nullptr && invalid_member_before->Type == CT_Variable, txt("cpp invalid member keeps preceding member typed")); + if (invalid_member_before) + expect(str_are_equal(invalid_member_before->Name, txt("before")), txt("cpp invalid member preceding member name")); + if (invalid_member_node) invalid_member_node = invalid_member_node->Next; + expect(invalid_member_node != nullptr && invalid_member_node->Type == CT_Untyped, txt("cpp invalid member recovery stores member as untyped")); + if (invalid_member_node) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(invalid_member_node)), txt("= { 1; 2 };")), txt("cpp invalid member recovery retains exact source span")); + Code invalid_member_after = invalid_member_node ? invalid_member_node->Next : Code{ nullptr }; + expect(invalid_member_after != nullptr && invalid_member_after->Type == CT_Variable, txt("cpp invalid member recovery keeps following member typed")); + if (invalid_member_after) + expect(str_are_equal(invalid_member_after->Name, txt("after")), txt("cpp invalid member recovery following member name")); + + ParseInfo import_recovery_info = {}; + Opts_parse import_recovery_options = { &ctx, &import_recovery_info }; + CodeBody import_recovery_body = parse_global_body(txt("s32 before; import foo; s32 after;"), import_recovery_options); + Code import_recovery_node = { nullptr }; + if (import_recovery_body.ast != nullptr) import_recovery_node = import_recovery_body->Front; + Code import_recovery_before = import_recovery_node; + expect(import_recovery_before != nullptr && import_recovery_before->Type == CT_Variable, txt("cpp import recovery keeps preceding member typed")); + if (import_recovery_before) + expect(str_are_equal(import_recovery_before->Name, txt("before")), txt("cpp import recovery preceding member name")); + if (import_recovery_node) import_recovery_node = import_recovery_node->Next; + expect(import_recovery_body.ast != nullptr && import_recovery_body->Type == CT_Global_Body, txt("cpp import recovery body")); + expect(import_recovery_info.messages != nullptr, txt("cpp import recovery diagnostic")); + if (import_recovery_info.messages != nullptr) + expect(import_recovery_info.messages->Next == nullptr, txt("cpp import recovery single diagnostic")); + expect(import_recovery_node != nullptr && import_recovery_node->Type == CT_Untyped, txt("cpp import recovery preserves member as untyped")); + if (import_recovery_node) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(import_recovery_node)), txt("import foo;")), txt("cpp import recovery exact source span")); + Code import_recovery_after = import_recovery_node ? import_recovery_node->Next : Code{ nullptr }; + expect(import_recovery_after != nullptr && import_recovery_after->Type == CT_Variable, txt("cpp import recovery keeps following member typed")); + if (import_recovery_after) + expect(str_are_equal(import_recovery_after->Name, txt("after")), txt("cpp import recovery following member name")); + + Macro unbounded_macro = { txt("UnboundedRecoveryMacro"), MT_Expression, MF_Null }; + register_macro(unbounded_macro); + ParseInfo macro_recovery_info = {}; + Opts_parse macro_recovery_options = { &ctx, ¯o_recovery_info }; + CodeBody macro_recovery_body = parse_global_body(txt("s32 before; UnboundedRecoveryMacro; s32 after;"), macro_recovery_options); + Code macro_recovery_node = { nullptr }; + if (macro_recovery_body.ast != nullptr) macro_recovery_node = macro_recovery_body->Front; + Code macro_recovery_before = macro_recovery_node; + expect(macro_recovery_before != nullptr && macro_recovery_before->Type == CT_Variable, txt("cpp macro recovery keeps preceding member typed")); + if (macro_recovery_before) + expect(str_are_equal(macro_recovery_before->Name, txt("before")), txt("cpp macro recovery preceding member name")); + if (macro_recovery_node) macro_recovery_node = macro_recovery_node->Next; + expect(macro_recovery_body.ast != nullptr && macro_recovery_body->Type == CT_Global_Body, txt("cpp unbounded macro recovery body")); + expect(macro_recovery_info.messages != nullptr, txt("cpp unbounded macro recovery diagnostic")); + if (macro_recovery_info.messages != nullptr) + expect(macro_recovery_info.messages->Next == nullptr, txt("cpp unbounded macro recovery single diagnostic")); + expect(macro_recovery_node != nullptr && macro_recovery_node->Type == CT_Untyped, txt("cpp unbounded macro recovery preserves member as untyped")); + if (macro_recovery_node) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(macro_recovery_node)), txt("UnboundedRecoveryMacro;")), txt("cpp unbounded macro recovery exact source span")); + Code macro_recovery_after = macro_recovery_node ? macro_recovery_node->Next : Code{ nullptr }; + expect(macro_recovery_after != nullptr && macro_recovery_after->Type == CT_Variable, txt("cpp unbounded macro recovery keeps following member typed")); + if (macro_recovery_after) + expect(str_are_equal(macro_recovery_after->Name, txt("after")), txt("cpp unbounded macro recovery following member name")); + + ParseInfo eof_recovery_info = {}; + Opts_parse eof_recovery_options = { &ctx, &eof_recovery_info }; + CodeBody eof_recovery_body = parse_global_body(txt("s32 before; s32 broken(s32 value"), eof_recovery_options); + Code eof_recovery_node = { nullptr }; + if (eof_recovery_body.ast != nullptr) eof_recovery_node = eof_recovery_body->Front; + Code eof_recovery_before = eof_recovery_node; + expect(eof_recovery_before != nullptr && eof_recovery_before->Type == CT_Variable, txt("cpp EOF recovery keeps preceding member typed")); + if (eof_recovery_before) + expect(str_are_equal(eof_recovery_before->Name, txt("before")), txt("cpp EOF recovery preceding member name")); + if (eof_recovery_node) eof_recovery_node = eof_recovery_node->Next; + expect(eof_recovery_body.ast != nullptr && eof_recovery_body->Type == CT_Global_Body, txt("cpp end-of-slice recovery body")); + expect(eof_recovery_info.messages != nullptr, txt("cpp end-of-slice recovery diagnostic")); + if (eof_recovery_info.messages != nullptr) + expect(eof_recovery_info.messages->Next == nullptr, txt("cpp end-of-slice recovery single diagnostic")); + expect(eof_recovery_node != nullptr && eof_recovery_node->Type == CT_Untyped, txt("cpp end-of-slice member is untyped")); + if (eof_recovery_node) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(eof_recovery_node)), txt("s32 broken(s32 value")), txt("cpp end-of-slice recovery exact source span")); + if (eof_recovery_node) + expect(eof_recovery_node->Next == nullptr, txt("cpp end-of-slice recovery has no following node")); + + ParseInfo comma_recovery_info = {}; + Opts_parse comma_recovery_options = { &ctx, &comma_recovery_info }; + CodeBody comma_recovery_body = parse_global_body(txt("s32 before; , s32 after;"), comma_recovery_options); + Code comma_recovery_node = { nullptr }; + if (comma_recovery_body.ast != nullptr) comma_recovery_node = comma_recovery_body->Front; + Code comma_recovery_before = comma_recovery_node; + expect(comma_recovery_before != nullptr && comma_recovery_before->Type == CT_Variable, txt("cpp comma recovery keeps preceding member typed")); + if (comma_recovery_before) + expect(str_are_equal(comma_recovery_before->Name, txt("before")), txt("cpp comma recovery preceding member name")); + if (comma_recovery_node) comma_recovery_node = comma_recovery_node->Next; + expect(comma_recovery_body.ast != nullptr && comma_recovery_body->Type == CT_Global_Body, txt("cpp comma recovery body")); + expect(comma_recovery_info.messages != nullptr, txt("cpp comma recovery diagnostic")); + if (comma_recovery_info.messages != nullptr) { + expect(str_contains(comma_recovery_info.messages->Content, txt("Dangling comma")), txt("cpp comma recovery diagnostic text")); + expect(comma_recovery_info.messages->Next == nullptr, txt("cpp comma recovery single diagnostic")); + } + expect(comma_recovery_node != nullptr && comma_recovery_node->Type == CT_Untyped, txt("cpp comma recovery preserves token as untyped")); + if (comma_recovery_node) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(comma_recovery_node)), txt(",")), txt("cpp comma recovery exact source span")); + Code comma_recovery_after = comma_recovery_node ? comma_recovery_node->Next : Code{ nullptr }; + expect(comma_recovery_after != nullptr && comma_recovery_after->Type == CT_Variable, txt("cpp comma recovery keeps following member typed")); + if (comma_recovery_after) + expect(str_are_equal(comma_recovery_after->Name, txt("after")), txt("cpp comma recovery following member name")); + + CodeBody recovery_reuse = parse_global_body(txt("s32 reused_after_recovery;"), recovery_options); + Str recovery_reuse_text = { nullptr, 0 }; + if (recovery_reuse.ast != nullptr) + recovery_reuse_text = strbuilder_to_str(code_to_strbuilder((Code)recovery_reuse)); + expect(recovery_reuse.ast != nullptr && recovery_reuse->Type == CT_Global_Body, txt("cpp parser context reuse after recovery")); + expect(recovery_info.messages == nullptr, txt("cpp parser context reuse clears prior diagnostics")); + expect(str_contains(recovery_reuse_text, txt("reused_after_recovery")), txt("cpp parser context reuse parses later input")); + + ParseInfo nested_struct_info = {}; + Opts_parse nested_struct_options = { &ctx, &nested_struct_info }; + CodeStruct nested_struct = parse_struct(txt("struct NestedRecovery { s32 before; s32 broken(s32 value; s32 after; };"), nested_struct_options); + expect(nested_struct.ast != nullptr && nested_struct->Type == CT_Struct, txt("cpp nested struct recovery result")); + expect(nested_struct_info.result.ast == ((Code)nested_struct).ast, txt("cpp nested struct recovery ParseInfo result")); + expect(nested_struct_info.messages != nullptr, txt("cpp nested struct recovery diagnostic")); + if (nested_struct_info.messages != nullptr) + expect(str_contains(nested_struct_info.messages->Content, txt("Expected")), txt("cpp nested struct recovery diagnostic text")); + Code nested_struct_member = nested_struct.ast != nullptr && nested_struct->Body.ast != nullptr ? nested_struct->Body->Front : Code{ nullptr }; + expect(nested_struct_member != nullptr && nested_struct_member->Type == CT_Variable, txt("cpp nested struct recovery keeps preceding member typed")); + if (nested_struct_member != nullptr) + expect(str_are_equal(nested_struct_member->Name, txt("before")), txt("cpp nested struct recovery preceding member name")); + if (nested_struct_member != nullptr) nested_struct_member = nested_struct_member->Next; + expect(nested_struct_member != nullptr && nested_struct_member->Type == CT_Untyped, txt("cpp nested struct recovery retains failed member")); + if (nested_struct_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(nested_struct_member)), txt("s32 broken(s32 value;")), txt("cpp nested struct recovery exact failed span")); + Code nested_struct_after = nested_struct_member != nullptr ? nested_struct_member->Next : Code{ nullptr }; + expect(nested_struct_after != nullptr && nested_struct_after->Type == CT_Variable, txt("cpp nested struct recovery keeps following member typed")); + if (nested_struct_after != nullptr) + expect(str_are_equal(nested_struct_after->Name, txt("after")), txt("cpp nested struct recovery following member name")); + Str nested_struct_text = { nullptr, 0 }; + if (nested_struct.ast != nullptr) + nested_struct_text = strbuilder_to_str(code_to_strbuilder((Code)nested_struct)); + expect(str_contains(nested_struct_text, txt("s32 after")), txt("cpp nested struct recovery serializes following member")); + + ParseInfo nested_namespace_info = {}; + Opts_parse nested_namespace_options = { &ctx, &nested_namespace_info }; + CodeNS nested_namespace = parse_namespace(txt("namespace NestedRecovery { s32 before; s32 broken(s32 value; s32 after; }"), nested_namespace_options); + expect(nested_namespace.ast != nullptr && nested_namespace->Type == CT_Namespace, txt("cpp nested namespace recovery result")); + expect(nested_namespace_info.result.ast == ((Code)nested_namespace).ast, txt("cpp nested namespace recovery ParseInfo result")); + expect(nested_namespace_info.messages != nullptr, txt("cpp nested namespace recovery diagnostic")); + if (nested_namespace_info.messages != nullptr) + expect(str_contains(nested_namespace_info.messages->Content, txt("Expected")), txt("cpp nested namespace recovery diagnostic text")); + Code nested_namespace_member = nested_namespace.ast != nullptr && nested_namespace->Body.ast != nullptr ? nested_namespace->Body->Front : Code{ nullptr }; + expect(nested_namespace_member != nullptr && nested_namespace_member->Type == CT_Variable, txt("cpp nested namespace recovery keeps preceding member typed")); + if (nested_namespace_member != nullptr) + expect(str_are_equal(nested_namespace_member->Name, txt("before")), txt("cpp nested namespace recovery preceding member name")); + if (nested_namespace_member != nullptr) nested_namespace_member = nested_namespace_member->Next; + expect(nested_namespace_member != nullptr && nested_namespace_member->Type == CT_Untyped, txt("cpp nested namespace recovery retains failed member")); + if (nested_namespace_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(nested_namespace_member)), txt("s32 broken(s32 value;")), txt("cpp nested namespace recovery exact failed span")); + Code nested_namespace_after = nested_namespace_member != nullptr ? nested_namespace_member->Next : Code{ nullptr }; + expect(nested_namespace_after != nullptr && nested_namespace_after->Type == CT_Variable, txt("cpp nested namespace recovery keeps following member typed")); + if (nested_namespace_after != nullptr) + expect(str_are_equal(nested_namespace_after->Name, txt("after")), txt("cpp nested namespace recovery following member name")); + Str nested_namespace_text = { nullptr, 0 }; + if (nested_namespace.ast != nullptr) + nested_namespace_text = strbuilder_to_str(code_to_strbuilder((Code)nested_namespace)); + expect(str_contains(nested_namespace_text, txt("s32 after")), txt("cpp nested namespace recovery serializes following member")); + + ParseInfo nested_brace_info = {}; + Opts_parse nested_brace_options = { &ctx, &nested_brace_info }; + CodeStruct nested_brace_struct = parse_struct(txt("struct NestedBraceRecovery { s32 before; s32 broken(s32 { 1; s32 after; };"), nested_brace_options); + expect(nested_brace_struct.ast != nullptr && nested_brace_struct->Type == CT_Struct, txt("cpp nested brace recovery result")); + expect(nested_brace_info.result.ast == ((Code)nested_brace_struct).ast, txt("cpp nested brace recovery ParseInfo result")); + expect(nested_brace_info.messages != nullptr, txt("cpp nested brace recovery diagnostic")); + Code nested_brace_member = nested_brace_struct.ast != nullptr && nested_brace_struct->Body.ast != nullptr ? nested_brace_struct->Body->Front : Code{ nullptr }; + expect(nested_brace_member != nullptr && nested_brace_member->Type == CT_Variable, txt("cpp nested brace recovery keeps preceding member typed")); + if (nested_brace_member != nullptr) nested_brace_member = nested_brace_member->Next; + expect(nested_brace_member != nullptr && nested_brace_member->Type == CT_Untyped, txt("cpp nested brace recovery retains failed member")); + if (nested_brace_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(nested_brace_member)), txt("s32 broken(s32 { 1;")), txt("cpp nested brace recovery exact failed span")); + Code nested_brace_after = nested_brace_member != nullptr ? nested_brace_member->Next : Code{ nullptr }; + expect(nested_brace_after != nullptr && nested_brace_after->Type == CT_Variable, txt("cpp nested brace recovery keeps following member typed")); + if (nested_brace_after != nullptr) + expect(str_are_equal(nested_brace_after->Name, txt("after")), txt("cpp nested brace recovery following member name")); + Str nested_brace_text = { nullptr, 0 }; + if (nested_brace_struct.ast != nullptr) + nested_brace_text = strbuilder_to_str(code_to_strbuilder((Code)nested_brace_struct)); + expect(str_contains(nested_brace_text, txt("s32 after")), txt("cpp nested brace recovery serializes following member")); + + ParseInfo nested_namespace_brace_info = {}; + Opts_parse nested_namespace_brace_options = { &ctx, &nested_namespace_brace_info }; + CodeNS nested_namespace_brace = parse_namespace(txt("namespace NestedBraceRecovery { s32 before; s32 broken(s32 { 1; s32 after; }"), nested_namespace_brace_options); + expect(nested_namespace_brace.ast != nullptr && nested_namespace_brace->Type == CT_Namespace, txt("cpp nested namespace brace recovery result")); + expect(nested_namespace_brace_info.result.ast == ((Code)nested_namespace_brace).ast, txt("cpp nested namespace brace recovery ParseInfo result")); + Code nested_namespace_brace_member = nested_namespace_brace.ast != nullptr && nested_namespace_brace->Body.ast != nullptr ? nested_namespace_brace->Body->Front : Code{ nullptr }; + expect(nested_namespace_brace_member != nullptr && nested_namespace_brace_member->Type == CT_Variable, txt("cpp nested namespace brace recovery keeps preceding member typed")); + if (nested_namespace_brace_member != nullptr) nested_namespace_brace_member = nested_namespace_brace_member->Next; + expect(nested_namespace_brace_member != nullptr && nested_namespace_brace_member->Type == CT_Untyped, txt("cpp nested namespace brace recovery retains failed member")); + if (nested_namespace_brace_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(nested_namespace_brace_member)), txt("s32 broken(s32 { 1;")), txt("cpp nested namespace brace recovery exact failed span")); + Code nested_namespace_brace_after = nested_namespace_brace_member != nullptr ? nested_namespace_brace_member->Next : Code{ nullptr }; + expect(nested_namespace_brace_after != nullptr && nested_namespace_brace_after->Type == CT_Variable, txt("cpp nested namespace brace recovery keeps following member typed")); + if (nested_namespace_brace_after != nullptr) + expect(str_are_equal(nested_namespace_brace_after->Name, txt("after")), txt("cpp nested namespace brace recovery following member name")); + Str nested_namespace_brace_text = { nullptr, 0 }; + if (nested_namespace_brace.ast != nullptr) + nested_namespace_brace_text = strbuilder_to_str(code_to_strbuilder((Code)nested_namespace_brace)); + expect(str_contains(nested_namespace_brace_text, txt("s32 after")), txt("cpp nested namespace brace recovery serializes following member")); + + ParseInfo nested_brace_tail_info = {}; + Opts_parse nested_brace_tail_options = { &ctx, &nested_brace_tail_info }; + CodeStruct nested_brace_tail_struct = parse_struct(txt("struct NestedBraceTailRecovery { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; };"), nested_brace_tail_options); + expect(nested_brace_tail_struct.ast != nullptr && nested_brace_tail_struct->Type == CT_Struct, txt("cpp nested brace tail recovery result")); + expect(nested_brace_tail_info.result.ast == ((Code)nested_brace_tail_struct).ast, txt("cpp nested brace tail recovery ParseInfo result")); + expect(nested_brace_tail_info.messages != nullptr, txt("cpp nested brace tail recovery diagnostic")); + Code nested_brace_tail_member = nested_brace_tail_struct.ast != nullptr && nested_brace_tail_struct->Body.ast != nullptr ? nested_brace_tail_struct->Body->Front : Code{ nullptr }; + if (nested_brace_tail_member != nullptr) nested_brace_tail_member = nested_brace_tail_member->Next; + expect(nested_brace_tail_member != nullptr && nested_brace_tail_member->Type == CT_Untyped, txt("cpp nested brace tail recovery retains failed member")); + if (nested_brace_tail_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(nested_brace_tail_member)), txt("s32 broken(s32 { 1; 2 } foo{ 3;")), txt("cpp nested brace tail recovery exact failed span")); + Code nested_brace_tail_after = nested_brace_tail_member != nullptr ? nested_brace_tail_member->Next : Code{ nullptr }; + expect(nested_brace_tail_after != nullptr && nested_brace_tail_after->Type == CT_Variable, txt("cpp nested brace tail recovery keeps following member typed")); + if (nested_brace_tail_after != nullptr) + expect(str_are_equal(nested_brace_tail_after->Name, txt("after")), txt("cpp nested brace tail recovery following member name")); + + ParseInfo nested_namespace_tail_info = {}; + Opts_parse nested_namespace_tail_options = { &ctx, &nested_namespace_tail_info }; + CodeNS nested_namespace_tail = parse_namespace(txt("namespace NestedBraceTailRecovery { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; }"), nested_namespace_tail_options); + expect(nested_namespace_tail.ast != nullptr && nested_namespace_tail->Type == CT_Namespace, txt("cpp nested namespace brace tail recovery result")); + expect(nested_namespace_tail_info.result.ast == ((Code)nested_namespace_tail).ast, txt("cpp nested namespace brace tail recovery ParseInfo result")); + Code nested_namespace_tail_member = nested_namespace_tail.ast != nullptr && nested_namespace_tail->Body.ast != nullptr ? nested_namespace_tail->Body->Front : Code{ nullptr }; + if (nested_namespace_tail_member != nullptr) nested_namespace_tail_member = nested_namespace_tail_member->Next; + expect(nested_namespace_tail_member != nullptr && nested_namespace_tail_member->Type == CT_Untyped, txt("cpp nested namespace brace tail recovery retains failed member")); + if (nested_namespace_tail_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(nested_namespace_tail_member)), txt("s32 broken(s32 { 1; 2 } foo{ 3;")), txt("cpp nested namespace brace tail recovery exact failed span")); + Code nested_namespace_tail_after = nested_namespace_tail_member != nullptr ? nested_namespace_tail_member->Next : Code{ nullptr }; + expect(nested_namespace_tail_after != nullptr && nested_namespace_tail_after->Type == CT_Variable, txt("cpp nested namespace brace tail recovery keeps following member typed")); + if (nested_namespace_tail_after != nullptr) + expect(str_are_equal(nested_namespace_tail_after->Name, txt("after")), txt("cpp nested namespace brace tail recovery following member name")); + + ParseInfo nested_inner_close_info = {}; + Opts_parse nested_inner_close_options = { &ctx, &nested_inner_close_info }; + CodeStruct nested_inner_close_struct = parse_struct(txt("struct NestedInnerCloseRecovery { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; struct Inner { s32 x; }; s32 tail; };"), nested_inner_close_options); + expect(nested_inner_close_struct.ast != nullptr && nested_inner_close_struct->Type == CT_Struct, txt("cpp nested inner-close recovery result")); + Code nested_inner_close_member = nested_inner_close_struct.ast != nullptr && nested_inner_close_struct->Body.ast != nullptr ? nested_inner_close_struct->Body->Front : Code{ nullptr }; + if (nested_inner_close_member != nullptr) nested_inner_close_member = nested_inner_close_member->Next; + expect(nested_inner_close_member != nullptr && nested_inner_close_member->Type == CT_Untyped, txt("cpp nested inner-close recovery retains failed member")); + if (nested_inner_close_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(nested_inner_close_member)), txt("s32 broken(s32 { 1; 2 } foo{ 3;")), txt("cpp nested inner-close recovery exact failed span")); + Code nested_inner_close_after = nested_inner_close_member != nullptr ? nested_inner_close_member->Next : Code{ nullptr }; + expect(nested_inner_close_after != nullptr && nested_inner_close_after->Type == CT_Variable, txt("cpp nested inner-close recovery keeps following member typed")); + if (nested_inner_close_after != nullptr) + expect(str_are_equal(nested_inner_close_after->Name, txt("after")), txt("cpp nested inner-close recovery following member name")); + Code nested_inner_close_inner = nested_inner_close_after != nullptr ? nested_inner_close_after->Next : Code{ nullptr }; + expect(nested_inner_close_inner != nullptr && nested_inner_close_inner->Type == CT_Struct, txt("cpp nested inner-close recovery parses nested struct")); + Code nested_inner_close_tail = nested_inner_close_inner != nullptr ? nested_inner_close_inner->Next : Code{ nullptr }; + expect(nested_inner_close_tail != nullptr && nested_inner_close_tail->Type == CT_Variable, txt("cpp nested inner-close recovery parses trailing member")); + + ParseInfo inner_before_sync_info = {}; + Opts_parse inner_before_sync_options = { &ctx, &inner_before_sync_info }; + CodeStruct inner_before_sync = parse_struct(txt("struct NestedInnerBeforeSync { s32 before; s32 broken(foo{ struct Inner { s32 x; }; s32 after; };"), inner_before_sync_options); + expect(inner_before_sync.ast != nullptr && inner_before_sync->Type == CT_Struct, txt("cpp inner-before-sync recovery result")); + Code inner_before_sync_member = inner_before_sync.ast != nullptr && inner_before_sync->Body.ast != nullptr ? inner_before_sync->Body->Front : Code{ nullptr }; + if (inner_before_sync_member != nullptr) inner_before_sync_member = inner_before_sync_member->Next; + expect(inner_before_sync_member != nullptr && inner_before_sync_member->Type == CT_Untyped, txt("cpp inner-before-sync recovery retains failed member")); + if (inner_before_sync_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(inner_before_sync_member)), txt("s32 broken(foo{ struct Inner { s32 x; };")), txt("cpp inner-before-sync recovery exact failed span")); + Code inner_before_sync_after = inner_before_sync_member != nullptr ? inner_before_sync_member->Next : Code{ nullptr }; + expect(inner_before_sync_after != nullptr && inner_before_sync_after->Type == CT_Variable, txt("cpp inner-before-sync recovery keeps following member typed")); + if (inner_before_sync_after != nullptr) + expect(str_are_equal(inner_before_sync_after->Name, txt("after")), txt("cpp inner-before-sync recovery following member name")); + + ParseInfo global_namespace_recovery_info = {}; + Opts_parse global_namespace_recovery_options = { &ctx, &global_namespace_recovery_info }; + CodeBody global_namespace_recovery = parse_global_body(txt("namespace NestedScopeInGlobal { s32 before; s32 broken(s32 { 1; 2 } foo{ 3; s32 after; } struct Later { s32 value; };"), global_namespace_recovery_options); + expect(global_namespace_recovery.ast != nullptr && global_namespace_recovery->Type == CT_Global_Body, txt("cpp global namespace recovery body")); + expect(global_namespace_recovery_info.result.ast == ((Code)global_namespace_recovery).ast, txt("cpp global namespace recovery ParseInfo result")); + Code global_namespace_node = global_namespace_recovery.ast != nullptr ? global_namespace_recovery->Front : Code{ nullptr }; + expect(global_namespace_node != nullptr && global_namespace_node->Type == CT_Namespace, txt("cpp global namespace recovery preserves namespace")); + Code global_namespace_member = global_namespace_node != nullptr && global_namespace_node->Body.ast != nullptr ? global_namespace_node->Body->Front : Code{ nullptr }; + if (global_namespace_member != nullptr) global_namespace_member = global_namespace_member->Next; + expect(global_namespace_member != nullptr && global_namespace_member->Type == CT_Untyped, txt("cpp global namespace recovery retains failed member")); + if (global_namespace_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(global_namespace_member)), txt("s32 broken(s32 { 1; 2 } foo{ 3;")), txt("cpp global namespace recovery exact failed span")); + Code global_namespace_after = global_namespace_member != nullptr ? global_namespace_member->Next : Code{ nullptr }; + expect(global_namespace_after != nullptr && global_namespace_after->Type == CT_Variable, txt("cpp global namespace recovery keeps following member typed")); + if (global_namespace_after != nullptr) + expect(str_are_equal(global_namespace_after->Name, txt("after")), txt("cpp global namespace recovery following member name")); + Code global_later_struct = global_namespace_node != nullptr ? global_namespace_node->Next : Code{ nullptr }; + expect(global_later_struct != nullptr && global_later_struct->Type == CT_Struct, txt("cpp global namespace recovery preserves following global struct")); + if (global_later_struct != nullptr) + expect(str_are_equal(global_later_struct->Name, txt("Later")), txt("cpp global namespace recovery following global name")); + + ParseInfo global_struct_recovery_info = {}; + Opts_parse global_struct_recovery_options = { &ctx, &global_struct_recovery_info }; + CodeBody global_struct_recovery = parse_global_body(txt("struct GlobalNestedRecovery { s32 before; s32 broken(s32 value; s32 after; }; struct LaterGlobal { s32 value; };"), global_struct_recovery_options); + expect(global_struct_recovery.ast != nullptr && global_struct_recovery->Type == CT_Global_Body, txt("cpp global struct recovery body")); + Code global_struct_node = global_struct_recovery.ast != nullptr ? global_struct_recovery->Front : Code{ nullptr }; + Code global_struct_before = global_struct_node != nullptr && global_struct_node->Body.ast != nullptr ? global_struct_node->Body->Front : Code{ nullptr }; + Code global_struct_member = global_struct_before != nullptr ? global_struct_before->Next : Code{ nullptr }; + Code global_struct_after = global_struct_member != nullptr ? global_struct_member->Next : Code{ nullptr }; + Code global_later_class = global_struct_node != nullptr ? global_struct_node->Next : Code{ nullptr }; + expect(global_struct_node != nullptr && global_struct_node->Type == CT_Struct, txt("cpp global struct recovery preserves partial struct")); + expect(global_struct_member != nullptr && global_struct_member->Type == CT_Untyped, txt("cpp global struct recovery retains failed member")); + if (global_struct_member != nullptr) + expect(str_are_equal(strbuilder_to_str(code_to_strbuilder(global_struct_member)), txt("s32 broken(s32 value;")), txt("cpp global struct recovery exact failed span")); + expect(global_struct_after != nullptr && global_struct_after->Type == CT_Variable, txt("cpp global struct recovery keeps following member")); + if (global_struct_after != nullptr) + expect(str_are_equal(global_struct_after->Name, txt("after")), txt("cpp global struct recovery following member name")); + expect(global_later_class != nullptr && global_later_class->Type == CT_Struct, txt("cpp global struct recovery keeps following global struct")); + + ParseInfo nested_owner_info = {}; + Opts_parse nested_owner_options = { &ctx, &nested_owner_info }; + CodeStruct nested_owner = parse_struct(txt("struct NestedOwner { s32 before; struct InnerRecovery { s32 inner_before; s32 broken(s32 value; s32 inner_after; }; s32 after; };"), nested_owner_options); + expect(nested_owner.ast != nullptr && nested_owner->Type == CT_Struct, txt("cpp nested owner recovery result")); + expect(nested_owner_info.result.ast == ((Code)nested_owner).ast, txt("cpp nested owner recovery ParseInfo result")); + expect(nested_owner_info.messages != nullptr, txt("cpp nested owner recovery diagnostic")); + Code nested_owner_before = nested_owner.ast != nullptr && nested_owner->Body.ast != nullptr ? nested_owner->Body->Front : Code{ nullptr }; + Code nested_owner_inner = nested_owner_before != nullptr ? nested_owner_before->Next : Code{ nullptr }; + Code nested_owner_after = nested_owner_inner != nullptr ? nested_owner_inner->Next : Code{ nullptr }; + Code nested_owner_inner_before = nested_owner_inner != nullptr && nested_owner_inner->Body.ast != nullptr ? nested_owner_inner->Body->Front : Code{ nullptr }; + Code nested_owner_inner_failed = nested_owner_inner_before != nullptr ? nested_owner_inner_before->Next : Code{ nullptr }; + Code nested_owner_inner_after = nested_owner_inner_failed != nullptr ? nested_owner_inner_failed->Next : Code{ nullptr }; + expect(nested_owner_before != nullptr && nested_owner_before->Type == CT_Variable, txt("cpp nested owner keeps preceding member typed")); + expect(nested_owner_inner != nullptr && nested_owner_inner->Type == CT_Struct, txt("cpp nested owner retains inner struct AST")); + expect(nested_owner_inner_before != nullptr && nested_owner_inner_before->Type == CT_Variable, txt("cpp nested inner recovery keeps preceding member typed")); + expect(nested_owner_inner_failed != nullptr && nested_owner_inner_failed->Type == CT_Untyped, txt("cpp nested inner recovery retains failed member")); + expect(nested_owner_inner_after != nullptr && nested_owner_inner_after->Type == CT_Variable, txt("cpp nested inner recovery keeps following member typed")); + expect(nested_owner_after != nullptr && nested_owner_after->Type == CT_Variable, txt("cpp nested owner keeps following member typed")); + #if ! GEN_BUILD_DEBUG CodeVar wrong_array_value = def_variable(def_type(name(u32)), name(invalid_array_expr)); Opts_def_type wrong_array_options = {}; wrong_array_options.array_expr = (Code)wrong_array_value; - CodeTypename invalid_array_type = def_type(name(InvalidArrayOption), wrong_array_options); + CodeTypename invalid_array_type = def_type(name(InvalidArrayOption), wrong_array_options); expect(((Code)invalid_array_type).ast == Code_Invalid.ast, txt("cpp wrong type invalid code")); CodeBody missing_union_body = {}; diff --git a/test/lexer_failures/test.cpp b/test/lexer_failures/test.cpp index 6309aa2..7ed0f33 100644 --- a/test/lexer_failures/test.cpp +++ b/test/lexer_failures/test.cpp @@ -12,14 +12,16 @@ internal void expect(int condition, Str label) { } 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); + Context ctx = {}; init(& ctx); + ParseInfo info = {}; Opts_parse options = { & ctx, & info }; + Code parsed = parse_variable(def, options); + expect(info.result.ast == parsed.ast, label); + expect(info.lexed.messages != nullptr, label); + if (info.lexed.messages != nullptr) expect(str_contains(info.lexed.messages->content, needle), label); + if (info.lexed.tokens.num == 0) expect(parsed == Code_Invalid, label); - CodeVar again = parse_variable(txt("char const* baseline_message = \"GENCPP_BASELINE\";")); expect(again.ast != nullptr, label); + CodeVar again = parse_variable(txt("char const* baseline_message = \"GENCPP_BASELINE\";"), options); expect(again.ast != nullptr, label); + expect(info.lexed.messages == nullptr && info.messages == nullptr, label); if (again.ast != nullptr) { expect(again->Type == CT_Variable, label); expect(str_contains(again->Name, txt("baseline_message")), label); diff --git a/test/parser_bounds/test.cpp b/test/parser_bounds/test.cpp index 84bb971..00e67c9 100644 --- a/test/parser_bounds/test.cpp +++ b/test/parser_bounds/test.cpp @@ -125,13 +125,13 @@ internal void expect_skip_end(Str label) Token storage[5] = {}; plant(storage, 2, newlines, 2); ParseContext parser = {}; bind(& parser, storage, 2, 0); - Token* got = lex_current(&parser, lex_skip_formatting); + 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); + 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); @@ -139,29 +139,40 @@ internal void expect_skip_end(Str 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); + 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); +internal void expect_boundary(Str def, Str label, Str mark, b32 expect_lexer_message) { + 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); + ParseMessage* parse_message = _ctx->parse_info.messages; + LexerMessage* lexer_message = _ctx->parse_info.lexed.messages; + if (expect_lexer_message) { + expect(lexer_message != nullptr, label); + expect(parse_message == nullptr, label); + if (lexer_message != nullptr) expect(str_contains(lexer_message->content, mark), label); + } + else { + expect(parse_message != nullptr, label); + expect(lexer_message == nullptr, label); + } + if (expect_lexer_message == false && parse_message != nullptr) { + expect(parse_message->Scope == nullptr, label); + expect(parse_message->Content.Ptr != nullptr && parse_message->Content.Len > 0, label); + expect(str_contains(parse_message->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); + deinit(& ctx); } internal void expect_eat_end(Str label) { @@ -222,11 +233,11 @@ int main() 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_boundary(null_def, txt("null-len"), txt("gen::parse_variable: length must greater than 0"), false); + expect_boundary(null_ptr, txt("null-ptr"), txt("gen::parse_variable: def was null"), false); + expect_boundary(txt(""), txt("zero-len"), txt("gen::parse_variable: length must greater than 0"), false); + expect_boundary(txt(" \n\t"), txt("whitespace"), txt("no tokens found"), true); + expect_boundary(txt("// note\n"), txt("comment"), txt("only formatting tokens"), false); expect_eat_end (txt("eat")); expect_direct (txt("direct"));