Skip to content

Skip comments wherever a list element can start, not only at top level - #150

Open
MesTTo wants to merge 2 commits into
trueagi-io:mainfrom
MesTTo:pr/parser-comment-before-bracket
Open

Skip comments wherever a list element can start, not only at top level#150
MesTTo wants to merge 2 commits into
trueagi-io:mainfrom
MesTTo:pr/parser-comment-before-bracket

Conversation

@MesTTo

@MesTTo MesTTo commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

(foo (bar baz) ; a comment with the closing bracket on the next line fails to parse with UnexpectedRightBracket.

sexpr's top-level loop treats ; as trivia. The loop reading a list's elements skips whitespace only, so a comment there looks like the start of a child. The recursive call skips the comment, finds ) where an expression was expected, and returns UnexpectedRightBracket. The arity had already been reserved for a child that parsed nothing.

The corpus already carries the case. Since 536c22b, differential/corpus/wiki/comment_before_closing_bracket.mm2 has shipped marked ;; @skip parser bug: this shape gives UnexpectedRightBracket, with the wiki block it was distilled from left unported.

The fix

Both loops have to skip the same set, so they now share one skip_trivia and the two open-coded copies are gone. The list loop no longer inspects what it is looking at: skip trivia, and until the bracket, parse a child and bump the arity.

A one-line b';' arm in the inner match fixes the symptom instead, and leaves the two loops free to drift apart again, which is what produced the bug. Say the word and I will reduce it to that; the corpus change is the same either way.

A ; with no whitespace before it is still part of the symbol being read. Changing that would change how every symbol containing one parses, and is a decision about the language rather than a fix to this loop.

Tests

The @skip is gone, and the entry now pins its parsed space, (foo (bar baz)), rather than only checking that the two engines agree.

reachability_p2_12.mm2 is new: MORK.wiki "MM2 tutorial: Reachability P2" code block 12, the 59-line shape the one-liner was distilled from, which cannot load today. A one-liner passing says nothing about the shape it came from. It pins @steps 0 and @expect-steps 0, following unify/large_statement.mm2's precedent for a parse-only entry.

differential/run.py --slow --generated
  on main:   both programs exit 101 (UnexpectedRightBracket)
  with this: 112 ok, 0 failed, 2 skipped

Deleting the b';' arm from skip_trivia breaks the corpus, so the new code is exercised. cargo test passes for mork and mork-frontend.

`(foo (bar baz)  ; a comment` with the closing bracket on the next line
fails to parse with UnexpectedRightBracket.

`sexpr`'s top-level loop treats `;` as trivia. The loop reading a list's
elements skips whitespace only, so a comment there looks like the start
of a child. The recursive call skips the comment, finds `)` where an
expression was expected, and returns UnexpectedRightBracket. The arity
had already been reserved for a child that parsed nothing.

Both loops have to skip the same set, so they now share one
`skip_trivia` and the two open-coded copies are gone. The list loop no
longer inspects what it is looking at: skip trivia, and until the
bracket, parse a child and bump the arity. A one-line `b';'` arm in the
inner match fixes the symptom instead, and leaves the two loops free to
drift apart again, which is what produced the bug.

A `;` with no whitespace before it is still part of the symbol being
read. Changing that would change how every symbol containing one
parses, and is a decision about the language rather than a fix to this
loop.

The corpus already carries the case. Since 536c22b,
`comment_before_closing_bracket.mm2` has shipped marked

  ;; @Skip   parser bug: this shape gives UnexpectedRightBracket

with the wiki block it was distilled from left unported. The `@skip` is
gone, the entry pins its parsed space rather than only checking that
the two engines agree, and the 59-line block is here too, because a
one-liner passing says nothing about the shape it came from. It is
MORK.wiki "MM2 tutorial: Reachability P2" block 12, which cannot load
today.

  differential/run.py --slow --generated
    on main:   both programs exit 101 (UnexpectedRightBracket)
    with this: 112 ok, 0 failed, 2 skipped

Deleting the `b';'` arm from `skip_trivia` breaks the corpus, so the
new code is exercised. cargo test passes for mork and mork-frontend.
@MesTTo
MesTTo force-pushed the pr/parser-comment-before-bracket branch from d99c2dc to 46ffe72 Compare August 23, 2026 00:36
@MesTTo MesTTo changed the title Skip a comment wherever a list element could start, not only at the top level Skip comments wherever a list element can start, not only at top level Aug 23, 2026
@Adam-Vandervorst

Copy link
Copy Markdown
Collaborator

What's the impact on parsing performance? Doesn't this technically call the helper one too many times? Also, inlining.

…parse cost

The bracket loop skips trivia to decide whether it is looking at `)`, and then the child it
recurses into skipped again on entry. The second skip always found nothing, because the first
had just left the cursor on a non-trivia byte. The version this replaces avoided that by
inspecting the byte itself before recursing, and only recursing when it was an element.

Splitting `sexpr` into the skipping entry point and `sexpr_at`, which takes the cursor already
on an element's first byte, keeps `sexpr`'s contract -- call it anywhere and it finds the next
element -- while letting the bracket loop skip exactly once per child.

Parsing, min-of-3 instructions:u over 120,000 statements, output identical:

                     before this commit   after
  no comments               +3.775%      +0.458%
  a comment per line        +3.750%      +0.472%
  deeply nested             +3.571%      +0.716%

The residue is the fix itself: recognising a comment where an element can start means testing
for `;` at each element boundary, which the loop this replaces did not do, and is exactly why
it could not read `(foo (bar baz)  ; comment` with the bracket on the next line.

`#[inline]` on `skip_trivia` was measured separately and moves nothing -- 1,361,840,573 against
1,361,851,696 instructions:u, inside run-to-run spread -- so it is already being inlined and is
not carried here.
@MesTTo

MesTTo commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

All three measured; you were right on the second one, and it was most of the cost.

Parsing performance. min-of-3 instructions:u, mork run FILE --steps 0, 120,000
statements per input, output byte-identical throughout:

input as you reviewed it after e28140b
no comments (12.3 MB) +3.775% +0.458%
a comment on every line (13.9 MB) +3.750% +0.472%
deeply nested (6.6 MB) +3.571% +0.716%

"one too many times" — yes, exactly once too many, per element. The bracket loop skips
trivia to decide whether it is looking at ), and then the child it recurses into skipped
again on entry, always finding nothing. The code I replaced did not have that redundancy: it
inspected the byte itself and only recursed when it was an element, so I introduced it by
hoisting the skip out.

Fixed by splitting sexpr into the skipping entry point and sexpr_at, which takes the cursor
already on an element's first byte. sexpr's contract is unchanged — call it anywhere and it
finds the next element — and the bracket loop now skips exactly once per child. That is 3.3 of
the 3.8 points.

Inlining. Measured on its own and it moves nothing: #[inline] on skip_trivia gives
1,361,840,573 against 1,361,851,696 instructions:u, well inside run-to-run spread. It is a
trait default method used from the same crate and was already being inlined, so I have not
carried the attribute.

The residual 0.46% is the fix itself rather than overhead I can remove. Recognising a
comment where an element can start means testing for ; at each element boundary, which the
loop this replaces did not do — which is precisely why it could not read

(foo (bar baz)  ; a comment here, and the closing bracket on the next line
)

and why MM2-tutorial: Reachability P2 block 12 does not load on main. Both are pinned in the
corpus here, the first flipping from @skip to @expect.

differential/run.py --slow --generated is 112 ok / 0 failed on the branch tip.

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.

2 participants