diff --git a/.gitkeep b/.gitkeep index 487e7055..1676ccc0 100644 --- a/.gitkeep +++ b/.gitkeep @@ -1,4 +1,5 @@ # .gitkeep file auto-generated at 2026-08-28T06:09:39.810Z for PR creation at branch issue-288-cc91e23553e8 for issue https://github.com/link-foundation/links-notation/issues/288 # Updated: 2026-08-28T07:33:05.436Z # Updated: 2026-08-28T11:11:17.987Z -# Updated: 2026-08-28T12:23:46.151Z \ No newline at end of file +# Updated: 2026-08-28T12:23:46.151Z +# Updated: 2026-09-05T15:58:22.114Z \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 54518056..e2529eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 NuGet, Maven Central, Packagist and proxy.golang.org are each polled for the version just released, and the GitHub release is created only once the registry confirms it ([#290](https://github.com/link-foundation/links-notation/issues/290)) +- Rust: `ParseError::SyntaxError` carries where a document stopped parsing — + `offset`, `line`, `column`, `expected`, `found` and the offending line — and + `SyntaxError::summary()` and `SyntaxError::snippet()` render it. The crate + also exports `parse_document_with_diagnostics`, and + `cargo run --example parse_error_positions` prints what several broken + documents report ([#302](https://github.com/link-foundation/links-notation/issues/302)) +- JavaScript: `ParseError`, exported from the package, thrown by + `Parser.parse` when a document does not parse. It carries `offset`, `line`, + `column`, `found`, `lineText`, `snippet`, the generated parser's `location` + and the original error as `cause` ([#302](https://github.com/link-foundation/links-notation/issues/302)) +- C#: `ParseException`, thrown by `Parser.Parse`, carrying `Offset`, `Line`, + `Column`, `Found`, `LineText`, `Summary` and `Snippet`. The Pegasus grammar + turns on `@trace true` so `FurthestFailureTracer` can record the furthest + position any rule reached, which is the only position that says where a + backtracking parser gave up ([#302](https://github.com/link-foundation/links-notation/issues/302)) +- `experiments/issue-302/run.sh` asks all seven implementations about the same + five documents, four of which do not parse, and prints the answers next to + each other ([#302](https://github.com/link-foundation/links-notation/issues/302)) ### Changed - Every manifest checked against what the registries publish today and updated: @@ -143,6 +161,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 where a cancelled run should not continue ([#290](https://github.com/link-foundation/links-notation/issues/290)) - C# packaging and DocFX configuration are vendored in the repository instead of being fetched at run time ([#290](https://github.com/link-foundation/links-notation/issues/290)) +- Rust, JavaScript and C# report a failed parse the same way: a first line + saying the position and what was expected, then the offending line with a + caret under it. All three agree on the offset of every defect the comparison + script checks ([#302](https://github.com/link-foundation/links-notation/issues/302)) +- Rust: `lino!` used to panic with a fixed sentence that named neither the + reason nor the position when text that balances its parentheses is refused by + the parser at runtime; it now panics with the parse error ([#302](https://github.com/link-foundation/links-notation/issues/302)) +- C#: `Parser.Parse` throws `ParseException` rather than the generated parser's + `FormatException`. `ParseException` derives from `FormatException`, so callers + that catch the base type keep working ([#302](https://github.com/link-foundation/links-notation/issues/302)) ### Fixed - Docs: nested contexts were described in the English READMEs and in the root @@ -237,6 +265,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `experiments/test_coverage_data.json` into the `go` flag ([#290](https://github.com/link-foundation/links-notation/issues/290)) - Docs website: `docs/website/package.json` declares `"type": "module"`, so the ESM `vite.config.js` is no longer loaded as CommonJS and every build no longer warns ([#290](https://github.com/link-foundation/links-notation/issues/290)) +- Rust: a failed parse printed the raw `nom` error — + `Error(Error { input: "", code: Eof })` — + which named no line, no column and nothing that was expected, and grew with + the size of the document. It now says + `line 2, column 8: expected "(", a reference or end of line, found ":"` and + quotes one line. The reported position is the furthest any alternative + reached, so it points at the defect rather than at the start of the line the + parser last accepted ([#302](https://github.com/link-foundation/links-notation/issues/302)) +- C#: a failed parse said `Failed to parse 'document'.` and pinned its cursor at + line 1, column 1, because the generated parser backtracks out of the start + rule before it throws. It now reports the position the document really stopped + at ([#302](https://github.com/link-foundation/links-notation/issues/302)) +- JavaScript: the generated parser reported the position on the error object but + not in the message, so a caller that printed the message lost it + ([#302](https://github.com/link-foundation/links-notation/issues/302)) ## [0.11.2] - 2024-XX-XX diff --git a/README.md b/README.md index 5ab0bd1d..32aa8214 100644 --- a/README.md +++ b/README.md @@ -209,9 +209,9 @@ All seven language implementations (C#, JavaScript, Rust, Python, Go, Java, PHP) | Language | Tests | Test categories | | --- | --- | --- | | Python | 146 | 14 | -| JavaScript | 204 | 16 | -| Rust | 283 | 18 | -| C# | 196 | 17 | +| JavaScript | 214 | 17 | +| Rust | 299 | 19 | +| C# | 207 | 18 | | Go | 86 | 10 | | Java | 133 | 9 | | PHP | 183 | 16 | diff --git a/README.ru.md b/README.ru.md index a598636d..f58c130a 100644 --- a/README.ru.md +++ b/README.ru.md @@ -208,9 +208,9 @@ value ( | Язык | Тестов | Категорий тестов | | --- | --- | --- | | Python | 146 | 14 | -| JavaScript | 204 | 16 | -| Rust | 283 | 18 | -| C# | 196 | 17 | +| JavaScript | 214 | 17 | +| Rust | 299 | 19 | +| C# | 207 | 18 | | Go | 86 | 10 | | Java | 133 | 9 | | PHP | 183 | 16 | diff --git a/TEST_CASE_COMPARISON.md b/TEST_CASE_COMPARISON.md index b9fd470e..2efbee7a 100644 --- a/TEST_CASE_COMPARISON.md +++ b/TEST_CASE_COMPARISON.md @@ -19,9 +19,9 @@ Go keeps most of its tests in a single file rather than one file per category, s | Language | Total Tests | Test Categories | |----------|-------------|----------------| | Python | 146 | 14 | -| JavaScript | 204 | 16 | -| Rust | 283 | 18 | -| C# | 196 | 17 | +| JavaScript | 214 | 17 | +| Rust | 299 | 19 | +| C# | 207 | 18 | | Go | 86 | 10 | | Java | 133 | 9 | | PHP | 183 | 16 | @@ -473,6 +473,35 @@ Go keeps most of its tests in a single file rather than one file per category, s **Category totals:** Python: 2, JavaScript: 2, Rust: 2, C#: 2, Go: 0, Java: 0, PHP: 2 +## Parse Error Position + +| Test Name | Python | JavaScript | Rust | C# | Go | Java | PHP | +|-----------|---|---|---|---|---|---|---| +| a document that parses reports nothing | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L107) | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L178) | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L134) | ❌ | ❌ | ❌ | +| both entry points report the same position | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L155) | ❌ | ❌ | ❌ | ❌ | +| column counts characters rather than bytes | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L167) | ❌ | ❌ | ❌ | ❌ | +| does not mention the grammar internals test | ❌ | ❌ | ❌ | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L125) | ❌ | ❌ | ❌ | +| error display starts with the position | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L136) | ❌ | ❌ | ❌ | ❌ | +| keeps catching code that expects a format exception working test | ❌ | ❌ | ❌ | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L114) | ❌ | ❌ | ❌ | +| message of a long line stays a message | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L89) | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L119) | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L101) | ❌ | ❌ | ❌ | +| message points a caret at the offending character test | ❌ | ❌ | ❌ | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L76) | ❌ | ❌ | ❌ | +| message quotes one line rather than the rest of the document | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L79) | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L105) | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L88) | ❌ | ❌ | ❌ | +| message says where the document broke | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L70) | ❌ | ❌ | ❌ | ❌ | ❌ | +| nom internals stay out of the message | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L146) | ❌ | ❌ | ❌ | ❌ | +| offset agrees with the javascript port | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L29) | ❌ | ❌ | ❌ | ❌ | +| offset agrees with the other implementations | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L34) | ❌ | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L32) | ❌ | ❌ | ❌ | +| points at the defect rather than at the line it starts on | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L39) | ❌ | ❌ | ❌ | ❌ | +| reports an unmatched closing parenthesis | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L61) | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L77) | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L65) | ❌ | ❌ | ❌ | +| reports the end of the document when a group is never closed | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L51) | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L65) | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L53) | ❌ | ❌ | ❌ | +| reports the line a late defect is on | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L43) | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L49) | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L43) | ❌ | ❌ | ❌ | +| reports the line and column of the defect | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L24) | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L18) | [✅](csharp/Link.Foundation.Links.Notation.Tests/ParseErrorPositionTests.cs#L20) | ❌ | ❌ | ❌ | +| says what could have stood there | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L58) | ❌ | ❌ | ❌ | ❌ | +| snippet points a caret at the offending character | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L98) | ❌ | ❌ | ❌ | ❌ | +| summary reads as a sentence | ❌ | ❌ | [✅](rust/links-notation/tests/parse_error_position_tests.rs#L88) | ❌ | ❌ | ❌ | ❌ | +| the location the parser used to report is still there | ❌ | [✅](js/tests/ParseErrorPosition.test.js#L100) | ❌ | ❌ | ❌ | ❌ | ❌ | + +**Category totals:** Python: 0, JavaScript: 10, Rust: 16, C#: 11, Go: 0, Java: 0, PHP: 0 + ## Single Line Parser | Test Name | Python | JavaScript | Rust | C# | Go | Java | PHP | @@ -821,6 +850,30 @@ Go keeps most of its tests in a single file rather than one file per category, s - nestedlinknoid - nestedlinkwithquotedvalues +**Parse Error Position** (22 missing): +- adocumentthatparsesreportsnothing +- bothentrypointsreportthesameposition +- columncountscharactersratherthanbytes +- doesnotmentionthegrammarinternals +- errordisplaystartswiththeposition +- keepscatchingcodethatexpectsaformatexceptionworking +- messageofalonglinestaysamessage +- messagepointsacaretattheoffendingcharacter +- messagequotesonelineratherthantherestofthedocument +- messagesayswherethedocumentbroke +- nominternalsstayoutofthemessage +- offsetagreeswiththejavascriptport +- offsetagreeswiththeotherimplementations +- pointsatthedefectratherthanatthelineitstartson +- reportsanunmatchedclosingparenthesis +- reportstheendofthedocumentwhenagroupisneverclosed +- reportsthelinealatedefectison +- reportsthelineandcolumnofthedefect +- sayswhatcouldhavestoodthere +- snippetpointsacaretattheoffendingcharacter +- summaryreadsasasentence +- thelocationtheparserusedtoreportisstillthere + **Single Line Parser** (4 missing): - parsequotedreferencesvalues - quotedreferencesinlink @@ -878,7 +931,7 @@ Go keeps most of its tests in a single file rather than one file per category, s - vecstringtoanonymouslink - vecstrtoanonymouslink -**Total missing: 264 tests** +**Total missing: 286 tests** ### JavaScript Missing Tests @@ -1062,6 +1115,20 @@ Go keeps most of its tests in a single file rather than one file per category, s - nestedlinknoid - nestedlinkwithquotedvalues +**Parse Error Position** (12 missing): +- bothentrypointsreportthesameposition +- columncountscharactersratherthanbytes +- doesnotmentionthegrammarinternals +- errordisplaystartswiththeposition +- keepscatchingcodethatexpectsaformatexceptionworking +- messagepointsacaretattheoffendingcharacter +- nominternalsstayoutofthemessage +- offsetagreeswiththejavascriptport +- pointsatthedefectratherthanatthelineitstartson +- sayswhatcouldhavestoodthere +- snippetpointsacaretattheoffendingcharacter +- summaryreadsasasentence + **Single Line Parser** (4 missing): - parsequotedreferencesvalues - quotedreferencesinlink @@ -1119,7 +1186,7 @@ Go keeps most of its tests in a single file rather than one file per category, s - vecstringtoanonymouslink - vecstrtoanonymouslink -**Total missing: 207 tests** +**Total missing: 219 tests** ### Rust Missing Tests @@ -1265,6 +1332,14 @@ Go keeps most of its tests in a single file rather than one file per category, s - nestedlinknoid - nestedlinkwithquotedvalues +**Parse Error Position** (6 missing): +- doesnotmentionthegrammarinternals +- keepscatchingcodethatexpectsaformatexceptionworking +- messagepointsacaretattheoffendingcharacter +- messagesayswherethedocumentbroke +- offsetagreeswiththeotherimplementations +- thelocationtheparserusedtoreportisstillthere + **Single Line Parser** (4 missing): - parsequotedreferencesvalues - quotedreferencesinlink @@ -1275,7 +1350,7 @@ Go keeps most of its tests in a single file rather than one file per category, s - namedtupletolink - tupletolink -**Total missing: 128 tests** +**Total missing: 134 tests** ### C# Missing Tests @@ -1469,6 +1544,19 @@ Go keeps most of its tests in a single file rather than one file per category, s - nestedlinknoid - nestedlinkwithquotedvalues +**Parse Error Position** (11 missing): +- bothentrypointsreportthesameposition +- columncountscharactersratherthanbytes +- errordisplaystartswiththeposition +- messagesayswherethedocumentbroke +- nominternalsstayoutofthemessage +- offsetagreeswiththejavascriptport +- pointsatthedefectratherthanatthelineitstartson +- sayswhatcouldhavestoodthere +- snippetpointsacaretattheoffendingcharacter +- summaryreadsasasentence +- thelocationtheparserusedtoreportisstillthere + **Single Line Parser** (3 missing): - parsequotedreferencesvalues - quotedreferencesinlink @@ -1523,7 +1611,7 @@ Go keeps most of its tests in a single file rather than one file per category, s - vecstringtoanonymouslink - vecstrtoanonymouslink -**Total missing: 214 tests** +**Total missing: 225 tests** ### Go Missing Tests @@ -1806,6 +1894,30 @@ Go keeps most of its tests in a single file rather than one file per category, s - nestedselfreferencedobjectinpairvalue - selfreferenceasdirectchildworkscorrectly +**Parse Error Position** (22 missing): +- adocumentthatparsesreportsnothing +- bothentrypointsreportthesameposition +- columncountscharactersratherthanbytes +- doesnotmentionthegrammarinternals +- errordisplaystartswiththeposition +- keepscatchingcodethatexpectsaformatexceptionworking +- messageofalonglinestaysamessage +- messagepointsacaretattheoffendingcharacter +- messagequotesonelineratherthantherestofthedocument +- messagesayswherethedocumentbroke +- nominternalsstayoutofthemessage +- offsetagreeswiththejavascriptport +- offsetagreeswiththeotherimplementations +- pointsatthedefectratherthanatthelineitstartson +- reportsanunmatchedclosingparenthesis +- reportstheendofthedocumentwhenagroupisneverclosed +- reportsthelinealatedefectison +- reportsthelineandcolumnofthedefect +- sayswhatcouldhavestoodthere +- snippetpointsacaretattheoffendingcharacter +- summaryreadsasasentence +- thelocationtheparserusedtoreportisstillthere + **Single Line Parser** (26 missing): - bugtest 1 - linkwithid @@ -1885,7 +1997,7 @@ Go keeps most of its tests in a single file rather than one file per category, s - vecstringtoanonymouslink - vecstrtoanonymouslink -**Total missing: 320 tests** +**Total missing: 342 tests** ### Java Missing Tests @@ -2142,6 +2254,30 @@ Go keeps most of its tests in a single file rather than one file per category, s - nestedselfreferencedobjectinpairvalue - selfreferenceasdirectchildworkscorrectly +**Parse Error Position** (22 missing): +- adocumentthatparsesreportsnothing +- bothentrypointsreportthesameposition +- columncountscharactersratherthanbytes +- doesnotmentionthegrammarinternals +- errordisplaystartswiththeposition +- keepscatchingcodethatexpectsaformatexceptionworking +- messageofalonglinestaysamessage +- messagepointsacaretattheoffendingcharacter +- messagequotesonelineratherthantherestofthedocument +- messagesayswherethedocumentbroke +- nominternalsstayoutofthemessage +- offsetagreeswiththejavascriptport +- offsetagreeswiththeotherimplementations +- pointsatthedefectratherthanatthelineitstartson +- reportsanunmatchedclosingparenthesis +- reportstheendofthedocumentwhenagroupisneverclosed +- reportsthelinealatedefectison +- reportsthelineandcolumnofthedefect +- sayswhatcouldhavestoodthere +- snippetpointsacaretattheoffendingcharacter +- summaryreadsasasentence +- thelocationtheparserusedtoreportisstillthere + **Single Line Parser** (9 missing): - linkwithoutidmultiline - linkwithoutidsingleline @@ -2204,7 +2340,7 @@ Go keeps most of its tests in a single file rather than one file per category, s - vecstringtoanonymouslink - vecstrtoanonymouslink -**Total missing: 277 tests** +**Total missing: 299 tests** ### PHP Missing Tests @@ -2396,6 +2532,30 @@ Go keeps most of its tests in a single file rather than one file per category, s - nestedlinknoid - nestedlinkwithquotedvalues +**Parse Error Position** (22 missing): +- adocumentthatparsesreportsnothing +- bothentrypointsreportthesameposition +- columncountscharactersratherthanbytes +- doesnotmentionthegrammarinternals +- errordisplaystartswiththeposition +- keepscatchingcodethatexpectsaformatexceptionworking +- messageofalonglinestaysamessage +- messagepointsacaretattheoffendingcharacter +- messagequotesonelineratherthantherestofthedocument +- messagesayswherethedocumentbroke +- nominternalsstayoutofthemessage +- offsetagreeswiththejavascriptport +- offsetagreeswiththeotherimplementations +- pointsatthedefectratherthanatthelineitstartson +- reportsanunmatchedclosingparenthesis +- reportstheendofthedocumentwhenagroupisneverclosed +- reportsthelinealatedefectison +- reportsthelineandcolumnofthedefect +- sayswhatcouldhavestoodthere +- snippetpointsacaretattheoffendingcharacter +- summaryreadsasasentence +- thelocationtheparserusedtoreportisstillthere + **Single Line Parser** (16 missing): - linkwithoutidmultiline - linkwithoutidsingleline @@ -2465,5 +2625,5 @@ Go keeps most of its tests in a single file rather than one file per category, s - vecstringtoanonymouslink - vecstrtoanonymouslink -**Total missing: 227 tests** +**Total missing: 249 tests** diff --git a/benchmarks/BENCHMARK_RESULTS.md b/benchmarks/BENCHMARK_RESULTS.md index 0d2068ff..d5f1e83d 100644 --- a/benchmarks/BENCHMARK_RESULTS.md +++ b/benchmarks/BENCHMARK_RESULTS.md @@ -298,4 +298,4 @@ Sequences of unlimited length (N-tuples). Shape: Tuples. Source: [`datasets/sequ - **The rest of the prompt.** Only the document is counted: no system prompt, no code fence, no schema description and no instructions. Those add a cost every format pays alike. - **Speed and memory.** This is a size benchmark. How fast each format parses is a separate question with a separate answer. -Generated by `links-notation-benchmark` against `links-notation` 0.17.0. +Generated by `links-notation-benchmark` against `links-notation` 0.18.0. diff --git a/benchmarks/java/pom.xml b/benchmarks/java/pom.xml index 39b681c9..8229a0d6 100644 --- a/benchmarks/java/pom.xml +++ b/benchmarks/java/pom.xml @@ -23,7 +23,7 @@ io.github.link-foundation links-notation - 0.17.0 + 0.18.0