Repository navigation
Conversation
…erant one The ANTLR parser threw away whole subtrees on common in-progress edits (e.g. a bare `@`, `$`, or `allowing` with nothing after it), and its lexer modes could even garble the rest of the statement. The LSP needs a meaningful AST for programs that are being typed. The new recursive descent parser produces the same AST types, never gives up (missing tokens are reported without being consumed, unexpected tokens are skipped up to a token an enclosing construct can handle), and reports clearer errors (e.g. "expected `destination`, found `destinaton`"). It can also report what it expected at a given cursor position (ParseForCompletion), which is the basis for LSP completion. The ANTLR parser is kept as a test oracle (internal/parser/internal/legacy): on ~45k programs (the repository corpus plus mutations), both parsers accept the same programs and build the same ASTs. The only intended difference is that a line comment at the end of the file no longer needs a trailing newline. Claude-Session: https://claude.ai/code/session_013WheaNzvuvbKPqmRbyREjs
Built on top of the parser's completion context: - keywords valid at the cursor (e.g. `allowing`, `with`, `destination` after a source account) - variables, filtered by the type expected at the cursor (account in a source, portion in an allotment, function parameter types, ...) - builtin functions (as snippets), filtered by return type - account and asset literals already used in the document, including `@world` - variable interpolation in account literals (`@users:$id`) - variable types in the vars block, and statement snippets - signature help for builtin function calls, including incomplete ones Also tests that analysing every prefix of the corpus programs (i.e. typing them one char at a time) never panics, plus a fuzz test. Claude-Session: https://claude.ai/code/session_013WheaNzvuvbKPqmRbyREjs
| i := start | ||
|
|
||
| end := -1 | ||
| for i < len(l.src) { |
There was a problem hiding this comment.
🟠 [major] Avoid quadratic scanning of unterminated nested comments
An input containing many consecutive /* openers and no closing delimiter makes matchFrom recursively scan every remaining suffix. Although results are memoized, each invocation still walks its entire suffix after nested matches return -1 (lines 466–480), so total work is quadratic in the number of openers, with linear recursion depth. Such an incomplete document is a normal input to the new fault-tolerant parser; a sufficiently large pasted comment can monopolize the LSP process rather than promptly producing an unterminated-comment diagnostic.
Suggestion: Replace suffix-by-suffix recursive matching with a bounded/linear-time delimiter scan or precomputed matching information while preserving the documented ANTLR fallback semantics. Add a regression test or benchmark with a large sequence of unmatched comment openers to cover recovery complexity.
Why
The ANTLR parser throws away whole subtrees on typical in-progress edits, so the LSP has almost nothing to work with while the user is typing. Some examples with the old parser:
send [COIN $SentValue,Source,Destinationall nilsource = { @a @ }destinationparsed as an account inside the source (the@lexer mode leaks), destination lostsource = @a allowing@ais validsend $m + (…rest of program(read as a parenthesized expression, source and destination lostExpression completion needs both a meaningful partial AST and "what is expected at the cursor", which ANTLR's Go runtime can't provide cleanly (no maintained antlr4-c3 port).
What
Parser (
internal/parser): a hand-written lexer + recursive descent parser that produces the same AST types, so the checker, interpreter and compiler are untouched.destination,),},send, …).expected `destination`, found `destinaton`(and the misspelled clause is still parsed).ParseForCompletion(src, cursor)records what the parser expected at the cursor: keywords, expression slots with the expected type, account parts, var types, statements, and the enclosing function calls.LSP:
textDocument/completion(triggered on$,@,:):@users:$idinterpolation, var types, statement snippetstextDocument/signatureHelpfor builtin calls, including unclosed ones.How it's verified
internal/parser/internal/legacy. The corpus is every.numfixture, every Numscript string literal in the repo's Go tests, and handwritten edge cases (precedence,[$asset -1]juxtaposition, nested comments, …). Every program is also mutated by deleting, duplicating and swapping words, for ~45k programs in total. Whenever ANTLR accepts a program, the new parser accepts it and builds an identical AST. Whenever ANTLR rejects a program, the new parser rejects it too.FuzzAnalysisfuzz test (1.6M executions locally, no crash).Behaviour changes to review
mcp_impl,specs_formatsnapshot).//comment on the last line no longer needs a trailing newline. That was a parse error before.Comment.Contentno longer includes the trailing newline.unifyNodeWithfor that.Follow-ups
legacypackage (and the root.g4grammars?) once this has been released for a while.CONTRIBUTING.mdexplains that for now both must be updated when the syntax changes.ParseForCompletion: completing metadata keys and feature flags in#![feature(...)].https://claude.ai/code/session_013WheaNzvuvbKPqmRbyREjs