Skip to content

Commit 8994838

Browse files
committed
Cross link between grammar and railroad diagrams
When reviewing a production in the grammar, one often wants to quickly find the corresponding railroad diagram, and when reviewing a railroad diagram, one often wants to quickly find the corresponding production in the grammar. Let's make this easy by linking each production in the grammar to the corresponding railroad diagram, and from the name of each railroad diagram to the corresponding production in the grammar. When clicking on a production in the grammar, we'll automatically display the railroad diagrams if those are not already displayed.
1 parent 6e81933 commit 8994838

File tree

5 files changed

+56
-22
lines changed

5 files changed

+56
-22
lines changed

mdbook-spec/src/grammar.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,18 @@ fn render_names(
309309
};
310310

311311
let markdown_link_map = updated_link_map(render_markdown::markdown_id);
312-
if let Err(e) = grammar.render_markdown(&names, &markdown_link_map, &mut output, for_summary) {
312+
// Modify the link map so that it contains the exact destination needed to
313+
// link to the railroad productions, and to accommodate the summary
314+
// chapter.
315+
let railroad_link_map = updated_link_map(render_railroad::railroad_id);
316+
317+
if let Err(e) = grammar.render_markdown(
318+
&names,
319+
&markdown_link_map,
320+
&railroad_link_map,
321+
&mut output,
322+
for_summary,
323+
) {
313324
warn_or_err!(
314325
diag,
315326
"grammar failed in chapter {:?}: {e}",
@@ -319,21 +330,23 @@ fn render_names(
319330

320331
output.push_str(
321332
"\n\
322-
<button class=\"grammar-toggle\" type=\"button\" \
323-
title=\"Toggle grammar display\" \
324-
onclick=\"toggle_grammar()\">\
333+
<button class=\"grammar-toggle-railroad\" type=\"button\" \
334+
title=\"Toggle railroad display\" \
335+
onclick=\"toggle_railroad()\">\
325336
Show Railroad\
326337
</button>\n\
327338
</div>\n\
328339
<div class=\"grammar-railroad grammar-hidden\">\n\
329340
\n",
330341
);
331342

332-
// Modify the link map so that it contains the exact destination needed to
333-
// link to the railroad productions, and to accommodate the summary
334-
// chapter.
335-
let railroad_link_map = updated_link_map(render_railroad::railroad_id);
336-
if let Err(e) = grammar.render_railroad(&names, &railroad_link_map, &mut output, for_summary) {
343+
if let Err(e) = grammar.render_railroad(
344+
&names,
345+
&railroad_link_map,
346+
&markdown_link_map,
347+
&mut output,
348+
for_summary,
349+
) {
337350
warn_or_err!(
338351
diag,
339352
"grammar failed in chapter {:?}: {e}",

mdbook-spec/src/grammar/render_markdown.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ impl Grammar {
1414
&self,
1515
names: &[&str],
1616
link_map: &HashMap<String, String>,
17+
rr_link_map: &HashMap<String, String>,
1718
output: &mut String,
1819
for_summary: bool,
1920
) -> anyhow::Result<()> {
2021
let mut iter = names.into_iter().peekable();
2122
while let Some(name) = iter.next() {
22-
let prod = match self.productions.get(*name) {
23-
Some(p) => p,
24-
None => bail!("could not find grammar production named `{name}`"),
23+
let Some(prod) = self.productions.get(*name) else {
24+
bail!("could not find grammar production named `{name}`");
2525
};
26-
prod.render_markdown(link_map, output, for_summary);
26+
prod.render_markdown(link_map, rr_link_map, output, for_summary);
2727
if iter.peek().is_some() {
2828
output.push_str("\n");
2929
}
@@ -45,14 +45,23 @@ impl Production {
4545
fn render_markdown(
4646
&self,
4747
link_map: &HashMap<String, String>,
48+
rr_link_map: &HashMap<String, String>,
4849
output: &mut String,
4950
for_summary: bool,
5051
) {
52+
let dest = rr_link_map
53+
.get(&self.name)
54+
.map(|path| path.to_string())
55+
.unwrap_or_else(|| format!("missing"));
5156
write!(
5257
output,
53-
"<span class=\"grammar-text grammar-production\" id=\"{id}\">{name}</span> → ",
58+
"<span class=\"grammar-text grammar-production\" id=\"{id}\" \
59+
onclick=\"show_railroad()\"\
60+
>\
61+
[{name}]({dest})\
62+
</span> → ",
5463
id = markdown_id(&self.name, for_summary),
55-
name = self.name
64+
name = self.name,
5665
)
5766
.unwrap();
5867
self.expression.render_markdown(link_map, output);

mdbook-spec/src/grammar/render_railroad.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ impl Grammar {
1414
&self,
1515
names: &[&str],
1616
link_map: &HashMap<String, String>,
17+
md_link_map: &HashMap<String, String>,
1718
output: &mut String,
1819
for_summary: bool,
1920
) -> anyhow::Result<()> {
@@ -22,7 +23,7 @@ impl Grammar {
2223
Some(p) => p,
2324
None => bail!("could not find grammar production named `{name}`"),
2425
};
25-
prod.render_railroad(link_map, output, for_summary);
26+
prod.render_railroad(link_map, md_link_map, output, for_summary);
2627
}
2728
Ok(())
2829
}
@@ -41,16 +42,17 @@ impl Production {
4142
fn render_railroad(
4243
&self,
4344
link_map: &HashMap<String, String>,
45+
md_link_map: &HashMap<String, String>,
4446
output: &mut String,
4547
for_summary: bool,
4648
) {
47-
let mut dia = self.make_diagram(false, link_map);
49+
let mut dia = self.make_diagram(false, link_map, md_link_map);
4850
// If the diagram is very wide, try stacking it to reduce the width.
4951
// This 900 is somewhat arbitrary based on looking at productions that
5052
// looked too squished. If your diagram is still too squished,
5153
// consider adding more rules to shorten it.
5254
if dia.width() > 900 {
53-
dia = self.make_diagram(true, link_map);
55+
dia = self.make_diagram(true, link_map, md_link_map);
5456
}
5557
writeln!(
5658
output,
@@ -67,12 +69,17 @@ impl Production {
6769
&self,
6870
stack: bool,
6971
link_map: &HashMap<String, String>,
72+
md_link_map: &HashMap<String, String>,
7073
) -> Diagram<Box<dyn Node>> {
7174
let n = self.expression.render_railroad(stack, link_map, false);
75+
let dest = md_link_map
76+
.get(&self.name)
77+
.map(|path| path.to_string())
78+
.unwrap_or_else(|| format!("missing"));
7279
let seq: Sequence<Box<dyn Node>> =
7380
Sequence::new(vec![Box::new(SimpleStart), n.unwrap(), Box::new(SimpleEnd)]);
7481
let vert = VerticalGrid::<Box<dyn Node>>::new(vec![
75-
Box::new(Comment::new(self.name.clone())),
82+
Box::new(Link::new(Comment::new(self.name.clone()), dest)),
7683
Box::new(seq),
7784
]);
7885

theme/reference.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ main > .rule {
626626
}
627627

628628
/* The toggle button. */
629-
.grammar-toggle {
629+
.grammar-toggle-railroad {
630630
width: 120px;
631631
padding: 5px 0px;
632632
border-radius: 5px;

theme/reference.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ function spec_toggle_tests(rule_id) {
2323
}
2424
}
2525

26-
function toggle_grammar() {
26+
function toggle_railroad() {
2727
const grammarRailroad = get_railroad();
2828
set_railroad(!grammarRailroad);
2929
update_railroad();
3030
}
3131

32+
function show_railroad() {
33+
set_railroad(true);
34+
update_railroad();
35+
}
36+
3237
function get_railroad() {
3338
let grammarRailroad = null;
3439
try {
@@ -58,7 +63,7 @@ function update_railroad() {
5863
element.classList.add('grammar-hidden');
5964
}
6065
});
61-
const buttons = document.querySelectorAll('.grammar-toggle');
66+
const buttons = document.querySelectorAll('.grammar-toggle-railroad');
6267
buttons.forEach(button => {
6368
if (grammarRailroad) {
6469
button.innerText = "Hide Railroad";

0 commit comments

Comments
 (0)