Skip to content

feat: brace-mode multi-statement support for expression evaluator (#8) - #13

Merged
novoj merged 4 commits into
mainfrom
feat/eval-block-mode
May 24, 2026
Merged

novoj merged 4 commits into
mainfrom
feat/eval-block-mode

Conversation

@novoj

@novoj novoj commented May 24, 2026

Copy link
Copy Markdown
Contributor

Re-targets the block-mode work onto main. PR #11 was merged into its stacked
base fix/eval-target-package-wrapper (PR #10) rather than main; since PR #10
had already merged to main, PR #11's content dead-ended on that branch and never
reached main (so it missed the 2.3.0 release). This PR brings it to main with the
correct base.

What it adds

Brace-mode multi-statement support for the expression evaluator (closes #8): an
input wrapped in { ... } is compiled as a statement body rather than a single
expression, letting you declare locals and run multiple statements.

Conflict resolution carried in

This branch already has main merged in (commit aa73206) with the one semantic
conflict in JdiExpressionEvaluator.java resolved: main's PR #10 refactor moved
the field-rewrite into evaluateInPackage(), so PR #11's !isBlockMode(...) guard
was ported there (the bare-identifier rewrite is skipped in block mode, where a
statement body may declare locals the identifier-level rewriter would mistake for
field references).

Verification

./mvnw -pl jdwp-mcp-server -am test → BUILD SUCCESS, 935 tests pass, NullAway/ECJ
clean. Diff vs main is exactly the block-mode feature (evaluator + JDWPTools +
rewrite test).

novoj added 4 commits May 23, 2026 14:22
Closes #8

User input wrapped in `{ ... }` is now spliced into the wrapper's method
body verbatim instead of being treated as a single expression. The mode
is detected by a tokenizer-aware brace-match that ignores braces inside
string/char/text-block literals, so `"{x}".length()` still routes
through expression mode while `{ try { return foo(); } catch (...) }`
routes through block mode.

In block mode the user is responsible for `return X;` statements; a
trailing `return null;` fallthrough guard keeps the wrapper type-correct
if the user block doesn't end with a return.

This automatically extends to every eval call site routing through
`evaluate()` -- jdwp_evaluate_expression, jdwp_assert_expression,
breakpoint conditions, logpoint expressions and conditions, exception
logpoints, field watchpoint conditions/expressions, and watchers.

Tool descriptions on all 8 eval-bearing tool methods/params updated
with a `(supports `{ ...; return X; }` block syntax)` suffix so the
feature is discoverable from any eval-shaped param in isolation.

Tests added: 9 in JdiExpressionEvaluatorRewriteTest covering block-mode
detection, nested braces, brace-inside-string-literal escapes, and
expression-mode fallback for `{x}.foo()`-style inputs.
- Javadoc on generateSourceCode and isBlockMode: replace malformed
  `{@code {}` and `{@code }}` inline tags (the `}` closes the inline
  tag prematurely) with plain '{' and '}' references in prose.
- isBlockMode now skips Java line (`//…`) and block (`/*…*/`) comments
  alongside string/char/text-block literals. Without this, valid blocks
  like `{ // }` + body or `{ /* } */ … }` would be misclassified as
  expression-mode and fail compilation.
  - Unterminated comment that swallows the trailing `}` is detected
    (skip past `n`) and reported as not-block-mode rather than left
    silently miscounted.
- generateSourceCode block-mode body now wraps the user body in
  `if (__mcpFallthroughGuard) { ... } return null;` where the guard
  is a non-final local set to true. JLS §15.29 says only final
  variables initialised with constant expressions are constant
  expressions, so the compiler cannot prove the post-if `return null;`
  unreachable. Fixes the "unreachable code" compile error when the
  user's block ends with an explicit `return X;` (the previous trailing
  `return null;` was statically unreachable in that case).
- evaluate() now SKIPS the bare-field-reference rewrite when the input
  is in block mode. The rewriter is identifier-level and cannot tell
  a field reference from a local-variable declaration; in block mode
  `int count = 1; ...` would otherwise become
  `int _this.count = 1; ...`. Block-mode users are expected to use
  explicit `this.field` / `_this.field` references; the keyword
  rewrite in generateSourceCode still handles `this.field` for them.

Tests added (4 in JdiExpressionEvaluatorRewriteTest):
- isBlockMode skips `}` inside a `/* ... */` block comment
- isBlockMode skips `}` inside a `//` line comment
- isBlockMode handles mixed nested braces + comment-buried braces
- isBlockMode tolerates unterminated block comment (returns false
  because the comment swallows the trailing `}`)
…branch

The literal was `{a;}+b;` which has no trailing `}`, so isBlockMode rejected
it at the cheap end-char guard and never reached the early `depth == 0`
branch the test claims to cover. Use `{a;}+b;}` so the input clears the
end-char guard and the rejection comes from the brace-balance walk.
# Conflicts:
#	jdwp-mcp-server/src/main/java/one/edee/mcp/jdwp/evaluation/JdiExpressionEvaluator.java
Copilot AI review requested due to automatic review settings May 24, 2026 09:32
@novoj
novoj merged commit 2cccdc8 into main May 24, 2026
1 check failed
novoj added a commit that referenced this pull request May 24, 2026
Every eval-bearing surface (jdwp_evaluate_expression, jdwp_assert_expression,
breakpoint conditions, logpoint expressions/conditions, exception logpoints,
field-watchpoint conditions/expressions, and watchers) now accepts a
brace-wrapped statement body in addition to a single expression. An input that
starts with `{` and ends with the matching `}` is spliced into the wrapper
method verbatim; the user writes `return X;` and a trailing `return null;`
fallthrough guard keeps the wrapper type-correct. This unlocks intermediate
locals, try/catch, early returns, and loops at a breakpoint. Resolves #8.

Mode detection is tokenizer-aware: braces inside string/char/text-block
literals don't trigger block mode, so `"{x}".length()` stays in expression
mode. All eight eval-bearing tool params advertise the block syntax in their
descriptions so it's discoverable in isolation. The bare-field `this.field`
auto-rewrite is skipped in block mode (the identifier-level rewriter can't
distinguish a field reference from a local declaration); block-mode users use
explicit this.field / _this.field.

Lands the work from PR #11, which had merged into its stacked base
(fix/eval-target-package-wrapper) rather than main and so missed 2.3.0;
re-targeted to main via PR #13.
@novoj
novoj removed the request for review from Copilot May 24, 2026 09:55
@novoj
novoj deleted the feat/eval-block-mode branch July 12, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expression evaluator is single-statement only — add { block } mode

1 participant