From: Francis De Brabandere <francisdb@gmail.com> --- dlls/vbscript/lex.c | 67 ++++++++++++++++++++---------------- dlls/vbscript/parse.h | 1 + dlls/vbscript/parser.y | 1 + dlls/vbscript/tests/lang.vbs | 36 +++++++++++++++++++ 4 files changed, 76 insertions(+), 29 deletions(-) diff --git a/dlls/vbscript/lex.c b/dlls/vbscript/lex.c index ef69d31cd53..670010363ae 100644 --- a/dlls/vbscript/lex.c +++ b/dlls/vbscript/lex.c @@ -575,36 +575,45 @@ int parser_lex(void *lval, unsigned *loc, parser_ctx_t *ctx) ctx->last_nl = ctx->ptr-ctx->code; } - /* Update is_statement_ctx: TRUE at the start of a statement, persists - * through dotted member chains (e.g. 'obj.prop.method'), FALSE - * once we see an operator or other expression token. - * An identifier keeps the state only if it follows a statement-start - * token or a dot (member chain); otherwise it's an argument. */ - switch(ret) { - case tNL: - case ':': - case tTHEN: - case tELSE: - case tELSEIF: - ctx->is_statement_ctx = TRUE; - break; - case tDOT: - case '.': - /* Dots in member chains keep current state. */ - break; - case tIdentifier: - /* First identifier after statement start or after a dot keeps TRUE. - * An identifier after another identifier (e.g. 'Sub Arg') means - * we're in argument context, so set to FALSE. */ - if(ctx->last_token != '.' && ctx->last_token != tDOT - && ctx->last_token != tNL && ctx->last_token != ':' - && ctx->last_token != tTHEN && ctx->last_token != tELSE - && ctx->last_token != tELSEIF) + /* Track paren depth and update is_statement_ctx only at depth 0. + * Tokens inside parentheses (e.g. the 'idx' in 'obj(idx).method') + * must not affect statement context tracking. */ + if(ret == '(' || ret == tEXPRLBRACKET) + ctx->paren_depth++; + else if(ret == ')' || ret == tEMPTYBRACKETS) { + if(ctx->paren_depth > 0) + ctx->paren_depth--; + } + + if(ctx->paren_depth == 0) { + switch(ret) { + case tNL: + case ':': + case tTHEN: + case tELSE: + case tELSEIF: + ctx->is_statement_ctx = TRUE; + break; + case tDOT: + case '.': + case ')': + case tEMPTYBRACKETS: + /* Dots and closing parens in member chains keep current state. */ + break; + case tIdentifier: + /* First identifier after statement start or after a dot keeps TRUE. + * An identifier after another identifier (e.g. 'Sub Arg') means + * we're in argument context, so set to FALSE. */ + if(ctx->last_token != '.' && ctx->last_token != tDOT + && ctx->last_token != tNL && ctx->last_token != ':' + && ctx->last_token != tTHEN && ctx->last_token != tELSE + && ctx->last_token != tELSEIF) + ctx->is_statement_ctx = FALSE; + break; + default: ctx->is_statement_ctx = FALSE; - break; - default: - ctx->is_statement_ctx = FALSE; - break; + break; + } } return (ctx->last_token = ret); } diff --git a/dlls/vbscript/parse.h b/dlls/vbscript/parse.h index df9243a3c13..b9fdcfb843a 100644 --- a/dlls/vbscript/parse.h +++ b/dlls/vbscript/parse.h @@ -302,6 +302,7 @@ typedef struct { int last_token; BOOL is_statement_ctx; + int paren_depth; unsigned last_nl; statement_t *stats; diff --git a/dlls/vbscript/parser.y b/dlls/vbscript/parser.y index cdf5f5901bb..4bc321e47c9 100644 --- a/dlls/vbscript/parser.y +++ b/dlls/vbscript/parser.y @@ -1232,6 +1232,7 @@ HRESULT parse_script(parser_ctx_t *ctx, const WCHAR *code, const WCHAR *delimite ctx->error_loc = -1; ctx->last_token = tNL; ctx->is_statement_ctx = TRUE; + ctx->paren_depth = 0; ctx->last_nl = 0; ctx->stats = ctx->stats_tail = NULL; ctx->class_decls = NULL; diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 901d217db56..b6d084ec2a5 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -1045,6 +1045,23 @@ 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 + Sub TestSubLocalVal x = false @@ -1442,6 +1459,25 @@ Call obj.test(obj) Call ok(getVT(test) = "VT_DISPATCH", "getVT(test) = " & getVT(test)) Call ok(Me is Test, "Me is not Test") +' Me(idx) should parse as accessing the default property of Me +' https://bugs.winehq.org/show_bug.cgi?id=58248 +'Class TestMeIndex +' Private arr_(1) +' Public Default Property Get Item(idx) +' Item = arr_(idx) +' End Property +' Public Sub SetVal(idx, val) +' arr_(idx) = val +' End Sub +' Public Sub TestAccess() +' SetVal 0, "hello" +' Call ok(Me(0) = "hello", "Me(0) = " & Me(0)) +' End Sub +'End Class +' +'Set obj = New TestMeIndex +'Call obj.TestAccess() + Const c1 = 1, c2 = 2, c3 = -3 Private Const c4 = 4 Public Const c5 = 5 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10244