From 8bbc2575c286421488b2d7e460bfc6493018ed4d Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sat, 2 May 2026 18:22:34 +0000 Subject: [PATCH 01/19] feat: add characters only rule into simplification array --- generate-railroad.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/generate-railroad.js b/generate-railroad.js index 9ba7b55..e6b7324 100644 --- a/generate-railroad.js +++ b/generate-railroad.js @@ -15,7 +15,12 @@ const INLINE_HEX_RULES = [ "multi-line-comment-start", "multi-line-comment-end", "asterisk", - "escape" + "escape", + "single-line-comment-start", + "decimal-point", + "minus", + "plus", + "zero", ]; function escapeRegExp(value) { From 4cdc01d4071d61e4aed4f105dcdfcf268e1ec4bd Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sat, 2 May 2026 19:17:21 +0000 Subject: [PATCH 02/19] feat: remove LS and PS notions from abnf --- grammar/jsonc.abnf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grammar/jsonc.abnf b/grammar/jsonc.abnf index d8ff0af..39e4378 100644 --- a/grammar/jsonc.abnf +++ b/grammar/jsonc.abnf @@ -23,8 +23,8 @@ comment = single-line-comment / multi-line-comment source-character = %x00-10FFFF ; Comment terminators and sequences (based on ECMAScript line terminators) -comment-terminator = %x0A / %x0D / %x2028 / %x2029 ; LF / CR / LS / PS -comment-terminator-sequence = %x0D.0A / %x0A / %x0D / %x2028 / %x2029 +comment-terminator = %x0A / %x0D ; LF / CR +comment-terminator-sequence = %x0D.0A / %x0A / %x0D ; Single-line comment: starts with //, continues until line ending ; Terminator is not part of the comment body. @@ -32,7 +32,7 @@ comment-terminator-sequence = %x0D.0A / %x0A / %x0D / %x2028 / %x2029 single-line-comment-start = %x2F.2F ; // double solidus single-line-comment-end = comment-terminator-sequence single-line-comment = single-line-comment-start *single-line-comment-char [ single-line-comment-end ] -single-line-comment-char = %x00-09 / %x0B-0C / %x0E-2027 / %x202A-10FFFF ; Any source character except comment terminators +single-line-comment-char = %x00-09 / %x0B-0C / %x0E-10FFFF ; Any source character except comment terminators ; Multi-line comment: /* ... */ ; Cannot be nested. The first */ closes the comment. From 6356c82e476437261fe3ebb7cd11d4eddf1c0e53 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sat, 2 May 2026 19:17:41 +0000 Subject: [PATCH 03/19] refactor: manke abnf self-contained --- grammar/jsonc.abnf | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/grammar/jsonc.abnf b/grammar/jsonc.abnf index 39e4378..a04f3ed 100644 --- a/grammar/jsonc.abnf +++ b/grammar/jsonc.abnf @@ -1,9 +1,8 @@ ; JSONC grammar with comments support (RFC 8259 extended with JavaScript-style comments) ; ; Notes: -; - Rule names and structure follow RFC 8259 ABNF snippets. -; - DIGIT and HEXDIG are core rules from RFC 5234. -; - comments are an extension not in RFC 8259. +; - Rule names and structure follow RFC 8259 ABNF. +; - Comments are an extension not in RFC 8259. ; - Trailing commas are NOT supported in this grammar. ; A JSONC-text is a serialized value surrounded by optional whitespace and comments. @@ -75,14 +74,23 @@ array = begin-array [ value *( value-separator value ) ] end-array ; Numbers number = [ minus ] int [ frac ] [ exp ] decimal-point = %x2E ; . +digit0-9 = %x30-39 ; 0-9 digit1-9 = %x31-39 ; 1-9 + e = %x65 / %x45 ; e E -exp = e [ minus / plus ] 1*DIGIT -frac = decimal-point 1*DIGIT -int = zero / ( digit1-9 *DIGIT ) +exp = e [ minus / plus ] 1*digit0-9 +frac = decimal-point 1*digit0-9 +int = zero / ( digit1-9 *digit0-9 ) minus = %x2D ; - plus = %x2B ; + zero = %x30 ; 0 +hexdigit = digit0-9 / + %x41 / ; A + %x42 / ; B + %x43 / ; C + %x44 / ; D + %x45 / ; E + %x46 ; F ; Strings string = quotation-mark *char quotation-mark @@ -97,7 +105,7 @@ char = unescaped / %x6E / ; n line feed U+000A %x72 / ; r carriage return U+000D %x74 / ; t tab U+0009 - %x75 4HEXDIG ; uXXXX U+XXXX + %x75 4hexdigit ; uXXXX U+XXXX ) escape = %x5C ; \ From d718091e5556c8e4f2ecb717924034afc58d9259 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sat, 2 May 2026 20:32:25 +0000 Subject: [PATCH 04/19] chore: update railroad diagram --- grammar/railroad-diagram.html | 127 ++++++++++------------- submodules/railroad-diagram-generator-js | 2 +- 2 files changed, 54 insertions(+), 75 deletions(-) diff --git a/grammar/railroad-diagram.html b/grammar/railroad-diagram.html index 27256f2..f01faf9 100644 --- a/grammar/railroad-diagram.html +++ b/grammar/railroad-diagram.html @@ -56,28 +56,19 @@

source-character

comment-terminator

-
comment-terminator := %x0A / %x0D / %x2028 / %x2029          ; LF / CR / LS / PS
-
- -<LF><CR><LS><PS> +
comment-terminator := %x0A / %x0D                ; LF / CR
+
+ +<LF><CR>

comment-terminator-sequence

-
comment-terminator-sequence := %x0D.0A / %x0A / %x0D / %x2028 / %x2029
-
- -<CR><LF><LF><CR><LS><PS> - -
-
-
-

single-line-comment-start

-
single-line-comment-start := %x2F.2F             ; // double solidus
-
- -// +
comment-terminator-sequence := %x0D.0A / %x0A / %x0D
+
+ +<CR><LF><LF><CR>
@@ -92,19 +83,19 @@

single-line-comment-end

single-line-comment

-
single-line-comment := single-line-comment-start *single-line-comment-char [ single-line-comment-end ]
-
- -single-line-comment-startsingle-line-comment-charsingle-line-comment-end +
single-line-comment := "//" *single-line-comment-char [ single-line-comment-end ]
+
+ +//single-line-comment-charsingle-line-comment-end

single-line-comment-char

-
single-line-comment-char := %x00-09 / %x0B-0C / %x0E-2027 / %x202A-10FFFF ; Any source character except comment terminators
-
- -%x00-09%x0B-0C%x0E-2027%x202A-10FFFF +
single-line-comment-char := %x00-09 / %x0B-0C / %x0E-10FFFF ; Any source character except comment terminators
+
+ +%x00-09%x0B-0C%x0E-10FFFF
@@ -274,19 +265,19 @@

array

number

-
number := [ minus ] int [ frac ] [ exp ]
-
- -minusintfracexp +
number := [ "-" ] int [ frac ] [ exp ]
+
+ +-intfracexp
-
-

decimal-point

-
decimal-point := %x2E        ; .
-
- -. +
+

digit0-9

+
digit0-9 := %x30-39          ; 0-9
+
+ +%x30-39
@@ -310,55 +301,43 @@

e

exp

-
exp := e [ minus / plus ] 1*DIGIT
-
+
exp := e [ "-" / "+" ] 1*digit0-9
+
-eminusplusDIGIT +e-+digit0-9

frac

-
frac := decimal-point 1*DIGIT
-
- -decimal-pointDIGIT +
frac := "." 1*digit0-9
+
+ +.digit0-9

int

-
int := zero / ( digit1-9 *DIGIT )
-
- -zerodigit1-9DIGIT +
int := "0" / ( digit1-9 *digit0-9 )
+
+ +0digit1-9digit0-9
-
-

minus

-
minus := %x2D                ; -
-
- -- - -
-
-
-

plus

-
plus := %x2B                 ; +
-
- -+ - -
-
-
-

zero

-
zero := %x30                 ; 0
-
- -0 +
+

hexdigit

+
hexdigit := digit0-9 /
+  %x41 /                    ; A
+  %x42 /                    ; B
+  %x43 /                    ; C
+  %x44 /                    ; D
+  %x45 /                    ; E
+  %x46                      ; F
+
+ +digit0-9ABCDEF
@@ -383,11 +362,11 @@

char

%x6E / ; n line feed U+000A %x72 / ; r carriage return U+000D %x74 / ; t tab U+0009 - %x75 4HEXDIG ; uXXXX U+XXXX + %x75 4hexdigit ; uXXXX U+XXXX ) -
- -unescaped\"" quotation mark U+0022\\ reverse solidus U+005C// solidus U+002Fbb backspace U+0008ff form feed U+000Cnn line feed U+000Arr carriage return U+000Dtt tab U+0009uuXXXX U+XXXXHEXDIGHEXDIGHEXDIGHEXDIG +
+ +unescaped\"quotation mark U+0022\reverse solidus U+005C/solidus U+002Fbbackspace U+0008fform feed U+000Cnline feed U+000Arcarriage return U+000Dttab U+0009uU+XXXXhexdigithexdigithexdigithexdigit
diff --git a/submodules/railroad-diagram-generator-js b/submodules/railroad-diagram-generator-js index e3e2166..411f16c 160000 --- a/submodules/railroad-diagram-generator-js +++ b/submodules/railroad-diagram-generator-js @@ -1 +1 @@ -Subproject commit e3e21669af406cad1c94c75774d891a9f6447363 +Subproject commit 411f16c244f8290dc45407a1337c339fe2009a6b From 3ba8b39f0c7f2be3ef9df100f9adfc8532fb9c0b Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sat, 2 May 2026 20:43:51 +0000 Subject: [PATCH 05/19] feat: replace false, true, null with simple characters representation --- generate-railroad.js | 68 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/generate-railroad.js b/generate-railroad.js index e6b7324..157f228 100644 --- a/generate-railroad.js +++ b/generate-railroad.js @@ -23,6 +23,15 @@ const INLINE_HEX_RULES = [ "zero", ]; +// Inline selected rule references as quoted literals in specific target rules. +// Add more mappings here to reuse this transformation pattern. +const INLINE_LITERAL_REFS = [ + { + targetRule: "value", + referencedRules: ["false", "true", "null"], + }, +]; + function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } @@ -41,6 +50,20 @@ function decodeAbnfHexSequence(value) { return String.fromCodePoint(...bytes); } +function getHexRuleLiteral(source, ruleName) { + const escapedRuleName = escapeRegExp(ruleName); + const ruleRegex = new RegExp( + `^\\s*${escapedRuleName}\\s*=\\s*(%x[0-9A-Fa-f]+(?:\\.[0-9A-Fa-f]+)*)\\b.*$`, + "m", + ); + const ruleMatch = source.match(ruleRegex); + if (!ruleMatch) { + throw new Error(`Rule ${ruleName} was not found.`); + } + + return decodeAbnfHexSequence(ruleMatch[1]); +} + function inlineHexRuleAsLiteral(source, ruleName) { const escapedRuleName = escapeRegExp(ruleName); const ruleRegex = new RegExp( @@ -95,6 +118,46 @@ function inlineHexRuleAsLiteral(source, ruleName) { .join("\n"); } +function inlineLiteralRefsInTargetRule(source, targetRule, referencedRules) { + const escapedTargetRule = escapeRegExp(targetRule); + const targetRuleRegex = new RegExp(`^(\\s*${escapedTargetRule}\\s*=\\s*)(.*)$`, "m"); + const match = source.match(targetRuleRegex); + if (!match) { + throw new Error(`Rule ${targetRule} was not found.`); + } + + const targetRulePrefix = match[1]; + const targetRuleRhs = match[2]; + + let updatedRhs = targetRuleRhs; + for (const referencedRule of referencedRules) { + const literalValue = getHexRuleLiteral(source, referencedRule); + const replacementLiteral = `"${literalValue.replace(/"/g, '\\"')}"`; + const referencedRuleRegex = new RegExp( + `(? { + const match = line.match(/^\s*([A-Za-z][A-Za-z0-9-]*)\s*=/); + if (!match) { + return true; + } + return !removalSet.has(match[1]); + }) + .join("\n"); +} + function processAbnfSource(source) { let processed = source; @@ -102,6 +165,11 @@ function processAbnfSource(source) { processed = inlineHexRuleAsLiteral(processed, ruleName); } + for (const { targetRule, referencedRules } of INLINE_LITERAL_REFS) { + processed = inlineLiteralRefsInTargetRule(processed, targetRule, referencedRules); + processed = removeRuleDefinitions(processed, referencedRules); + } + return processed; } From b054a9e6ee45f29c2fe5e003a44f3e87b546eec1 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sat, 2 May 2026 20:52:18 +0000 Subject: [PATCH 06/19] feat: normalize quotation-marrk in railroad diagram --- generate-railroad.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/generate-railroad.js b/generate-railroad.js index 157f228..9058b33 100644 --- a/generate-railroad.js +++ b/generate-railroad.js @@ -17,6 +17,7 @@ const INLINE_HEX_RULES = [ "asterisk", "escape", "single-line-comment-start", + "quotation-mark", "decimal-point", "minus", "plus", @@ -78,10 +79,10 @@ function inlineHexRuleAsLiteral(source, ruleName) { const hexSequence = ruleMatch[1]; const literalChars = decodeAbnfHexSequence(hexSequence); - // For backslash or other problematic characters, keep them as hex format - // ABNF doesn't support backslash escaping in quoted strings + // Keep hex format for characters that cannot be represented safely + // as a single ABNF quoted string literal. let replacement; - if (literalChars === "\\") { + if (literalChars === "\\" || literalChars === '"') { replacement = hexSequence; } else { // For other characters, escape only double quotes (not backslashes) From f49f339fd89983fe10ba77e0d1c369176b802270 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sat, 2 May 2026 21:36:55 +0000 Subject: [PATCH 07/19] feat: change nodes appearance to match ECMA-404 --- grammar/railroad-diagram.css | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/grammar/railroad-diagram.css b/grammar/railroad-diagram.css index 87adf27..f644eb7 100644 --- a/grammar/railroad-diagram.css +++ b/grammar/railroad-diagram.css @@ -41,17 +41,19 @@ .textbox { stroke-width: var(--text-border); stroke: black; - rx: calc(var(--grid-size) * var(--text-box-corner-radius-factor)); - ry: calc(var(--grid-size) * var(--text-box-corner-radius-factor)); + rx: 0; + ry: 0; fill: none; } .textbox.terminal { - fill: rgb(200, 200, 200); + fill: rgb(205, 240, 205); + rx: calc(var(--grid-size) * var(--text-box-corner-radius-factor)); + ry: calc(var(--grid-size) * var(--text-box-corner-radius-factor)); } .textbox.nonterminal { - fill: rgb(210, 210, 210); + fill: rgb(205, 240, 205); } .textbox-text { @@ -64,6 +66,7 @@ } .textbox-text.nonterminal { + font-weight: 700; text-decoration: underline; /* Underline non-terminals */ cursor: pointer; /* Show it's clickable */ pointer-events: auto; /* Ensure text can receive click events */ @@ -176,7 +179,7 @@ } .textbox.nonterminal:hover { - fill: rgb(190, 190, 190); + fill: rgb(190, 230, 190); stroke-width: calc(var(--text-border) * 1.5); } From be43cef53c85afced829f514ce1181787e2012be Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 04:26:10 +0000 Subject: [PATCH 08/19] refactor: make railroad more similar to ECMA's --- grammar/jsonc.abnf | 31 ++++++++++++++++--------------- grammar/railroad-diagram.css | 6 ++++++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/grammar/jsonc.abnf b/grammar/jsonc.abnf index a04f3ed..dd7b6a9 100644 --- a/grammar/jsonc.abnf +++ b/grammar/jsonc.abnf @@ -57,7 +57,7 @@ name-separator = wsc %x3A wsc ; : colon value-separator = wsc %x2C wsc ; , comma ; Any JSON value -value = false / null / true / object / array / number / string +value = object / array / number / string / true / false / null ; Literal names (boolean values and null) false = %x66.61.6C.73.65 ; false @@ -74,38 +74,39 @@ array = begin-array [ value *( value-separator value ) ] end-array ; Numbers number = [ minus ] int [ frac ] [ exp ] decimal-point = %x2E ; . -digit0-9 = %x30-39 ; 0-9 +digit = %x30-39 ; 0-9 digit1-9 = %x31-39 ; 1-9 e = %x65 / %x45 ; e E -exp = e [ minus / plus ] 1*digit0-9 -frac = decimal-point 1*digit0-9 -int = zero / ( digit1-9 *digit0-9 ) +exp = e [ minus / plus ] 1*digit +frac = decimal-point 1*digit +int = zero / ( digit1-9 *digit ) minus = %x2D ; - plus = %x2B ; + zero = %x30 ; 0 -hexdigit = digit0-9 / +hexdigit = digit / %x41 / ; A %x42 / ; B %x43 / ; C %x44 / ; D %x45 / ; E %x46 ; F +four-hexdigits = 4hexdigit ; Strings string = quotation-mark *char quotation-mark char = unescaped / escape ( - %x22 / ; " quotation mark U+0022 - %x5C / ; \ reverse solidus U+005C - %x2F / ; / solidus U+002F - %x62 / ; b backspace U+0008 - %x66 / ; f form feed U+000C - %x6E / ; n line feed U+000A - %x72 / ; r carriage return U+000D - %x74 / ; t tab U+0009 - %x75 4hexdigit ; uXXXX U+XXXX + %x22 / ; " quotation mark U+0022 + %x5C / ; \ reverse solidus U+005C + %x2F / ; / solidus U+002F + %x62 / ; b backspace U+0008 + %x66 / ; f form feed U+000C + %x6E / ; n line feed U+000A + %x72 / ; r carriage return U+000D + %x74 / ; t tab U+0009 + %x75 four-hexdigits ; uXXXX U+XXXX ) escape = %x5C ; \ diff --git a/grammar/railroad-diagram.css b/grammar/railroad-diagram.css index f644eb7..8ea78d3 100644 --- a/grammar/railroad-diagram.css +++ b/grammar/railroad-diagram.css @@ -76,6 +76,12 @@ fill: #0066cc; /* Blue on hover */ } +.terminal-label { + font-family: Arial, sans-serif; + font-size: 12px; + fill: #555; +} + /* CSS-based debug visualization using HTML overlays */ .debug-overlay { position: absolute !important; From b56113dcddb705f64293467bdc168178b0d03eac Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 04:26:29 +0000 Subject: [PATCH 09/19] chore: update railroad diagram --- grammar/railroad-diagram.html | 121 +++++++++++++--------------------- 1 file changed, 47 insertions(+), 74 deletions(-) diff --git a/grammar/railroad-diagram.html b/grammar/railroad-diagram.html index f01faf9..34328bb 100644 --- a/grammar/railroad-diagram.html +++ b/grammar/railroad-diagram.html @@ -202,37 +202,10 @@

value-separator

value

-
value := false / null / true / object / array / number / string
-
+
value := object / array / number / string / "true" / "false" / "null"
+
-falsenulltrueobjectarraynumberstring - -
-
-
-

false

-
false := %x66.61.6C.73.65   ; false
-
- -false - -
-
-
-

true

-
true := %x74.72.75.65      ; true
-
- -true - -
-
-
-

null

-
null := %x6E.75.6C.6C      ; null
-
- -null +objectarraynumberstringtruefalsenull
@@ -272,9 +245,9 @@

number

-
-

digit0-9

-
digit0-9 := %x30-39          ; 0-9
+
+

digit

+
digit := %x30-39          ; 0-9
%x30-39 @@ -301,52 +274,61 @@

e

exp

-
exp := e [ "-" / "+" ] 1*digit0-9
-
- -e-+digit0-9 +
exp := e [ "-" / "+" ] 1*digit
+
+ +e-+digit

frac

-
frac := "." 1*digit0-9
-
- -.digit0-9 +
frac := "." 1*digit
+
+ +.digit

int

-
int := "0" / ( digit1-9 *digit0-9 )
-
- -0digit1-9digit0-9 +
int := "0" / ( digit1-9 *digit )
+
+ +0digit1-9digit

hexdigit

-
hexdigit := digit0-9 /
+        
hexdigit := digit /
   %x41 /                    ; A
   %x42 /                    ; B
   %x43 /                    ; C
   %x44 /                    ; D
   %x45 /                    ; E
   %x46                      ; F
-
- -digit0-9ABCDEF +
+ +digitABCDEF + +
+
+
+

four-hexdigits

+
four-hexdigits := 4hexdigit
+
+ +hexdigithexdigithexdigithexdigit

string

-
string := quotation-mark *char quotation-mark
-
- -quotation-markcharquotation-mark +
string := %x22 *char %x22
+
+ +"char"
@@ -354,28 +336,19 @@

string

char

char := unescaped /
  %x5C (
- %x22 /             ; "    quotation mark  U+0022
- %x5C /             ; \    reverse solidus U+005C
- %x2F /             ; /    solidus         U+002F
- %x62 /             ; b    backspace       U+0008
- %x66 /             ; f    form feed       U+000C
- %x6E /             ; n    line feed       U+000A
- %x72 /             ; r    carriage return U+000D
- %x74 /             ; t    tab             U+0009
- %x75 4hexdigit    ; uXXXX                U+XXXX
+ %x22 /              ; "    quotation mark  U+0022
+ %x5C /              ; \    reverse solidus U+005C
+ %x2F /              ; /    solidus         U+002F
+ %x62 /              ; b    backspace       U+0008
+ %x66 /              ; f    form feed       U+000C
+ %x6E /              ; n    line feed       U+000A
+ %x72 /              ; r    carriage return U+000D
+ %x74 /              ; t    tab             U+0009
+ %x75 four-hexdigits ; uXXXX                U+XXXX
  )
-
- -unescaped\"quotation mark U+0022\reverse solidus U+005C/solidus U+002Fbbackspace U+0008fform feed U+000Cnline feed U+000Arcarriage return U+000Dttab U+0009uU+XXXXhexdigithexdigithexdigithexdigit - -
-
-
-

quotation-mark

-
quotation-mark := %x22       ; "
-
- -" +
+ +unescaped\"quotation markU+0022\reverse solidusU+005C/solidusU+002FbbackspaceU+0008fform feedU+000Cnline feedU+000Arcarriage returnU+000DttabU+0009uU+XXXXfour-hexdigits
From 4656a012582ba04978a0da3f9ad8d5fe321c9764 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 04:58:47 +0000 Subject: [PATCH 10/19] chore: update submodule railroad-diagram-generator-js to latest commit --- submodules/railroad-diagram-generator-js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/railroad-diagram-generator-js b/submodules/railroad-diagram-generator-js index 411f16c..30c46a5 160000 --- a/submodules/railroad-diagram-generator-js +++ b/submodules/railroad-diagram-generator-js @@ -1 +1 @@ -Subproject commit 411f16c244f8290dc45407a1337c339fe2009a6b +Subproject commit 30c46a509addb2bd577e38e25651297ce940def7 From 4b05329302c1090c443801ba0795ba67ae6addf3 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 05:52:28 +0000 Subject: [PATCH 11/19] feat: simplify single-line-comment-end rules --- grammar/jsonc.abnf | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/grammar/jsonc.abnf b/grammar/jsonc.abnf index dd7b6a9..19b9e1e 100644 --- a/grammar/jsonc.abnf +++ b/grammar/jsonc.abnf @@ -10,7 +10,7 @@ JSONC-text = wsc value wsc ; Whitespace with Comments: zero or more whitespace characters or comments -wsc = *(ws-char / comment) +wsc = *(ws-char / comment) ; White space and comments ; Single whitespace character (space, tab, line feed, carriage return) ws-char = %x20 / %x09 / %x0A / %x0D ; space / tab / LF / CR @@ -18,20 +18,12 @@ ws-char = %x20 / %x09 / %x0A / %x0D ; space / tab / LF / CR ; Comments: single-line or multi-line comment = single-line-comment / multi-line-comment -; Source character: any Unicode code point, as per ECMAScript. -source-character = %x00-10FFFF - -; Comment terminators and sequences (based on ECMAScript line terminators) -comment-terminator = %x0A / %x0D ; LF / CR -comment-terminator-sequence = %x0D.0A / %x0A / %x0D - ; Single-line comment: starts with //, continues until line ending -; Terminator is not part of the comment body. ; Note that the single-line-comment-end is optional, allowing comments to end at the end of the file without a line terminator. single-line-comment-start = %x2F.2F ; // double solidus -single-line-comment-end = comment-terminator-sequence +single-line-comment-end = %x0D.0A / %x0A / %x0D single-line-comment = single-line-comment-start *single-line-comment-char [ single-line-comment-end ] -single-line-comment-char = %x00-09 / %x0B-0C / %x0E-10FFFF ; Any source character except comment terminators +single-line-comment-char = %x00-09 / %x0B-0C / %x0E-10FFFF ; Any source character except CR and LF (line terminator) ; Multi-line comment: /* ... */ ; Cannot be nested. The first */ closes the comment. From e712a50bbc900153aec715838f4fb79af79efb96 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 05:54:31 +0000 Subject: [PATCH 12/19] feat: abnf simplified for numbers --- grammar/jsonc.abnf | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/grammar/jsonc.abnf b/grammar/jsonc.abnf index 19b9e1e..b3a2806 100644 --- a/grammar/jsonc.abnf +++ b/grammar/jsonc.abnf @@ -64,15 +64,11 @@ member = string name-separator value array = begin-array [ value *( value-separator value ) ] end-array ; Numbers -number = [ minus ] int [ frac ] [ exp ] +number = [ minus ] ( zero / ( digit1-9 *digit ) ) [ decimal-point 1*digit ] [ ( %x65 / %x45 ) [ minus / plus ] 1*digit ] decimal-point = %x2E ; . digit = %x30-39 ; 0-9 digit1-9 = %x31-39 ; 1-9 -e = %x65 / %x45 ; e E -exp = e [ minus / plus ] 1*digit -frac = decimal-point 1*digit -int = zero / ( digit1-9 *digit ) minus = %x2D ; - plus = %x2B ; + zero = %x30 ; 0 From 718f0af4dbaa0dd9a64355c9c4296807637e90e5 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 06:05:53 +0000 Subject: [PATCH 13/19] feat: add missing lowercase letters to hexdigits --- grammar/jsonc.abnf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/grammar/jsonc.abnf b/grammar/jsonc.abnf index b3a2806..78082a8 100644 --- a/grammar/jsonc.abnf +++ b/grammar/jsonc.abnf @@ -73,12 +73,12 @@ minus = %x2D ; - plus = %x2B ; + zero = %x30 ; 0 hexdigit = digit / - %x41 / ; A - %x42 / ; B - %x43 / ; C - %x44 / ; D - %x45 / ; E - %x46 ; F + %x41 / %x61 / ; A a + %x42 / %x62 / ; B b + %x43 / %x63 / ; C c + %x44 / %x64 / ; D d + %x45 / %x65 / ; E e + %x46 / %x66 ; F f four-hexdigits = 4hexdigit ; Strings From d67cab66e964d467ba4b8e61cd644d1418333db7 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 06:06:44 +0000 Subject: [PATCH 14/19] feat: add re-orderings in railroad diagrams --- generate-railroad.js | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/generate-railroad.js b/generate-railroad.js index 9058b33..93e3a10 100644 --- a/generate-railroad.js +++ b/generate-railroad.js @@ -33,6 +33,51 @@ const INLINE_LITERAL_REFS = [ }, ]; +// Move selected rule definitions after another rule in the processed ABNF. +// Add more entries here to control rule ordering in generated output. +const REPOSITION_RULES_AFTER = [ + { + ruleName: "begin-array", + afterRule: "array", + }, + { + ruleName: "end-array", + afterRule: "begin-array", + }, + { + ruleName: "begin-object", + afterRule: "object", + }, + { + ruleName: "end-object", + afterRule: "begin-object", + }, + { + ruleName: "name-separator", + afterRule: "member", + }, + { + ruleName: "value-separator", + afterRule: "value", + }, + { + ruleName: "digit", + afterRule: "unescaped", + }, + { + ruleName: "digit1-9", + afterRule: "digit", + }, + { + ruleName: "hexdigit", + afterRule: "digit1-9", + }, + { + ruleName: "four-hexdigits", + afterRule: "hexdigit", + } +]; + function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } @@ -159,6 +204,39 @@ function removeRuleDefinitions(source, ruleNames) { .join("\n"); } +function findRuleBlock(lines, ruleName) { + const ruleStartRegex = new RegExp(`^\\s*${escapeRegExp(ruleName)}\\s*=`); + const startIndex = lines.findIndex((line) => ruleStartRegex.test(line)); + if (startIndex === -1) { + throw new Error(`Rule ${ruleName} was not found.`); + } + + let endIndex = startIndex + 1; + while (endIndex < lines.length && /^\s/.test(lines[endIndex])) { + endIndex += 1; + } + + return { + startIndex, + endIndex, + blockLines: lines.slice(startIndex, endIndex), + }; +} + +function repositionRulesAfter(source, reorderings) { + let lines = source.split(/\r?\n/); + + for (const { ruleName, afterRule } of reorderings) { + const ruleBlock = findRuleBlock(lines, ruleName); + lines.splice(ruleBlock.startIndex, ruleBlock.endIndex - ruleBlock.startIndex); + + const afterRuleBlock = findRuleBlock(lines, afterRule); + lines.splice(afterRuleBlock.endIndex, 0, ...ruleBlock.blockLines); + } + + return lines.join("\n"); +} + function processAbnfSource(source) { let processed = source; @@ -171,6 +249,8 @@ function processAbnfSource(source) { processed = removeRuleDefinitions(processed, referencedRules); } + processed = repositionRulesAfter(processed, REPOSITION_RULES_AFTER); + return processed; } From 78c7effc63e71a587bd81d5838eac23567cf9b97 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 06:07:15 +0000 Subject: [PATCH 15/19] chore: update railroad diagram --- grammar/railroad-diagram.html | 257 +++++++++++++--------------------- 1 file changed, 97 insertions(+), 160 deletions(-) diff --git a/grammar/railroad-diagram.html b/grammar/railroad-diagram.html index 34328bb..7f631e4 100644 --- a/grammar/railroad-diagram.html +++ b/grammar/railroad-diagram.html @@ -20,7 +20,7 @@

JSONC-text

wsc

-
wsc := *(ws-char / comment)
+
wsc := *(ws-char / comment)   ; White space and comments
ws-charcomment @@ -45,39 +45,12 @@

comment

-
-

source-character

-
source-character := %x00-10FFFF
-
- -%x00-10FFFF - -
-
-
-

comment-terminator

-
comment-terminator := %x0A / %x0D                ; LF / CR
-
- -<LF><CR> - -
-
-
-

comment-terminator-sequence

-
comment-terminator-sequence := %x0D.0A / %x0A / %x0D
+
+

single-line-comment-end

+
single-line-comment-end := %x0D.0A / %x0A / %x0D
<CR><LF><LF><CR> - -
-
-
-

single-line-comment-end

-
single-line-comment-end := comment-terminator-sequence
-
- -comment-terminator-sequence
@@ -92,7 +65,7 @@

single-line-comment

single-line-comment-char

-
single-line-comment-char := %x00-09 / %x0B-0C / %x0E-10FFFF ; Any source character except comment terminators
+
single-line-comment-char := %x00-09 / %x0B-0C / %x0E-10FFFF ; Any source character except CR and LF (line terminator)
%x00-09%x0B-0C%x0E-10FFFF @@ -146,12 +119,30 @@

not-forward-slash-or-asterisk-char

-
-

begin-array

-
begin-array := wsc %x5B wsc  ; [ left square bracket
-
+
+

value

+
value := object / array / number / string / "true" / "false" / "null"
+
+ +objectarraynumberstringtruefalsenull + +
+
+
+

value-separator

+
value-separator := wsc %x2C wsc  ; , comma
+
-wsc[wsc +wsc,wsc + +
+
+
+

object

+
object := begin-object [ member *( value-separator member ) ] end-object
+
+ +begin-objectmembervalue-separatormemberend-object
@@ -161,15 +152,6 @@

begin-object

wsc{wsc - -
-
-
-

end-array

-
end-array := wsc %x5D wsc  ; ] right square bracket
-
- -wsc]wsc
@@ -179,42 +161,6 @@

end-object

wsc}wsc - -
-
-
-

name-separator

-
name-separator := wsc %x3A wsc  ; : colon
-
- -wsc:wsc - -
-
-
-

value-separator

-
value-separator := wsc %x2C wsc  ; , comma
-
- -wsc,wsc - -
-
-
-

value

-
value := object / array / number / string / "true" / "false" / "null"
-
- -objectarraynumberstringtruefalsenull - -
-
-
-

object

-
object := begin-object [ member *( value-separator member ) ] end-object
-
- -begin-objectmembervalue-separatormemberend-object
@@ -224,6 +170,15 @@

member

stringname-separatorvalue + +
+
+
+

name-separator

+
name-separator := wsc %x3A wsc  ; : colon
+
+ +wsc:wsc
@@ -236,90 +191,30 @@

array

-
-

number

-
number := [ "-" ] int [ frac ] [ exp ]
-
- --intfracexp - -
-
-
-

digit

-
digit := %x30-39          ; 0-9
-
- -%x30-39 - -
-
-
-

digit1-9

-
digit1-9 := %x31-39          ; 1-9
-
- -%x31-39 - -
-
-
-

e

-
e := %x65 / %x45             ; e E
-
- -eE - -
-
-
-

exp

-
exp := e [ "-" / "+" ] 1*digit
-
- -e-+digit - -
-
-
-

frac

-
frac := "." 1*digit
-
- -.digit - -
-
-
-

int

-
int := "0" / ( digit1-9 *digit )
-
- -0digit1-9digit +
+

begin-array

+
begin-array := wsc %x5B wsc  ; [ left square bracket
+
+ +wsc[wsc
-
-

hexdigit

-
hexdigit := digit /
-  %x41 /                    ; A
-  %x42 /                    ; B
-  %x43 /                    ; C
-  %x44 /                    ; D
-  %x45 /                    ; E
-  %x46                      ; F
-
- -digitABCDEF +
+

end-array

+
end-array := wsc %x5D wsc  ; ] right square bracket
+
+ +wsc]wsc
-
-

four-hexdigits

-
four-hexdigits := 4hexdigit
-
- -hexdigithexdigithexdigithexdigit +
+

number

+
number := [ "-" ] ( "0" / ( digit1-9 *digit ) ) [ "." 1*digit ] [ ( %x65 / %x45 ) [ "-" / "+" ] 1*digit ]
+
+ +-0digit1-9digit.digiteE-+digit
@@ -358,6 +253,48 @@

unescaped

%x20-21%x23-5B%x5D-10FFFF + +
+
+
+

digit

+
digit := %x30-39          ; 0-9
+
+ +%x30-39 + +
+
+
+

digit1-9

+
digit1-9 := %x31-39          ; 1-9
+
+ +%x31-39 + +
+
+
+

hexdigit

+
hexdigit := digit /
+  %x41 / %x61 /             ; A a
+  %x42 / %x62 /             ; B b
+  %x43 / %x63 /             ; C c
+  %x44 / %x64 /             ; D d
+  %x45 / %x65 /             ; E e
+  %x46 / %x66               ; F f
+
+ +digitAaBbCcDdEeFf + +
+
+
+

four-hexdigits

+
four-hexdigits := 4hexdigit
+
+ +hexdigithexdigithexdigithexdigit
From 49bd2d82d6d6fef33481a25684bb023abf5fd719 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 06:08:53 +0000 Subject: [PATCH 16/19] refactor: rename to JSONC.abnf --- generate-railroad.js | 2 +- grammar/{jsonc.abnf => JSONC.abnf} | 0 grammar/README.md | 8 ++++---- 3 files changed, 5 insertions(+), 5 deletions(-) rename grammar/{jsonc.abnf => JSONC.abnf} (100%) diff --git a/generate-railroad.js b/generate-railroad.js index 93e3a10..1c9be68 100644 --- a/generate-railroad.js +++ b/generate-railroad.js @@ -5,7 +5,7 @@ const { spawnSync } = require("node:child_process"); const path = require("node:path"); // Customization section -const DEFAULT_INPUT_ABNF = "grammar/jsonc.abnf"; +const DEFAULT_INPUT_ABNF = "grammar/JSONC.abnf"; const DEFAULT_PROCESSED_ABNF = "grammar/jsonc-processed.abnf"; const DEFAULT_OUTPUT_HTML = "grammar/railroad-diagram.html"; diff --git a/grammar/jsonc.abnf b/grammar/JSONC.abnf similarity index 100% rename from grammar/jsonc.abnf rename to grammar/JSONC.abnf diff --git a/grammar/README.md b/grammar/README.md index c852672..af7cd7a 100644 --- a/grammar/README.md +++ b/grammar/README.md @@ -4,7 +4,7 @@ This directory contains the ABNF grammar for JSONC, along with plans for generat ## Railroad Diagram Generation Plan -Generate railroad diagrams from `grammar/jsonc.abnf` using a simple one-file Node.js script. +Generate railroad diagrams from `grammar/JSONC.abnf` using a simple one-file Node.js script. Instead of building a custom ABNF parser and converter to Tab Atkins constructor calls, use: @@ -18,7 +18,7 @@ The wrapper script should: 1. Accept input ABNF path and optional output HTML path. 2. Default to: - - input: `grammar/jsonc.abnf` + - input: `grammar/JSONC.abnf` - output: `grammar/railroad-diagram.html` 3. Optionally accept `--title` to set the HTML title. 4. Execute the upstream CLI from our installed dependency. @@ -53,13 +53,13 @@ npm run railroad Generate from a specific input and output: ```bash -npm run railroad -- grammar/jsonc.abnf grammar/railroad-diagram.html +npm run railroad -- grammar/JSONC.abnf grammar/railroad-diagram.html ``` Generate with a custom title: ```bash -npm run railroad -- grammar/jsonc.abnf grammar/railroad-diagram.html --title "JSONC Grammar" +npm run railroad -- grammar/JSONC.abnf grammar/railroad-diagram.html --title "JSONC Grammar" ``` ### Notes on EOF for single-line comments From 96ff26c229baa26e799cbb885f8808b2ab5a8594 Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 06:15:01 +0000 Subject: [PATCH 17/19] feat: add post-processing for name --- generate-railroad.js | 23 ++++++++++++++++++++++- grammar/railroad-diagram.html | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/generate-railroad.js b/generate-railroad.js index 1c9be68..448b145 100644 --- a/generate-railroad.js +++ b/generate-railroad.js @@ -8,6 +8,7 @@ const path = require("node:path"); const DEFAULT_INPUT_ABNF = "grammar/JSONC.abnf"; const DEFAULT_PROCESSED_ABNF = "grammar/jsonc-processed.abnf"; const DEFAULT_OUTPUT_HTML = "grammar/railroad-diagram.html"; +const FORCED_HTML_HEADER = "JSONC GRAMMAR"; // Rules to inline from their %x... definitions as literal ABNF strings. // Add more rule names here to apply the same transformation. @@ -254,6 +255,15 @@ function processAbnfSource(source) { return processed; } +function postProcessGeneratedHtml(htmlPath) { + const html = fs.readFileSync(htmlPath, "utf8"); + const updated = html.replace(/

[^<]*<\/h1>/, `

${FORCED_HTML_HEADER}

`); + + if (updated !== html) { + fs.writeFileSync(htmlPath, updated, "utf8"); + } +} + const args = process.argv.slice(2); const titleIndex = args.indexOf("--title"); @@ -327,4 +337,15 @@ if (result.error) { process.exit(1); } -process.exit(result.status === null ? 1 : result.status); \ No newline at end of file +if (result.status !== 0) { + process.exit(result.status === null ? 1 : result.status); +} + +try { + postProcessGeneratedHtml(outputPath); +} catch (error) { + console.error(`Failed to post-process generated HTML: ${error.message}`); + process.exit(1); +} + +process.exit(0); \ No newline at end of file diff --git a/grammar/railroad-diagram.html b/grammar/railroad-diagram.html index 7f631e4..3c9179a 100644 --- a/grammar/railroad-diagram.html +++ b/grammar/railroad-diagram.html @@ -7,7 +7,7 @@ -

jsonc-processed Grammar

+

JSONC GRAMMAR

JSONC-text

From 239768a3cd957e311207f645f93318dc37bb553f Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 06:23:17 +0000 Subject: [PATCH 18/19] feat: add formal grammar section --- index.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/index.markdown b/index.markdown index c92441a..84dae61 100644 --- a/index.markdown +++ b/index.markdown @@ -109,6 +109,16 @@ Consumers that support JSONC SHOULD accept `application/jsonc`. - Configuration Files: JSONC is useful for configuration files where comments can provide explanations or instructions. - Data Annotation: JSONC allows developers to annotate JSON data with comments for better understanding and maintenance. +## Formal grammar + +The formal grammar of JSONC is available here: + +- [JSONC ABNF Grammar]({{ '/grammar/JSONC.abnf' | relative_url }}) + +For a visual representation of the grammar rules, see the JSONC railroad diagram page: + +- [JSONC Railroad Diagram]({{ '/grammar/railroad-diagram.html' | relative_url }}) + ## Tools and Libraries Several tools and libraries support JSONC, enabling developers to parse and generate JSONC data easily. From e50873db197f53d55fbe667736cebef9e84fe47b Mon Sep 17 00:00:00 2001 From: Martin Leduc <31558169+DecimalTurn@users.noreply.github.com> Date: Sun, 3 May 2026 07:01:18 +0000 Subject: [PATCH 19/19] fix: correct comment in whitespace definition --- grammar/JSONC.abnf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar/JSONC.abnf b/grammar/JSONC.abnf index 78082a8..e89ae02 100644 --- a/grammar/JSONC.abnf +++ b/grammar/JSONC.abnf @@ -10,7 +10,7 @@ JSONC-text = wsc value wsc ; Whitespace with Comments: zero or more whitespace characters or comments -wsc = *(ws-char / comment) ; White space and comments +wsc = *(ws-char / comment) ; Whitespace and/or comments ; Single whitespace character (space, tab, line feed, carriage return) ws-char = %x20 / %x09 / %x0A / %x0D ; space / tab / LF / CR