From f9c43be225fb9736ba5f0f6453b1b9c03a242493 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 7 Sep 2025 01:55:12 +0000 Subject: [PATCH 1/4] Clean up whitespace in `reference.css` --- theme/reference.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/theme/reference.css b/theme/reference.css index 727c800044..cbf8f1cdae 100644 --- a/theme/reference.css +++ b/theme/reference.css @@ -441,11 +441,11 @@ main > .rule { main > .rule > a.rule-link::before { content: "[*]"; - } - + } + .test-link > a::before { content: "[T]"; - } + } } /* Align rules to various siblings */ From 0e6e036bfa9ce3849434ed8c0d2f3c1921bc9d26 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 7 Sep 2025 02:01:09 +0000 Subject: [PATCH 2/4] Add trailing semicolon to CSS rules missing one Some CSS rules were missing a trailing semicolon. Let's add this where it was missing. --- theme/reference.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/theme/reference.css b/theme/reference.css index cbf8f1cdae..99379a8b95 100644 --- a/theme/reference.css +++ b/theme/reference.css @@ -470,7 +470,7 @@ main > .rule { 2.5em * var(--h2-em-mult) - 16px /* half of the font size difference */ + (1em * var(--h2-em-mult) - 1em) / 2 - ) + ); } .rule:has(+ h3, + .tests-popup + h3) { /* multiplying by this turns h3's em into .rule's em*/ @@ -484,7 +484,7 @@ main > .rule { 2.5em * var(--h3-em-mult) - 16px /* half of the font size difference */ + (1em * var(--h3-em-mult) - 1em) / 2 - ) + ); } .rule:has(+ h4, + .tests-popup + h4) { @@ -499,7 +499,7 @@ main > .rule { 2em * var(--h4-em-mult) - 16px /* half of the font size difference */ + (1em * var(--h4-em-mult) - 1em) / 2 - ) + ); } .rule:has(+ h5, + .tests-popup + h5) { @@ -514,7 +514,7 @@ main > .rule { 2em * var(--h5-em-mult) - 16px /* half of the font size difference */ + (1em * var(--h5-em-mult) - 1em) / 2 - ) + ); } .rule:has(+ h6, + .tests-popup + h6) { @@ -529,7 +529,7 @@ main > .rule { 2em * var(--h6-em-mult) - 16px /* half of the font size difference */ + (1em * var(--h6-em-mult) - 1em) / 2 - ) + ); } /* Sets the color for [!HISTORY] blockquote admonitions. */ @@ -597,10 +597,10 @@ main > .rule { } .light .grammar-literal { - background-color: #fafafa + background-color: #fafafa; } .rust .grammar-literal { - background-color: #dedede + background-color: #dedede; } .coal .grammar-literal { background-color: #1d1f21; @@ -609,7 +609,7 @@ main > .rule { background-color: #1d1f21; } .ayu .grammar-literal { - background-color: #191f26 + background-color: #191f26; } .grammar-production:target, .railroad-production:target { From 890caa2cf209b1e2ea7d5313139b6fdc7c9660c5 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 7 Sep 2025 05:37:59 +0000 Subject: [PATCH 3/4] Move per-theme CSS variables to top When defining colors relative to other colors defined per theme, it's helpful to have these all defined as variables and have all the variables defined in one place. Let's do that. --- theme/reference.css | 109 +++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/theme/reference.css b/theme/reference.css index 99379a8b95..be0a2bdc99 100644 --- a/theme/reference.css +++ b/theme/reference.css @@ -1,5 +1,54 @@ /* Custom CSS for the Rust Specification. */ +/* Per-theme variables. */ +:root { + --railroad-background-color: hsl(30, 20%, 95%); + --railroad-background-image: + linear-gradient(to right, rgba(30, 30, 30, .05) 1px, transparent 1px), + linear-gradient(to bottom, rgba(30, 30, 30, .05) 1px, transparent 1px); + --railroad-path-stroke: black; + --railroad-rect-stroke: black; + --railroad-rect-fill: hsl(-290, 70%, 90%); +} +.light { + --alert-note-color: #0969da; + --alert-warning-color: #9a6700; + --alert-edition-color: #1a7f37; + --alert-example-color: #8250df; + --gramar-literal-bg: #fafafa; +} +.rust { + --alert-note-color: #023b95; + --alert-warning-color: #603700; + --alert-edition-color: #008200; + --alert-example-color: #8250df; + --grammar-literal-bg: #dedede; +} +.coal, .navy { + --alert-note-color: #4493f8; + --alert-warning-color: #d29922; + --alert-edition-color: #3fb950; + --alert-example-color: #ab7df8; + --grammar-literal-bg: #1d1f21; +} +.ayu { + --alert-note-color: #74b9ff; + --alert-warning-color: #f0b72f; + --alert-edition-color: #2bd853; + --alert-example-color: #d3abff; + --gramar-literal-bg: #191f26; +} +.coal, .navy, .ayu { + --railroad-background-color: hsl(230, 10%, 20%); + --railroad-background-image: + linear-gradient(to right, rgba(150, 150, 150, .05) 1px, transparent 1px), + linear-gradient(to bottom, rgba(150, 150, 150, .05) 1px, transparent 1px); + --railroad-path-stroke: hsl(200, 10%, 60%); + --railroad-text-fill: hsl(230, 30%, 80%); + --railroad-rect-stroke: hsl(200, 10%, 50%); + --railroad-rect-fill: hsl(230, 20%, 20%); +} + /* .parenthetical class used to keep e.g. "less-than symbol (<)" from wrapping the end parenthesis onto its own line. Use in a span between the last word and @@ -58,31 +107,6 @@ See mdbook-spec/src/admonitions.rs. margin-right: 8px; } -.light .alert { - --alert-note-color: #0969da; - --alert-warning-color: #9a6700; - --alert-edition-color: #1a7f37; - --alert-example-color: #8250df; -} -.ayu .alert { - --alert-note-color: #74b9ff; - --alert-warning-color: #f0b72f; - --alert-edition-color: #2bd853; - --alert-example-color: #d3abff; -} -.rust .alert { - --alert-note-color: #023b95; - --alert-warning-color: #603700; - --alert-edition-color: #008200; - --alert-example-color: #8250df; -} -.coal .alert, -.navy .alert { - --alert-note-color: #4493f8; - --alert-warning-color: #d29922; - --alert-edition-color: #3fb950; - --alert-example-color: #ab7df8; -} .alert-note blockquote { border-inline-start-color: var(--alert-note-color); } @@ -596,20 +620,8 @@ main > .rule { color: var(--inline-code-color); } -.light .grammar-literal { - background-color: #fafafa; -} -.rust .grammar-literal { - background-color: #dedede; -} -.coal .grammar-literal { - background-color: #1d1f21; -} -.navy .grammar-literal { - background-color: #1d1f21; -} -.ayu .grammar-literal { - background-color: #191f26; +.grammar-literal { + background-color: var(--grammar-literal-bg); } .grammar-production:target, .railroad-production:target { @@ -653,25 +665,6 @@ main > .rule { display: none; } -:root { - --railroad-background-color: hsl(30, 20%, 95%); - --railroad-background-image: linear-gradient(to right, rgba(30, 30, 30, .05) 1px, transparent 1px), - linear-gradient(to bottom, rgba(30, 30, 30, .05) 1px, transparent 1px); - --railroad-path-stroke: black; - --railroad-rect-stroke: black; - --railroad-rect-fill: hsl(-290, 70%, 90%); -} - -.coal, .navy, .ayu { - --railroad-background-color: hsl(230, 10%, 20%); - --railroad-background-image: linear-gradient(to right, rgba(150, 150, 150, .05) 1px, transparent 1px), - linear-gradient(to bottom, rgba(150, 150, 150, .05) 1px, transparent 1px); - --railroad-path-stroke: hsl(200, 10%, 60%); - --railroad-text-fill: hsl(230, 30%, 80%); - --railroad-rect-stroke: hsl(200, 10%, 50%); - --railroad-rect-fill: hsl(230, 20%, 20%); -} - svg.railroad { background-color: var(--railroad-background-color); background-size: 15px 15px; From b3b582cf6e5be78cebdf0bb6f3e959b1ce44e80d Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 7 Sep 2025 00:22:05 +0000 Subject: [PATCH 4/4] Add support to grammar for single line comments Sometimes it would be useful in the grammar to be able to add comments inline. Let's allow this. We'll treat these comments as expressions and add special handling to allow these on lines ahead of the production name. For now we'll render these only in the Markdown and not in the railroad diagrams. --- docs/grammar.md | 8 +++++++- mdbook-spec/src/grammar.rs | 5 +++++ mdbook-spec/src/grammar/parser.rs | 16 ++++++++++++++++ mdbook-spec/src/grammar/render_markdown.rs | 7 +++++++ mdbook-spec/src/grammar/render_railroad.rs | 22 ++++++++++++++-------- src/notation.md | 1 + theme/reference.css | 15 +++++++++++++++ 7 files changed, 65 insertions(+), 9 deletions(-) diff --git a/docs/grammar.md b/docs/grammar.md index c99257df85..190782bba4 100644 --- a/docs/grammar.md +++ b/docs/grammar.md @@ -27,7 +27,9 @@ BACKTICK -> U+0060 LF -> U+000A -Production -> `@root`? Name ` ->` Expression +Production -> + ( Comment LF )* + `@root`? Name ` ->` Expression Name -> + @@ -55,6 +57,7 @@ Expr1 -> Unicode | NonTerminal | Break + | Comment | Terminal | Charset | Prose @@ -67,6 +70,8 @@ NonTerminal -> Name Break -> LF ` `+ +Comment -> `//` ~[LF]+ + Terminal -> BACKTICK ~[LF]+ BACKTICK Charset -> `[` (` `* Characters)+ ` `* `]` @@ -96,6 +101,7 @@ The general format is a series of productions separated by blank lines. The expr | Unicode | U+0060 | A single unicode character. | | NonTerminal | FunctionParameters | A reference to another production by name. | | Break | | This is used internally by the renderer to detect line breaks and indentation. | +| Comment | // Single line comment. | A comment extending to the end of the line. | | Terminal | \`example\` | This is a sequence of exact characters, surrounded by backticks | | Charset | [ \`A\`-\`Z\` \`0\`-\`9\` \`_\` ] | A choice from a set of characters, space separated. There are three different forms. | | CharacterRange | [ \`A\`-\`Z\` ] | A range of characters, each character should be in backticks. diff --git a/mdbook-spec/src/grammar.rs b/mdbook-spec/src/grammar.rs index 98d3cd5d9d..8a15c71936 100644 --- a/mdbook-spec/src/grammar.rs +++ b/mdbook-spec/src/grammar.rs @@ -22,6 +22,8 @@ pub struct Grammar { #[derive(Debug)] pub struct Production { name: String, + /// Comments and breaks that precede the production name. + comments: Vec, /// Category is from the markdown lang string, and defines how it is /// grouped and organized on the summary page. category: String, @@ -70,6 +72,8 @@ enum ExpressionKind { /// /// Used by the renderer to help format and structure the grammar. Break(usize), + /// `// Single line comment.` + Comment(String), /// ``[`A`-`Z` `_` LF]`` Charset(Vec), /// ``~[` ` LF]`` @@ -135,6 +139,7 @@ impl Expression { ExpressionKind::Terminal(_) | ExpressionKind::Prose(_) | ExpressionKind::Break(_) + | ExpressionKind::Comment(_) | ExpressionKind::Unicode(_) => {} ExpressionKind::Charset(set) => { for ch in set { diff --git a/mdbook-spec/src/grammar/parser.rs b/mdbook-spec/src/grammar/parser.rs index 631fb7ae8f..c2d6fd546b 100644 --- a/mdbook-spec/src/grammar/parser.rs +++ b/mdbook-spec/src/grammar/parser.rs @@ -122,6 +122,12 @@ impl Parser<'_> { } fn parse_production(&mut self, category: &str, path: &Path) -> Result { + let mut comments = Vec::new(); + while let Ok(comment) = self.parse_comment() { + self.expect("\n", "expected newline")?; + comments.push(Expression::new_kind(comment)); + comments.push(Expression::new_kind(ExpressionKind::Break(0))); + } let is_root = self.parse_is_root(); self.space0(); let name = self @@ -133,6 +139,7 @@ impl Parser<'_> { }; Ok(Production { name, + comments, category: category.to_string(), expression, path: path.to_owned(), @@ -218,6 +225,8 @@ impl Parser<'_> { bail!(self, "expected indentation on next line"); } ExpressionKind::Break(space.len()) + } else if next == b'/' { + self.parse_comment()? } else if next == b'`' { self.parse_terminal()? } else if next == b'[' { @@ -269,6 +278,13 @@ impl Parser<'_> { Ok(term) } + /// Parse e.g. `// Single line comment.`. + fn parse_comment(&mut self) -> Result { + self.expect("//", "expected `//`")?; + let text = self.take_while(&|x| x != '\n').to_string(); + Ok(ExpressionKind::Comment(text)) + } + fn parse_charset(&mut self) -> Result { self.expect("[", "expected opening [")?; let mut characters = Vec::new(); diff --git a/mdbook-spec/src/grammar/render_markdown.rs b/mdbook-spec/src/grammar/render_markdown.rs index 1aacc9e86a..e119044601 100644 --- a/mdbook-spec/src/grammar/render_markdown.rs +++ b/mdbook-spec/src/grammar/render_markdown.rs @@ -45,6 +45,9 @@ impl Production { .get(&self.name) .map(|path| path.to_string()) .unwrap_or_else(|| format!("missing")); + for expr in &self.comments { + expr.render_markdown(cx, output); + } write!( output, " &self.kind, @@ -163,6 +167,9 @@ impl Expression { output.push_str("\\\n"); output.push_str(&" ".repeat(*indent)); } + ExpressionKind::Comment(s) => { + write!(output, "// {s}").unwrap(); + } ExpressionKind::Charset(set) => charset_render_markdown(cx, set, output), ExpressionKind::NegExpression(e) => { output.push('~'); diff --git a/mdbook-spec/src/grammar/render_railroad.rs b/mdbook-spec/src/grammar/render_railroad.rs index 93d68a589c..b31684f7df 100644 --- a/mdbook-spec/src/grammar/render_railroad.rs +++ b/mdbook-spec/src/grammar/render_railroad.rs @@ -102,8 +102,11 @@ impl Expression { .map(|e| e.render_railroad(cx, stack)) .filter_map(|n| n) .collect(); + if seq.is_empty() { + return None; + } let seq: Sequence> = Sequence::new(seq); - Box::new(seq) + Some(Box::new(seq)) }; // If `stack` is true, split the sequence on Breaks and @@ -127,16 +130,18 @@ impl Expression { &es[..] }; - let mut breaks: Vec<_> = - es.split(|e| e.is_break()).map(|es| make_seq(es)).collect(); + let mut breaks: Vec<_> = es + .split(|e| e.is_break()) + .flat_map(|es| make_seq(es)) + .collect(); // If there aren't any breaks, don't bother stacking. - if breaks.len() == 1 { - breaks.pop().unwrap() - } else { - Box::new(Stack::new(breaks)) + match breaks.len() { + 0 => return None, + 1 => breaks.pop().unwrap(), + _ => Box::new(Stack::new(breaks)), } } else { - make_seq(&es) + make_seq(&es)? } } // Treat `e?` and `e{..1}` / `e{0..1}` equally. @@ -205,6 +210,7 @@ impl Expression { ExpressionKind::Terminal(t) => Box::new(Terminal::new(t.clone())), ExpressionKind::Prose(s) => Box::new(Terminal::new(s.clone())), ExpressionKind::Break(_) => return None, + ExpressionKind::Comment(_) => return None, ExpressionKind::Charset(set) => { let ns: Vec<_> = set.iter().map(|c| c.render_railroad(cx)).collect(); Box::new(Choice::>::new(ns)) diff --git a/src/notation.md b/src/notation.md index b4954d42c9..e88679220c 100644 --- a/src/notation.md +++ b/src/notation.md @@ -27,6 +27,7 @@ The following notations are used by the *Lexer* and *Syntax* grammar snippets: | U+xxxx | U+0060 | A single unicode character | | \ | \ | An English description of what should be matched | | Rule suffix | IDENTIFIER_OR_KEYWORD _except `crate`_ | A modification to the previous rule | +| // Comment. | // Single line comment. | A comment extending to the end of the line. | Sequences have a higher precedence than `|` alternation. diff --git a/theme/reference.css b/theme/reference.css index be0a2bdc99..3e16b4cd9b 100644 --- a/theme/reference.css +++ b/theme/reference.css @@ -24,6 +24,10 @@ --alert-example-color: #8250df; --grammar-literal-bg: #dedede; } +.light, .rust { + --grammar-comment-color: lch(from var(--quote-bg) calc(l - 50) 0 0); + --inline-code-color: var(--grammar-comment-color); +} .coal, .navy { --alert-note-color: #4493f8; --alert-warning-color: #d29922; @@ -39,6 +43,8 @@ --gramar-literal-bg: #191f26; } .coal, .navy, .ayu { + --grammar-comment-color: lch(from var(--quote-bg) calc(l + 50) 0 0); + --inline-code-color: var(--grammar-comment-color); --railroad-background-color: hsl(230, 10%, 20%); --railroad-background-image: linear-gradient(to right, rgba(150, 150, 150, .05) 1px, transparent 1px), @@ -609,6 +615,15 @@ main > .rule { font-family: "Open Sans", sans-serif; } +/* Comments inside the grammar. */ +.grammar-comment { + font-family: "Open Sans", sans-serif; + color: var(--grammar-comment-color); +} +.grammar-comment code.hljs { + background: var(--grammar-literal-bg); +} + /* Places a box around literals to differentiate from other grammar punctuation like | and ( . */ .grammar-literal { font-family: var(--mono-font);