killme2008 / killme2008/aviatorscript
Support property access after array index / method call / parenthesized expression (unified postfix chain)
- Dominant language
- Java
- Stars
- 5.2k
- Forks
- 906
- PR merge metrics
- No merged PRs in 30d
Description
## Background
5.4.4 (#693 / #704) let **variables** support dotted property chains + array indexing without the `#` prefix, e.g. `foo.bars[0].name` and `response.model_details[0].score` now evaluate correctly.
But the property chain breaks as soon as it follows an **array access / method call / parenthesized expression**. Verified on 5.4.4:
| Expression | Result |
|------------|--------|
| `o.oprninfos[0].name` (pure dotted-variable prefix) | ✅ OK |
| `xs[0].name` (single variable + index + property) | ❌ `unexpect token ']'` |
| `(xs[0]).name` | ❌ `unexpect token ')'` |
| `seq.get(o.oprninfos, 0).name` (property on a function return value) | ❌ `unexpect token ')'` |
| `take_while(coll, fn)[0].name` | ❌ `unexpect token ']'` |
See #606 for the user-facing case (taking an element's property from a `take_while` result — currently no pure-expression form).
## Root cause
Property access `.` is **not** a first-class parser / codegen operation today; it is folded into the "dotted variable name":
- `ExpressionLexer.scanVariable` scans `foo.bars[0].name` (with a mid-chain integer-literal index) into a **single `Variable` token**, lexeme = `"foo.bars[0].name"`.
- At evaluation time `AviatorJavaType` detects the `.` in the lexeme and delegates to `Reflector` for level-by-level resolution (full-key first, then property chain).
- Array access `[...]` (`CodeGenerator.onArray/onArrayIndexStart/onArrayIndexEnd`) and method call (`onMethodName/onMethodInvoke`) are **independent** runtime operations in the parser.
- `CodeGenerator` has **no property-access primitive** (nothing like `onGetProperty`).
So: as long as the chain prefix can be merged by the lexer into one dotted variable, it works; once the chain contains an array access / method call / parenthesized expression, the top of stack becomes an intermediate result, and the following `.prop` has no "get property of the top-of-stack object" operation to lower to. `parseFactor0` does not handle `.` after `ExpressionParser.arrayAccess()`, so it reports a syntax error.
`#704` already established that the lexer can only merge a mid-chain **integer literal** index (`Reflector.fastGetProperty`'s bracket handling only supports `Integer.valueOf`); dynamic indices / function return values cannot be merged at the lexer stage. So "make the lexer absorb more" is not a viable path.
## Proposed approach
Promote property access to a **first-class postfix operation** in the parser, unifying the primary suffix into:
```
primary ( '[' index ']' | '(' args ')' | '.' identifier )*
```
Changes:
1. Add a property-access primitive to `CodeGenerator`, e.g. `onGetProperty(Token propertyName)` — semantics: "pop the top-of-stack object, get its property named `propertyName`, push the result".
2. Implement in both backends:
- `InterpretCodeGenerator`: add a `GetPropertyIR`, resolved against the top-of-stack value in `InterpretContext` using the existing property-resolution logic.
- `ASMCodeGenerator`: emit bytecode that calls a runtime helper (reusing `Reflector`'s property resolution).
3. `ExpressionParser`: in the unified suffix loop after variable / method / array access in `parseFactor0`, handle `.` identifier and emit `onGetProperty`.
4. Compatibility & semantics:
- Keep the lexer's merge fast-path for **pure dotted-variable prefixes** to preserve the current full-key precedence semantics and performance; only fall back to the parser postfix when the chain contains an array access / method call / parenthesized expression.
- Reuse `Reflector`'s existing property resolution and `NoSuchProperty` semantics for consistency.
- Mind the interaction with future nil-safe navigation (#608) and options like `USE_USER_ENV_AS_TOP_ENV_DIRECTLY`.
## Impact / risk
- Touches the `CodeGenerator` interface + ASM / interpreter backends + the parser suffix chain — a medium-to-large change.
- The ASM backend needs a stable runtime helper and correct bytecode.
- Must cover: dynamic index `a[i].b`, multi-dimensional `a[0][1].b`, function return value `f(x).b`, parenthesized `(x).b`, and combinations with the existing dotted-variable chain.
- Payoff: supports any `primary(.prop | [idx] | (args))*` combination in one shot, removing the current "only pure dotted-variable prefix works" inconsistency.
Related to #606.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with ExpressionParser.parseFactor0 and the existing CodeGenerator array and method callbacks, then inspect InterpretCodeGenerator, ASMCodeGenerator, InterpretContext, and Reflector for property resolution. Add the postfix property operation across both backends while preserving the lexer’s pure dotted-variable fast path. Done means dynamic indexes, nested indexes, method and function results, parenthesized expressions, and mixed postfix chains resolve properties consistently.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100