[PATCH v3 0/2] MR10897: vbscript: Allow Class declarations inline after Dim/Sub separated by ':'.
ClassDeclaration was only allowed at the top SourceElement level, so any prior statement on the same line caused err 1002 instead of either parsing successfully or, for a name collision, err 1041. Treat ClassDeclaration as a SimpleStatement matching the FunctionDecl pattern: it produces a STAT_CLASS statement that registers the class with the parser at compile time and errors out if the declaration is not at script global scope. -- v3: vbscript: Allow Class declarations inline after Dim/Sub separated by ':'. vbscript/tests: Add tests for Class declarations inline after Dim/Sub. https://gitlab.winehq.org/wine/wine/-/merge_requests/10897
From: Francis De Brabandere <francisdb@gmail.com> --- dlls/vbscript/tests/lang.vbs | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index d8d612bd389..d771c9e2067 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -4015,6 +4015,63 @@ ExecuteGlobal "ReDim egDynArr(1) : egDynArr(0) = ""a"" : egDynArr(1) = ""b""" Call ok(egDynArr(0) = "a", "ExecuteGlobal ReDim egDynArr(0) = " & egDynArr(0)) Call ok(egDynArr(1) = "b", "ExecuteGlobal ReDim egDynArr(1) = " & egDynArr(1)) +' Class declaration inline after a Dim/Sub statement separated by ':' +Sub TestClassInlineAfterDim + on error resume next + Err.Clear + ExecuteGlobal "Dim egInlineA : Class EGInlineClassA : End Class" + todo_wine_ok Err.Number = 0, "ExecuteGlobal Dim:Class err=" & Err.Number + + Err.Clear + ExecuteGlobal "Sub EGInlineSub() : End Sub : Class EGInlineClassB : End Class" + todo_wine_ok Err.Number = 0, "ExecuteGlobal Sub:Class err=" & Err.Number + + Err.Clear + ExecuteGlobal "Dim EGInlineCollision : Class EGInlineCollision : End Class" + todo_wine_ok Err.Number = 1041, "ExecuteGlobal Dim x : Class x err=" & Err.Number +End Sub +Call TestClassInlineAfterDim + +' Class declarations are only valid at script global scope. Native rejects any +' inner scope (If/For/Do/While/Sub/Function/nested Class/single-line If) with +' err 1002. +Sub TestClassRejectedInInnerScope + on error resume next + + Err.Clear + ExecuteGlobal "If True Then" & vbCrLf & "Class EGInIf" & vbCrLf & "End Class" & vbCrLf & "End If" + call ok(Err.Number = 1002, "ExecuteGlobal If/Then class err=" & Err.Number) + + Err.Clear + ExecuteGlobal "Dim i : For i = 1 To 1" & vbCrLf & "Class EGInFor" & vbCrLf & "End Class" & vbCrLf & "Next" + call ok(Err.Number = 1002, "ExecuteGlobal For class err=" & Err.Number) + + Err.Clear + ExecuteGlobal "Do" & vbCrLf & "Class EGInDo" & vbCrLf & "End Class" & vbCrLf & "Loop Until True" + call ok(Err.Number = 1002, "ExecuteGlobal Do/Loop class err=" & Err.Number) + + Err.Clear + ExecuteGlobal "Dim d : d = False : While Not d" & vbCrLf & "Class EGInWhile" & vbCrLf & "End Class" & vbCrLf & "d = True" & vbCrLf & "Wend" + call ok(Err.Number = 1002, "ExecuteGlobal While/Wend class err=" & Err.Number) + + Err.Clear + ExecuteGlobal "Sub EGSubClass" & vbCrLf & "Class EGInSub" & vbCrLf & "End Class" & vbCrLf & "End Sub" + call ok(Err.Number = 1002, "ExecuteGlobal Sub-local class err=" & Err.Number) + + Err.Clear + ExecuteGlobal "Function EGFuncClass" & vbCrLf & "Class EGInFunc" & vbCrLf & "End Class" & vbCrLf & "End Function" + call ok(Err.Number = 1002, "ExecuteGlobal Function-local class err=" & Err.Number) + + Err.Clear + ExecuteGlobal "Class EGOuter" & vbCrLf & "Class EGInner" & vbCrLf & "End Class" & vbCrLf & "End Class" + call ok(Err.Number = 1002, "ExecuteGlobal nested class err=" & Err.Number) + + Err.Clear + ExecuteGlobal "If True Then Class EGSingleLine : End Class" + call ok(Err.Number = 1002, "ExecuteGlobal single-line If Then class err=" & Err.Number) +End Sub +Call TestClassRejectedInInnerScope + ' Execute tests x = 0 Execute "x = 99" -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10897
From: Francis De Brabandere <francisdb@gmail.com> ClassDeclaration was only allowed at the top SourceElement level, so any prior statement on the same line caused err 1002 instead of either parsing successfully or, for a name collision, err 1041. Treat ClassDeclaration as a SimpleStatement matching the FunctionDecl pattern: it produces a STAT_CLASS statement that registers the class with the parser at compile time and errors out if the declaration is not at script global scope. --- dlls/vbscript/compile.c | 27 ++++++++++++++++++++++++--- dlls/vbscript/parse.h | 6 ++++++ dlls/vbscript/parser.y | 24 +++++++++++++++--------- dlls/vbscript/tests/lang.vbs | 6 +++--- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 940703762f5..735a041cfd9 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -46,6 +46,7 @@ typedef struct { unsigned loc; statement_ctx_t *stat_ctx; + unsigned stmt_depth; unsigned *labels; unsigned labels_size; @@ -1441,6 +1442,18 @@ static HRESULT compile_function_statement(compile_ctx_t *ctx, function_statement return S_OK; } +static HRESULT compile_class_statement(compile_ctx_t *ctx, class_statement_t *stat) +{ + if(ctx->func != &ctx->code->main_code || ctx->stmt_depth > 1) { + ctx->loc = stat->stat.loc; + return MAKE_VBSERROR(VBSE_SYNTAX_ERROR); + } + + stat->class_decl->next = ctx->parser.class_decls; + ctx->parser.class_decls = stat->class_decl; + return S_OK; +} + static HRESULT compile_exitdo_statement(compile_ctx_t *ctx) { statement_ctx_t *iter; @@ -1663,6 +1676,8 @@ static HRESULT compile_statement(compile_ctx_t *ctx, statement_ctx_t *stat_ctx, { HRESULT hres; + ctx->stmt_depth++; + if(stat_ctx) { stat_ctx->next = ctx->stat_ctx; ctx->stat_ctx = stat_ctx; @@ -1712,6 +1727,9 @@ static HRESULT compile_statement(compile_ctx_t *ctx, statement_ctx_t *stat_ctx, case STAT_FUNC: hres = compile_function_statement(ctx, (function_statement_t*)stat); break; + case STAT_CLASS: + hres = compile_class_statement(ctx, (class_statement_t*)stat); + break; case STAT_IF: hres = compile_if_statement(ctx, (if_statement_t*)stat); break; @@ -1750,16 +1768,18 @@ static HRESULT compile_statement(compile_ctx_t *ctx, statement_ctx_t *stat_ctx, } if(FAILED(hres)) - return hres; + goto done; stat = stat->next; } + hres = S_OK; +done: if(stat_ctx) { assert(ctx->stat_ctx == stat_ctx); ctx->stat_ctx = stat_ctx->next; } - - return S_OK; + ctx->stmt_depth--; + return hres; } static void resolve_labels(compile_ctx_t *ctx, unsigned off) @@ -1834,6 +1854,7 @@ static HRESULT compile_func(compile_ctx_t *ctx, statement_t *stat, function_t *f ctx->func = func; ctx->dim_decls = ctx->dim_decls_tail = NULL; ctx->const_decls = NULL; + ctx->stmt_depth = 0; hres = collect_const_decls(ctx, stat); if(FAILED(hres)) diff --git a/dlls/vbscript/parse.h b/dlls/vbscript/parse.h index 4febe0ca535..66451c1cec0 100644 --- a/dlls/vbscript/parse.h +++ b/dlls/vbscript/parse.h @@ -112,6 +112,7 @@ typedef struct { typedef enum { STAT_ASSIGN, STAT_CALL, + STAT_CLASS, STAT_CONST, STAT_DIM, STAT_DOUNTIL, @@ -223,6 +224,11 @@ typedef struct _class_decl_t { struct _class_decl_t *next; } class_decl_t; +typedef struct { + statement_t stat; + class_decl_t *class_decl; +} class_statement_t; + typedef struct _elseif_decl_t { expression_t *expr; statement_t *stat; diff --git a/dlls/vbscript/parser.y b/dlls/vbscript/parser.y index ec67cde2215..5edd1942913 100644 --- a/dlls/vbscript/parser.y +++ b/dlls/vbscript/parser.y @@ -30,7 +30,6 @@ static int parser_error(unsigned*,parser_ctx_t*,const char*); static void handle_isexpression_script(parser_ctx_t *ctx, expression_t *expr); static void source_add_statement(parser_ctx_t*,statement_t*); -static void source_add_class(parser_ctx_t*,class_decl_t*); static void *new_expression(parser_ctx_t*,expression_type_t,size_t); static expression_t *new_bool_expression(parser_ctx_t*,VARIANT_BOOL); @@ -58,6 +57,7 @@ static statement_t *new_forto_statement(parser_ctx_t*,unsigned,const WCHAR*,expr static statement_t *new_foreach_statement(parser_ctx_t*,unsigned,const WCHAR*,expression_t*,statement_t*); static statement_t *new_if_statement(parser_ctx_t*,unsigned,expression_t*,statement_t*,elseif_decl_t*,statement_t*); static statement_t *new_function_statement(parser_ctx_t*,unsigned,function_decl_t*); +static statement_t *new_class_statement(parser_ctx_t*,unsigned,class_decl_t*); static statement_t *new_onerror_statement(parser_ctx_t*,unsigned,BOOL); static statement_t *new_const_statement(parser_ctx_t*,unsigned,const_decl_t*); static statement_t *new_select_statement(parser_ctx_t*,unsigned,expression_t*,case_clausule_t*); @@ -186,7 +186,6 @@ SourceElements | SourceElements GlobalDimDeclaration StSep { source_add_statement(ctx, $2); } | SourceElements StatementNl { source_add_statement(ctx, $2); } - | SourceElements ClassDeclaration { source_add_class(ctx, $2); } GlobalDimDeclaration : tPRIVATE tCONST ConstDeclList { $$ = new_const_statement(ctx, @$, $3); CHECK_ERROR; } @@ -267,6 +266,7 @@ SimpleStatement | tDO StSep StatementsNl_opt error { ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_LOOP); YYABORT; } | tDO error { ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_WHILE_UNTIL_EOS); YYABORT; } | FunctionDecl { $$ = new_function_statement(ctx, @$, $1); CHECK_ERROR; } + | ClassDeclaration { $$ = new_class_statement(ctx, @$, $1); CHECK_ERROR; } | tEXIT tDO { $$ = new_statement(ctx, STAT_EXITDO, 0, @2); CHECK_ERROR; } | tEXIT tFOR { $$ = new_statement(ctx, STAT_EXITFOR, 0, @2); CHECK_ERROR; } | tEXIT tFUNCTION { $$ = new_statement(ctx, STAT_EXITFUNC, 0, @2); CHECK_ERROR; } @@ -574,7 +574,7 @@ PrimaryExpression | tME { $$ = new_expression(ctx, EXPR_ME, 0); CHECK_ERROR; } ClassDeclaration - : tCLASS Identifier StSep ClassBody tEND tCLASS StSep { $4->name = $2; $4->loc = @2; $$ = $4; } + : tCLASS Identifier StSep ClassBody tEND tCLASS { $4->name = $2; $4->loc = @2; $$ = $4; } | tCLASS Identifier tEND tCLASS { ctx->error_loc = @3; ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_STATEMENT); YYABORT; } | tCLASS Identifier StSep ClassBody tEND error { ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_CLASS); YYABORT; } @@ -731,12 +731,6 @@ static void source_add_statement(parser_ctx_t *ctx, statement_t *stat) } } -static void source_add_class(parser_ctx_t *ctx, class_decl_t *class_decl) -{ - class_decl->next = ctx->class_decls; - ctx->class_decls = class_decl; -} - static void handle_isexpression_script(parser_ctx_t *ctx, expression_t *expr) { retval_statement_t *stat; @@ -1262,6 +1256,18 @@ static statement_t *new_function_statement(parser_ctx_t *ctx, unsigned loc, func return &stat->stat; } +static statement_t *new_class_statement(parser_ctx_t *ctx, unsigned loc, class_decl_t *decl) +{ + class_statement_t *stat; + + stat = new_statement(ctx, STAT_CLASS, sizeof(*stat), loc); + if(!stat) + return NULL; + + stat->class_decl = decl; + return &stat->stat; +} + static class_decl_t *new_class_decl(parser_ctx_t *ctx) { class_decl_t *class_decl; diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index d771c9e2067..8e34ba4c536 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -4020,15 +4020,15 @@ Sub TestClassInlineAfterDim on error resume next Err.Clear ExecuteGlobal "Dim egInlineA : Class EGInlineClassA : End Class" - todo_wine_ok Err.Number = 0, "ExecuteGlobal Dim:Class err=" & Err.Number + call ok(Err.Number = 0, "ExecuteGlobal Dim:Class err=" & Err.Number) Err.Clear ExecuteGlobal "Sub EGInlineSub() : End Sub : Class EGInlineClassB : End Class" - todo_wine_ok Err.Number = 0, "ExecuteGlobal Sub:Class err=" & Err.Number + call ok(Err.Number = 0, "ExecuteGlobal Sub:Class err=" & Err.Number) Err.Clear ExecuteGlobal "Dim EGInlineCollision : Class EGInlineCollision : End Class" - todo_wine_ok Err.Number = 1041, "ExecuteGlobal Dim x : Class x err=" & Err.Number + call ok(Err.Number = 1041, "ExecuteGlobal Dim x : Class x err=" & Err.Number) End Sub Call TestClassInlineAfterDim -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10897
On Sat May 16 08:01:08 2026 +0000, Francis De Brabandere wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/10897/diffs?diff_id=268119&start_sha=8b8da11e95daa0b5a855a834462e787cae3b01ec#7e6a3bd1d6c51ede8f2a0fa46f76324e4c02a37e_1445_1447) Added more test cases + depth tracking
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10897#note_140163
I'm not sure about moving that logic to the compiler. Technically, this is the kind of thing the parser should handle. The statement separation syntax in vbscript is very nuanced, so I can see why it's tempting to move it into the compiler (that is also one of the reasons why I considered rewriting the parser in pure C at some point). I could be fine with that approach in some cases, but the additional state tracking needed to reconstruct parser state does not seem great. Note that the distinction between the parser and bytecode emitter stages is not purely internal. It has some externally visible effects. For example, with this MR, something like the following: ``` sub s() class c end class end sub ; parse error ; ``` would report a parser error in line 5 instead of failing earlier in line 2 first. (Admittedly, it's not a very important difference.) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10897#note_140957
participants (3)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb) -
Jacek Caban (@jacek)