[PATCH v3 0/1] MR10692: vbscript: Fix Sub first argument parentheses handling (alt parser-only).
Alternative to MR !10244 that does the disambiguation entirely in the parser grammar. The lex keeps master's behavior (no state tracking, no paren lookahead); a new SimpleStatement production handles the 'CallExpression Arguments OP Expression [, rest]' pattern and rewrites it as 'f((args) OP expr)' via the action. Cost: +2 shift/reduce conflicts (12 vs baseline 10). Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=54177 -- v3: vbscript: Fix Sub first argument parentheses handling. https://gitlab.winehq.org/wine/wine/-/merge_requests/10692
From: Francis De Brabandere <francisdb@gmail.com> Disambiguation is handled entirely in the parser grammar. A new SimpleStatement production handles the 'CallExpression Arguments OP Expression [, rest]' pattern and rewrites it as 'f((args) OP expr)' via the action. Cost: +2 shift/reduce conflicts (12 vs baseline 10). Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=54177 --- dlls/vbscript/parser.y | 39 +++++++- dlls/vbscript/tests/lang.vbs | 178 +++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 1 deletion(-) diff --git a/dlls/vbscript/parser.y b/dlls/vbscript/parser.y index e716bf98d3a..2230171c61b 100644 --- a/dlls/vbscript/parser.y +++ b/dlls/vbscript/parser.y @@ -153,7 +153,8 @@ static statement_t *link_statements(statement_t*,statement_t*); %type <func_decl> FunctionDecl PropertyDecl %type <elseif> ElseIfs_opt ElseIfs ElseIf %type <class_decl> ClassDeclaration ClassBody -%type <uint> Storage Storage_opt IntegerValue +%type <uint> Storage Storage_opt IntegerValue SubFirstArgOp +%type <expression> SubFirstArgRest %type <dim_decl> DimDeclList DimDecl MemberDeclList MemberDecl %type <dim_list> DimList %type <redim_decl> ReDimDeclList ReDimDecl @@ -223,6 +224,17 @@ Statement SimpleStatement : CallExpression ArgumentList_opt { call_expression_t *call_expr = make_call_expression(ctx, $1, $2); CHECK_ERROR; $$ = new_call_statement(ctx, @$, &call_expr->expr); CHECK_ERROR; } + | CallExpression Arguments SubFirstArgOp Expression SubFirstArgRest + { expression_t *first_arg, *combined; + call_expression_t *call_expr; + first_arg = new_unary_expression(ctx, EXPR_BRACKETS, $2); + CHECK_ERROR; + combined = new_binary_expression(ctx, $3, first_arg, $4); + CHECK_ERROR; + combined->next = $5; + call_expr = new_call_expression(ctx, $1, combined); + CHECK_ERROR; + $$ = new_call_statement(ctx, @$, &call_expr->expr); CHECK_ERROR; } | tCALL UnaryExpression { $$ = new_call_statement(ctx, @$, $2); CHECK_ERROR; } | CallExpression '=' Expression { $$ = new_assign_statement(ctx, @$, $1, $3); CHECK_ERROR; } @@ -328,6 +340,31 @@ Step_opt : /* empty */ { $$ = NULL;} | tSTEP Expression { $$ = $2; } +SubFirstArgRest + : /* empty */ { $$ = NULL; } + | ',' ArgumentList { $$ = $2; } + +SubFirstArgOp + : '+' { $$ = EXPR_ADD; } + | '-' { $$ = EXPR_SUB; } + | '*' { $$ = EXPR_MUL; } + | '/' { $$ = EXPR_DIV; } + | '\\' { $$ = EXPR_IDIV; } + | '^' { $$ = EXPR_EXP; } + | '&' { $$ = EXPR_CONCAT; } + | '<' { $$ = EXPR_LT; } + | '>' { $$ = EXPR_GT; } + | tLTEQ { $$ = EXPR_LTEQ; } + | tGTEQ { $$ = EXPR_GTEQ; } + | tNEQ { $$ = EXPR_NEQUAL; } + | tMOD { $$ = EXPR_MOD; } + | tAND { $$ = EXPR_AND; } + | tOR { $$ = EXPR_OR; } + | tXOR { $$ = EXPR_XOR; } + | tEQV { $$ = EXPR_EQV; } + | tIMP { $$ = EXPR_IMP; } + | tIS { $$ = EXPR_IS; } + IfStatement : tIF Expression tTHEN tNL StSep_opt StatementsNl_opt ElseIfs_opt Else_opt tEND tIF { $$ = new_if_statement(ctx, @$, $2, $6, $7, $8); CHECK_ERROR; } diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index d59f05c54a0..c598857553b 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -1217,6 +1217,184 @@ Call TestSubExit2 TestSubMultiArgs 1, 2, 3, 4, 5 Call TestSubMultiArgs(1, 2, 3, 4, 5) +Sub TestSubParenExpr(a, b) + Call ok(a=16, "a = " & a) + Call ok(b=7, "b = " & b) +End Sub + +TestSubParenExpr (2) * 8, 7 +TestSubParenExpr 8 * (2), 7 + +Sub TestSubParenExprAdd(a, b) + Call ok(a=6, "a = " & a) + Call ok(b=7, "b = " & b) +End Sub + +TestSubParenExprAdd (2) + 4, 7 +TestSubParenExprAdd 4 + (2), 7 + +Sub TestSubParenExprNoSpace(a) + Call ok(a=6, "a = " & a) +End Sub + +TestSubParenExprNoSpace(10 \ 2) + 1 + +' Regression test: function call with space before ( in expression context +' e.g. x = (CInt (2) + 1) * 3 must parse and evaluate correctly +x = CInt (2) + 1 +Call ok(x = 3, "CInt (2) + 1 = " & x) +x = (CInt (2) + 1) * 3 +Call ok(x = 9, "(CInt (2) + 1) * 3 = " & x) + +' Regression test: function call with space before ( and * in expression context +' e.g. x = CInt (2) * 3 must treat CInt (2) as a function call, not expression grouping +x = CInt (2) * 3 +Call ok(x = 6, "CInt (2) * 3 = " & x) + +' Test member expression in statement context: obj.Method (x) * y, z +Class TestObjParenExpr + Sub Check(a, b) + Call ok(a=16, "obj a = " & a) + Call ok(b=7, "obj b = " & b) + End Sub +End Class + +Dim objParenExpr +Set objParenExpr = New TestObjParenExpr +objParenExpr.Check (2) * 8, 7 + +Sub TestSubParenExprConcat(a, b) + Call ok(a="helloworld", "a = " & a) + Call ok(b=7, "b = " & b) +End Sub + +TestSubParenExprConcat ("hello") & "world", 7 + +' Test: function call as argument with & after paren must be a call, not grouping +' e.g. TestSub Mid ("hello", 2) & "x" should call TestSub with "ellox" +' Mid("hello", 2) returns "ello", & "x" concatenates to "ellox" +Sub TestSubArgCallConcat(a) + Call ok(a="ellox", "a = " & a) +End Sub + +TestSubArgCallConcat Mid ("hello", 2) & "x" + +' Test: obj(idx).method (expr) * val, y in statement context +' The (expr) after .method must be expression grouping, not call paren +Class TestIndexedObjParenExpr + Public arr_(1) + Public Sub Init() + Set arr_(0) = New TestObjParenExpr + End Sub + Public Default Property Get Item(idx) + Set Item = arr_(idx) + End Property +End Class + +Dim idxObj +Set idxObj = New TestIndexedObjParenExpr +Call idxObj.Init() +idxObj(0).Check (2) * 8, 7 + +' No-space variants of Sub-first-arg paren pattern. +' On native VBScript, `S(x) OP y` in statement context treats the whole +' `S(x) OP y` as a call to S with argument `(x) OP y` — for every binary +' operator except `=` (parsed as assignment). +' Each case is wrapped in Execute so parse failures of one don't abort the rest. +Dim npArg, npArgA, npArgB +Sub NpS(a) + npArg = a +End Sub +Sub NpT(a, b) + npArgA = a + npArgB = b +End Sub + +Sub CheckNpS(src, expected) + npArg = Empty + On Error Resume Next + Err.Clear + Execute src + Dim e : e = Err.Number + On Error GoTo 0 + Call ok(e = 0, "parse error for " & src & ": err=" & e) + If e = 0 Then Call ok(npArg = expected, src & ": npArg = " & npArg & " expected " & expected) +End Sub + +CheckNpS "NpS(10)+5", 15 +CheckNpS "NpS(10)-3", 7 +CheckNpS "NpS(10)*3", 30 +CheckNpS "NpS(10)/2", 5 +CheckNpS "NpS(10)\3", 3 +CheckNpS "NpS(10)^2", 100 +CheckNpS "NpS(""hi"")&""!""", "hi!" +CheckNpS "NpS(10) Mod 3", 1 +CheckNpS "NpS(10)<>10", False +CheckNpS "NpS(10)<5", False +CheckNpS "NpS(10)>5", True +CheckNpS "NpS(10)<=10", True +CheckNpS "NpS(10)>=5", True +CheckNpS "NpS(1) And 1", 1 +CheckNpS "NpS(0) Or 1", 1 +CheckNpS "NpS(1) Xor 1", 0 +CheckNpS "NpS(1) Eqv 1", -1 +CheckNpS "NpS(1) Imp 1", -1 +CheckNpS "NpS(Nothing) Is Nothing", True + +' Two-arg form: S(x) OP y, z — result of `(x) OP y` is first arg, z is second. +Sub CheckNpT(src, expectedA, expectedB) + npArgA = Empty + npArgB = Empty + On Error Resume Next + Err.Clear + Execute src + Dim e : e = Err.Number + On Error GoTo 0 + Call ok(e = 0, "parse error for " & src & ": err=" & e) + If e = 0 Then Call ok(npArgA = expectedA and npArgB = expectedB, _ + src & ": a=" & npArgA & " b=" & npArgB) +End Sub + +CheckNpT "NpT(10)+5, 7", 15, 7 +CheckNpT "NpT(10)*3, 7", 30, 7 +CheckNpT "NpT(""hi"")&""!"", 7", "hi!", 7 + +' Member expression: obj.Method(x) OP y — no space, same pattern. +Class NpCls + Sub Check(a) + npArg = a + End Sub +End Class +Dim npObj +Set npObj = New NpCls +CheckNpS "npObj.Check(10)+5", 15 +CheckNpS "npObj.Check(10)*3", 30 + +Function ParenId(a) + ParenId = a +End Function + +Dim parenRes +parenRes = 0 +If False Then +ElseIf ParenId(3) <= ParenId(4) + 0.1 Then + parenRes = 1 +End If +Call ok(parenRes = 1, "ElseIf f(x) <= f(y) + z: parenRes = " & parenRes) + +parenRes = 0 +If False Then +ElseIf ParenId(3) * 2 > 0 Then + parenRes = 1 +End If +Call ok(parenRes = 1, "ElseIf f(x) * y > z: parenRes = " & parenRes) + +Dim parenOuter, parenInner +ReDim parenOuter(3) +parenInner = Array(1, 3, 5, 7) +parenOuter(parenInner(1) And 1) = 99 +Call ok(parenOuter(1) = 99, "outer(inner(i) And k) = v: parenOuter(1) = " & parenOuter(1)) + Sub TestSubLocalVal x = false Call ok(not x, "local x is not false?") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10692
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)