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