fix: make parentheses open a nested indentation context - #283
Conversation
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: #282
A parenthesised group used to parse its body as a flat, whitespace separated list of values, so line breaks and indentation inside ( ) were ignored and records lost their boundaries. The group now opens a nested context that starts fresh at indentation level zero and is parsed by the same links rule as the root of the document, so 'array (a\n b\nc\n d)' yields the same links the root produces, nested under 'array'. A group holding a single link keeps that link, unless the link is itself a parenthesised group, so '(a b c)' and '((a b))' are unchanged.
The content of a parenthesized group is now parsed as a document of its own, starting fresh at indentation level zero, so line breaks and indentation mean the same thing inside ( ) as they do at the root. Quoted strings are skipped with the same multi-quote rules everywhere, which also makes a colon inside a group stop being mistaken for a top level id separator and keeps '(a b) (c d)' two groups instead of one.
The content of a parenthesized group is now parsed as a document of its own, starting fresh at indentation level zero, so line breaks and indentation mean the same thing inside ( ) as they do at the root. Quoted strings are skipped with the same multi-quote rules everywhere, which also makes a colon inside a group stop being mistaken for a top level id separator and keeps '(a b) (c d)' two groups instead of one.
Parentheses now start a fresh context at indentation level zero and are parsed by the same document rules as the root, so nested records keep their boundaries instead of collapsing into one flat list. Refs #282
Parentheses now start a fresh context at indentation level zero and are parsed by the same document rules as the root, so nested records keep their boundaries instead of collapsing into one flat list. Refs #282
Parentheses now open a nested context that starts fresh at indentation level zero and is parsed with the same rules as the root document, so indented records inside `( )` keep their boundaries instead of collapsing into one flat list. Blank lines are skipped between lines as well, at the root and inside parentheses, which keeps documents such as multi-line quoted references parseable. Fixes part of #282
Update the grammar specification to match the parsers: parentheses now open a nested context, so multiline_link, multiline_value_link and multiline_values are replaced by nested_group and nested_group_body, eol also matches the end of a nested group, and ENTER_NESTED_CONTEXT/EXIT_NESTED_CONTEXT are added to the indentation semantic actions. Bump the package versions so the release workflows publish the fix.
Working session summaryThe work is complete. PR #283 is ready for review with all CI green. What was doneRoot cause: Fix: Result: Single-line forms are unchanged because a body yielding one link collapses to that link — except when the body is itself a parenthesised group, which keeps Scope: all six implementations (JS/Peggy, Python, Go, Java, C#/Pegasus, Rust/nom), each with a reproducing test file, plus
This session finished the release prep: grammar docs rewritten ( Two things worth flagging:
This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost: $20.177393📊 Context and tokens usage:Claude Opus 5: (5 sub-sessions)
Total: (12.6K new + 471.8K cache writes + 19.2M cache reads) input tokens, 232.8K output tokens, $20.177393 cost 🤖 Models used:
📎 Log file uploaded as Gist (6677KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
✅ Ready to mergeThis pull request is now ready to be merged:
Monitored by hive-mind with --auto-restart-until-mergeable flag |
Fixes #282.
Problem
Indentation was structural at the root but ignored inside
( ), so aparenthesised group collapsed into one flat list of references and could not
express nested records.
Reproduction (before this PR):
At the root the same four lines yield four links —
(a),(a b),(c),(c d)— so the two contexts disagreed.Solution
(now opens a nested context: the indentation stack and base indentationare saved and reset, the body is parsed by the same document/links production
the root uses, and
)restores the enclosing context. Inside a group theclosing parenthesis ends the last line, just as a line break does at the root.
The body is flattened the same way the root document is. A body that yields a
single link collapses to that link, so
(a b c)and(a: b c)are unchanged.The one exception is a body that is itself a single parenthesised group, which
keeps
((a b))distinct from(a b).Blank lines no longer end a block, at the root and inside parentheses alike.
After this PR:
Changes
All six implementations were changed the same way:
js/src/grammar.pegjs,js/src/parser-generated.js,js/src/Parser.jspython/links_notation/parser.pygo/parser.gojava/src/main/java/io/github/linkfoundation/linksnotation/Parser.javacsharp/Link.Foundation.Links.Notation/Parser.peg,LinksGroup.cs,ILinksGroupListExtensions.csrust/links-notation/src/parser.rs,rust/links-notation/src/lib.rsEach implementation marks links that came from parentheses (
nestedkey inJS/Python/Go/Java,
LinksGroup.IsParenthesizedin C#,Link.nestedin Rust)so the single-link collapse can spare
((a b)).Docs updated to match the parsers:
multiline_link,multiline_value_linkandmultiline_valuesare replaced bynested_groupandnested_group_body,eolalso matches the end of a nested group, and
ENTER_NESTED_CONTEXT/EXIT_NESTED_CONTEXTwere added to the indentation semantic actions(
docs/grammar/links-notation.ebnf,GRAMMAR.md,grammar.lino,syntax-diagrams.md).Tests
A reproducing test suite was added for every implementation, covering the exact
cases from the issue plus record boundaries, deep nesting, the indented-id
syntax inside parentheses, blank lines inside parentheses, and the unchanged
single-line forms:
js/tests/NestedIndentation.test.jspython/tests/test_nested_indentation.pygo/nested_indentation_test.gojava/src/test/java/io/github/linkfoundation/linksnotation/NestedIndentationTest.javacsharp/Link.Foundation.Links.Notation.Tests/NestedIndentationTests.csrust/links-notation/tests/nested_indentation_tests.rsexperiments/issue-282/repro.jsreproduces the issue directly.Full suites, run locally on this branch:
gofmt,go vetclean)spotless:checkclean)cargo fmt --check,cargo clippy -D warningsclean)Release
Package versions bumped to
0.14.0(js/package.json,python/pyproject.toml,rust/links-notation/Cargo.toml,go/VERSION, the C#VersionPrefix) and theJava artifact from
0.1.0to0.2.0, so the release workflows publish the fix.CHANGELOG.mdrecords the change under[Unreleased].Known divergence (pre-existing, out of scope)
Rust's
single_line_value_linkcollapses a lone reference, so a documentcontaining just
aformats asawhere the other implementations produce(a); the same shows in(a)→aand(((a)))→((a)). This predatesthis PR — removing the collapse breaks 49 existing Rust tests — so it is
documented in
rust/links-notation/tests/nested_indentation_tests.rsratherthan changed here.