[PATCH v4 0/2] MR11120: Draft: vbscript: Lex a dot immediately followed by a digit as a numeric literal.
Native VBScript treats a dot directly followed by a digit as the start of a numeric literal even right after an identifier or closing bracket, so obj.Method.5 parses as a call with argument 0.5. -- v4: vbscript: Report specific compile errors for trailing tokens and missing identifiers. https://gitlab.winehq.org/wine/wine/-/merge_requests/11120
From: Francis De Brabandere <francisdb@gmail.com> Native VBScript treats a dot directly followed by a digit as the start of a numeric literal even right after an identifier or closing bracket, so obj.Method.5 parses as a call with argument 0.5. --- dlls/vbscript/lex.c | 8 +++++--- dlls/vbscript/tests/lang.vbs | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/dlls/vbscript/lex.c b/dlls/vbscript/lex.c index db1b8b7b355..ef20604c8db 100644 --- a/dlls/vbscript/lex.c +++ b/dlls/vbscript/lex.c @@ -534,7 +534,12 @@ static int parse_next_token(void *lval, unsigned *loc, parser_ctx_t *ctx) * We need to distinguish between '.' used as part of a member expression and * a beginning of a dot expression (a member expression accessing with statement * expression) and a floating point number like ".2" . + * + * A dot immediately followed by a digit is always a numeric literal, even + * right after an identifier: obj.method.5 parses as obj.method(0.5). */ + if('0' <= ctx->ptr[1] && ctx->ptr[1] <= '9') + return parse_numeric_literal(ctx, lval); c = ctx->ptr > ctx->code ? ctx->ptr[-1] : '\n'; if (is_identifier_char(c) || c == ')') { ctx->ptr++; @@ -548,9 +553,6 @@ static int parse_next_token(void *lval, unsigned *loc, parser_ctx_t *ctx) ctx->ptr++; return '.'; } - c = ctx->ptr[1]; - if('0' <= c && c <= '9') - return parse_numeric_literal(ctx, lval); ctx->ptr++; return tDOT; case '-': diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index acdd992cbcf..3c7dbdaa8e3 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -1907,6 +1907,17 @@ Set npObj = New NpCls CheckNpS "npObj.Check(10)+5", 15 CheckNpS "npObj.Check(10)*3", 30 +' A dot immediately followed by a digit is a numeric literal, not member access. +CheckNpS "NpS.5", 0.5 +CheckNpS "NpS.0", 0 +CheckNpS "NpS.5E1", 5 +CheckNpS "npObj.Check.5", 0.5 +CheckNpS "npObj.Check.0", 0 +CheckNpS "With npObj : .Check.5 : End With", 0.5 +CheckNpT "NpT.5,.25", 0.5, 0.25 +NpS.5 +Call ok(getVT(npArg) = "VT_R8*", "NpS.5: getVT(npArg) = " & getVT(npArg)) + Function ParenId(a) ParenId = a End Function -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11120
From: Francis De Brabandere <francisdb@gmail.com> Native VBScript reports error 1025 (Expected end of statement) for trailing tokens after a complete statement and error 1010 (Expected identifier) where an identifier is required, while Wine reported a generic syntax error for both. Map a literal at an unexpected position to error 1025 and rewrite the guess to error 1010 in error productions for contexts that expect an identifier. --- dlls/vbscript/parser.y | 31 ++++++++++++++++++++++++++++--- dlls/vbscript/tests/lang.vbs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/dlls/vbscript/parser.y b/dlls/vbscript/parser.y index fea607a8d63..2080126b278 100644 --- a/dlls/vbscript/parser.y +++ b/dlls/vbscript/parser.y @@ -26,6 +26,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(vbscript); static int parser_error(unsigned*,parser_ctx_t*,const char*); +static void override_generic_error(parser_ctx_t*,HRESULT); static void handle_isexpression_script(parser_ctx_t *ctx, expression_t *expr); @@ -311,6 +312,8 @@ SimpleStatement | tFOR tEACH MemberExpression error { ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_IN); YYABORT; } | tFOR tEACH MemberExpression tIN Expression StSep StatementsNl_opt error { ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_NEXT); YYABORT; } + | tFOR tEACH error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } + | tFOR error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } | tSELECT tCASE Expression StSep CaseClausules tEND tSELECT { $$ = new_select_statement(ctx, @$, $3, $5); } | tSELECT tCASE Expression StSep CaseClausules tEND error @@ -327,8 +330,10 @@ SimpleStatement MemberExpression : Identifier { $$ = new_member_expression(ctx, NULL, $1); CHECK_ERROR; } | CallExpression '.' tIdentifier { $$ = new_member_expression(ctx, $1, $3); CHECK_ERROR; } + | CallExpression '.' error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } | tDOT tIdentifier { expression_t *dot_expr = new_expression(ctx, EXPR_DOT, sizeof(*dot_expr)); CHECK_ERROR; $$ = new_member_expression(ctx, dot_expr, $2); CHECK_ERROR; } + | tDOT error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } Preserve_opt : /* empty */ { $$ = FALSE; } @@ -349,14 +354,13 @@ ReDimDecl ReDimDeclList : ReDimDecl { $$ = $1; } | ReDimDecl ',' ReDimDeclList { $1->next = $3; $$ = $1; } + | error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } DimDeclList : DimDecl { $$ = $1; } | DimDecl ',' DimDeclList { $1->next = $3; $$ = $1; } - | error { if(ctx->hres == MAKE_VBSERROR(VBSE_SYNTAX_ERROR)) - ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER); - YYABORT; } + | error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } DimDecl : Identifier { $$ = new_dim_decl(ctx, $1, @1, FALSE, NULL); CHECK_ERROR; } @@ -371,6 +375,7 @@ DimList ConstDeclList : ConstDecl { $$ = $1; } | ConstDecl ',' ConstDeclList { $1->next = $3; $$ = $1; } + | error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } ConstDecl : Identifier '=' Expression { $$ = new_const_decl(ctx, @1, $1, $3); CHECK_ERROR; } @@ -588,6 +593,7 @@ ClassDeclaration : 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; } + | tCLASS error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } ClassBody : /* empty */ { $$ = new_class_decl(ctx); } @@ -642,6 +648,8 @@ FunctionDecl { ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_LPAREN); YYABORT; } | Storage_opt tFUNCTION Identifier error { ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_LPAREN); YYABORT; } + | Storage_opt tSUB error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } + | Storage_opt tFUNCTION error { override_generic_error(ctx, MAKE_VBSERROR(VBSE_EXPECTED_IDENTIFIER)); YYABORT; } Storage_opt : /* empty*/ { $$ = 0; } @@ -714,6 +722,14 @@ static int parser_error(unsigned *loc, parser_ctx_t *ctx, const char *str) case tNEXT: ctx->hres = MAKE_VBSERROR(VBSE_UNEXPECTED_NEXT); break; + case tInt: + case tDouble: + case tString: + /* A literal can only start an expression, so a misplaced literal + means the statement should have ended before it. Error + productions override this in contexts expecting an identifier. */ + ctx->hres = MAKE_VBSERROR(VBSE_EXPECTED_END_OF_STATEMENT); + break; default: WARN("%s: %s\n", debugstr_w(ctx->code + *loc), debugstr_a(str)); ctx->hres = MAKE_VBSERROR(VBSE_SYNTAX_ERROR); @@ -724,6 +740,15 @@ static int parser_error(unsigned *loc, parser_ctx_t *ctx, const char *str) return 0; } +/* Replace the generic code chosen by parser_error with a context-specific + one, keeping specific errors reported by the lexer or other productions. */ +static void override_generic_error(parser_ctx_t *ctx, HRESULT hres) +{ + if(ctx->hres == MAKE_VBSERROR(VBSE_SYNTAX_ERROR) + || ctx->hres == MAKE_VBSERROR(VBSE_EXPECTED_END_OF_STATEMENT)) + ctx->hres = hres; +} + static void source_add_statement(parser_ctx_t *ctx, statement_t *stat) { if(!stat) diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 3c7dbdaa8e3..68fea1268dd 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -1918,6 +1918,37 @@ CheckNpT "NpT.5,.25", 0.5, 0.25 NpS.5 Call ok(getVT(npArg) = "VT_R8*", "NpS.5: getVT(npArg) = " & getVT(npArg)) +Sub CheckParseErr(src, expected) + On Error Resume Next + Err.Clear + Execute src + Dim e : e = Err.Number + On Error GoTo 0 + Call ok(e = expected, "parse error for " & src & ": err = " & e & " expected " & expected) +End Sub + +CheckParseErr "npArg = npObj.Check.5", 1025 +CheckParseErr "NpS.5.5", 1025 +CheckParseErr "npArg = (1).5", 1025 +CheckParseErr "npArg = 1.5.5", 1025 +CheckParseErr "npArg = 1 2", 1025 +CheckParseErr "NpS 1 2", 1025 +CheckParseErr "npArg = 1 ""x""", 1025 +CheckParseErr "npObj.Check. 5", 1010 +CheckParseErr "npObj.Check .", 1010 +CheckParseErr "npArg = npObj.", 1010 +CheckParseErr "npArg = .", 1010 +CheckParseErr "With npObj : . : End With", 1010 +CheckParseErr "Sub 5 : End Sub", 1010 +CheckParseErr "Function 5 : End Function", 1010 +CheckParseErr "Class 5 : End Class", 1010 +CheckParseErr "Const 5 = 1", 1010 +CheckParseErr "For 5 = 1 To 2 : Next", 1010 +CheckParseErr "For Each 5 In npArg : Next", 1010 +CheckParseErr "ReDim 5", 1010 +CheckParseErr "Dim 5", 1010 +CheckParseErr "Dim 1.5", 1010 + Function ParenId(a) ParenId = a End Function -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11120
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)