[PATCH 0/1] MR11124: Draft: vbscript: Treat Default as a keyword only in class member declarations.
Native VBScript lexes Default as a keyword only when the next word is a member kind or access modifier, so class member variables can be named Default. A Default specification with a missing or non-Public modifier reports error 1057 inside a class body and a generic syntax error at global scope. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11124
From: Francis De Brabandere <francisdb@gmail.com> Native VBScript lexes Default as a keyword only when the next word is a member kind or access modifier, so class member variables can be named Default. A Default specification with a missing or non-Public modifier reports error 1057 inside a class body and a generic syntax error at global scope. --- dlls/vbscript/lex.c | 40 +++++++++++++++++++++++ dlls/vbscript/parser.y | 11 +++++-- dlls/vbscript/tests/lang.vbs | 62 ++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 3 deletions(-) diff --git a/dlls/vbscript/lex.c b/dlls/vbscript/lex.c index db1b8b7b355..fc0b8bff30c 100644 --- a/dlls/vbscript/lex.c +++ b/dlls/vbscript/lex.c @@ -141,6 +141,41 @@ static int check_keyword(parser_ctx_t *ctx, const WCHAR *word, const WCHAR **lva return 0; } +/* Check whether the next word matches the given lowercase keyword using + ASCII-only case-insensitive matching, without advancing the parser. */ +static BOOL next_word_is(const WCHAR *ptr, const WCHAR *end, const WCHAR *word) +{ + WCHAR c; + + while(*word) { + if(ptr == end) + return FALSE; + c = *ptr; + if(c >= 'A' && c <= 'Z') c += 'a' - 'A'; + if(c != *word) + return FALSE; + ptr++; + word++; + } + + return ptr == end || !is_identifier_char(*ptr); +} + +/* 'default' is a keyword only in class member declarations, where it is + followed by another modifier or by the member kind; anywhere else it is + an ordinary identifier. */ +static BOOL is_default_keyword(parser_ctx_t *ctx) +{ + const WCHAR *ptr = ctx->ptr; + + while(ptr < ctx->end && (*ptr == ' ' || *ptr == '\t')) + ptr++; + + return next_word_is(ptr, ctx->end, L"sub") || next_word_is(ptr, ctx->end, L"function") + || next_word_is(ptr, ctx->end, L"property") || next_word_is(ptr, ctx->end, L"public") + || next_word_is(ptr, ctx->end, L"private"); +} + static int check_keywords(parser_ctx_t *ctx, const WCHAR **lval) { int min = 0, max = ARRAY_SIZE(keywords)-1, r, i; @@ -502,9 +537,14 @@ static int parse_next_token(void *lval, unsigned *loc, parser_ctx_t *ctx) return parse_numeric_literal(ctx, lval); if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + const WCHAR *word_start = ctx->ptr; int ret = 0; if(ctx->last_token != '.' && ctx->last_token != tDOT) ret = check_keywords(ctx, lval); + if(ret == tDEFAULT && !is_default_keyword(ctx)) { + ctx->ptr = word_start; + ret = 0; + } if(!ret) return parse_identifier(ctx, lval); if(ret != tREM) diff --git a/dlls/vbscript/parser.y b/dlls/vbscript/parser.y index fea607a8d63..ce69fea8416 100644 --- a/dlls/vbscript/parser.y +++ b/dlls/vbscript/parser.y @@ -96,12 +96,15 @@ static statement_t *link_statements(statement_t*,statement_t*); /* Resolve dangling Else / End If conflicts via precedence: the empty * EndIf_opt rule gets lower precedence than tELSE and tEND so that - * shift (binding to innermost If) always wins. The remaining 8 + * shift (binding to innermost If) always wins. 8 of the remaining * shift/reduce conflicts are benign colon-chain ambiguities where - * bison's default shift gives the correct greedy behavior. */ + * bison's default shift gives the correct greedy behavior. The 9th is + * "Public Default" in a class body, where shifting tDEFAULT correctly + * prefers the default-member modifier over the member-variable error + * production. */ %nonassoc LOWER_THAN_ELSE %nonassoc tELSE tEND -%expect 8 +%expect 9 %union { const WCHAR *string; @@ -599,6 +602,8 @@ ClassBody | tDIM DimDeclList StSep ClassBody { $$ = add_dim_prop(ctx, $4, $2, 0); CHECK_ERROR; } | PropertyDecl { $$ = add_class_function(ctx, new_class_decl(ctx), $1); CHECK_ERROR; } | PropertyDecl StSep ClassBody { $$ = add_class_function(ctx, $3, $1); CHECK_ERROR; } + | tDEFAULT error { ctx->error_loc = @1; ctx->hres = MAKE_VBSERROR(VBSE_DEFAULT_MUST_BE_PUBLIC); YYABORT; } + | Storage tDEFAULT error { ctx->error_loc = @2; ctx->hres = MAKE_VBSERROR(VBSE_DEFAULT_MUST_BE_PUBLIC); YYABORT; } PropertyDecl : Storage_opt tPROPERTY tGET Identifier ArgumentsDecl_opt StSep BodyStatements tEND tPROPERTY diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index acdd992cbcf..2ddcde602cf 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -3826,6 +3826,68 @@ funcCalled = "" 'obj() 'call ok(funcCalled = "init","funcCalled=" & funcCalled) +class DefaultVarTest1 + Public Default +end class + +Set obj = New DefaultVarTest1 +obj.Default = 31 +call ok(obj.Default = 31, "obj.Default = " & obj.Default) + +class DefaultVarTest2 + Public Default(2), DefaultOther +end class + +Set obj = New DefaultVarTest2 +obj.Default(1) = 32 +obj.DefaultOther = 33 +call ok(obj.Default(1) = 32, "obj.Default(1) = " & obj.Default(1)) +call ok(obj.DefaultOther = 33, "obj.DefaultOther = " & obj.DefaultOther) + +class DefaultVarTest3 + Private Default + Public Sub SetIt(v) + Default = v + End Sub + Public Function GetIt + GetIt = Default + End Function +end class + +Set obj = New DefaultVarTest3 +obj.SetIt 34 +call ok(obj.GetIt = 34, "obj.GetIt = " & obj.GetIt) + +Dim Default +Default = 35 +call ok(Default = 35, "global Default = " & Default) + +sub TestDefaultDeclErrors + on error resume next + + err.clear : Execute "Class CDE1 : Default Sub S : End Sub : End Class" + call ok(err.number = 1057, "Default Sub err=" & err.number) + + err.clear : Execute "Class CDE2 : Default Function F : End Function : End Class" + call ok(err.number = 1057, "Default Function err=" & err.number) + + err.clear : Execute "Class CDE3 : Default Property Get P : P = 1 : End Property : End Class" + call ok(err.number = 1057, "Default Property Get err=" & err.number) + + err.clear : Execute "Class CDE4 : Private Default Sub S : End Sub : End Class" + call ok(err.number = 1057, "Private Default Sub err=" & err.number) + + err.clear : Execute "Class CDE5 : Default Public Sub S : End Sub : End Class" + call ok(err.number = 1057, "Default Public Sub err=" & err.number) + + err.clear : Execute "Class CDE6 : Default Private Sub S : End Sub : End Class" + call ok(err.number = 1057, "Default Private Sub err=" & err.number) + + err.clear : Execute "Default Sub S : End Sub" + call ok(err.number = 1002, "global Default Sub err=" & err.number) +end sub +call TestDefaultDeclErrors + with nothing end with -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11124
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)