1
1
//! Converts a [`Grammar`] to an SVG railroad diagram.
2
2
3
- use super :: { Characters , Expression , ExpressionKind , Production } ;
3
+ use super :: { Characters , Expression , ExpressionKind , Production , RenderCtx } ;
4
4
use crate :: grammar:: Grammar ;
5
5
use anyhow:: bail;
6
6
use railroad:: * ;
7
7
use regex:: Regex ;
8
- use std:: collections:: HashMap ;
9
8
use std:: fmt:: Write ;
10
9
use std:: sync:: LazyLock ;
11
10
12
11
impl Grammar {
13
12
pub fn render_railroad (
14
13
& self ,
14
+ cx : & RenderCtx ,
15
15
names : & [ & str ] ,
16
- link_map : & HashMap < String , String > ,
17
- md_link_map : & HashMap < String , String > ,
18
16
output : & mut String ,
19
- for_summary : bool ,
20
17
) -> anyhow:: Result < ( ) > {
21
18
for name in names {
22
19
let prod = match self . productions . get ( * name) {
23
20
Some ( p) => p,
24
21
None => bail ! ( "could not find grammar production named `{name}`" ) ,
25
22
} ;
26
- prod. render_railroad ( link_map , md_link_map , output, for_summary ) ;
23
+ prod. render_railroad ( cx , output) ;
27
24
}
28
25
Ok ( ( ) )
29
26
}
@@ -39,40 +36,30 @@ pub fn railroad_id(name: &str, for_summary: bool) -> String {
39
36
}
40
37
41
38
impl Production {
42
- fn render_railroad (
43
- & self ,
44
- link_map : & HashMap < String , String > ,
45
- md_link_map : & HashMap < String , String > ,
46
- output : & mut String ,
47
- for_summary : bool ,
48
- ) {
49
- let mut dia = self . make_diagram ( false , link_map, md_link_map) ;
39
+ fn render_railroad ( & self , cx : & RenderCtx , output : & mut String ) {
40
+ let mut dia = self . make_diagram ( cx, false ) ;
50
41
// If the diagram is very wide, try stacking it to reduce the width.
51
42
// This 900 is somewhat arbitrary based on looking at productions that
52
43
// looked too squished. If your diagram is still too squished,
53
44
// consider adding more rules to shorten it.
54
45
if dia. width ( ) > 900 {
55
- dia = self . make_diagram ( true , link_map , md_link_map ) ;
46
+ dia = self . make_diagram ( cx , true ) ;
56
47
}
57
48
writeln ! (
58
49
output,
59
50
"<div style=\" width: {width}px; height: auto; max-width: 100%; max-height: 100%\" \
60
51
class=\" railroad-production\" \
61
52
id=\" {id}\" >{dia}</div>",
62
53
width = dia. width( ) ,
63
- id = railroad_id( & self . name, for_summary) ,
54
+ id = railroad_id( & self . name, cx . for_summary) ,
64
55
)
65
56
. unwrap ( ) ;
66
57
}
67
58
68
- fn make_diagram (
69
- & self ,
70
- stack : bool ,
71
- link_map : & HashMap < String , String > ,
72
- md_link_map : & HashMap < String , String > ,
73
- ) -> Diagram < Box < dyn Node > > {
74
- let n = self . expression . render_railroad ( stack, link_map) ;
75
- let dest = md_link_map
59
+ fn make_diagram ( & self , cx : & RenderCtx , stack : bool ) -> Diagram < Box < dyn Node > > {
60
+ let n = self . expression . render_railroad ( cx, stack) ;
61
+ let dest = cx
62
+ . md_link_map
76
63
. get ( & self . name )
77
64
. map ( |path| path. to_string ( ) )
78
65
. unwrap_or_else ( || format ! ( "missing" ) ) ;
@@ -88,11 +75,7 @@ impl Production {
88
75
}
89
76
90
77
impl Expression {
91
- fn render_railroad (
92
- & self ,
93
- stack : bool ,
94
- link_map : & HashMap < String , String > ,
95
- ) -> Option < Box < dyn Node > > {
78
+ fn render_railroad ( & self , cx : & RenderCtx , stack : bool ) -> Option < Box < dyn Node > > {
96
79
let mut state;
97
80
let mut state_ref = & self . kind ;
98
81
let n: Box < dyn Node > = ' l: loop {
@@ -101,12 +84,12 @@ impl Expression {
101
84
// Render grouped nodes and `e{1..1}` repeats directly.
102
85
ExpressionKind :: Grouped ( e)
103
86
| ExpressionKind :: RepeatRange ( e, Some ( 1 ) , Some ( 1 ) ) => {
104
- e. render_railroad ( stack , link_map ) ?
87
+ e. render_railroad ( cx , stack ) ?
105
88
}
106
89
ExpressionKind :: Alt ( es) => {
107
90
let choices: Vec < _ > = es
108
91
. iter ( )
109
- . map ( |e| e. render_railroad ( stack , link_map ) )
92
+ . map ( |e| e. render_railroad ( cx , stack ) )
110
93
. filter_map ( |n| n)
111
94
. collect ( ) ;
112
95
Box :: new ( Choice :: < Box < dyn Node > > :: new ( choices) )
@@ -116,7 +99,7 @@ impl Expression {
116
99
let make_seq = |es : & [ & Expression ] | {
117
100
let seq: Vec < _ > = es
118
101
. iter ( )
119
- . map ( |e| e. render_railroad ( stack , link_map ) )
102
+ . map ( |e| e. render_railroad ( cx , stack ) )
120
103
. filter_map ( |n| n)
121
104
. collect ( ) ;
122
105
let seq: Sequence < Box < dyn Node > > = Sequence :: new ( seq) ;
@@ -159,29 +142,29 @@ impl Expression {
159
142
// Treat `e?` and `e{..1}` / `e{0..1}` equally.
160
143
ExpressionKind :: Optional ( e)
161
144
| ExpressionKind :: RepeatRange ( e, None | Some ( 0 ) , Some ( 1 ) ) => {
162
- let n = e. render_railroad ( stack , link_map ) ?;
145
+ let n = e. render_railroad ( cx , stack ) ?;
163
146
Box :: new ( Optional :: new ( n) )
164
147
}
165
148
// Treat `e*` and `e{..}` / `e{0..}` equally.
166
149
ExpressionKind :: Repeat ( e)
167
150
| ExpressionKind :: RepeatRange ( e, None | Some ( 0 ) , None ) => {
168
- let n = e. render_railroad ( stack , link_map ) ?;
151
+ let n = e. render_railroad ( cx , stack ) ?;
169
152
Box :: new ( Optional :: new ( Repeat :: new ( n, railroad:: Empty ) ) )
170
153
}
171
154
ExpressionKind :: RepeatNonGreedy ( e) => {
172
- let n = e. render_railroad ( stack , link_map ) ?;
155
+ let n = e. render_railroad ( cx , stack ) ?;
173
156
let r = Box :: new ( Optional :: new ( Repeat :: new ( n, railroad:: Empty ) ) ) ;
174
157
let lbox = LabeledBox :: new ( r, Comment :: new ( "non-greedy" . to_string ( ) ) ) ;
175
158
Box :: new ( lbox)
176
159
}
177
160
// Treat `e+` and `e{1..}` equally.
178
161
ExpressionKind :: RepeatPlus ( e)
179
162
| ExpressionKind :: RepeatRange ( e, Some ( 1 ) , None ) => {
180
- let n = e. render_railroad ( stack , link_map ) ?;
163
+ let n = e. render_railroad ( cx , stack ) ?;
181
164
Box :: new ( Repeat :: new ( n, railroad:: Empty ) )
182
165
}
183
166
ExpressionKind :: RepeatPlusNonGreedy ( e) => {
184
- let n = e. render_railroad ( stack , link_map ) ?;
167
+ let n = e. render_railroad ( cx , stack ) ?;
185
168
let r = Repeat :: new ( n, railroad:: Empty ) ;
186
169
let lbox = LabeledBox :: new ( r, Comment :: new ( "non-greedy" . to_string ( ) ) ) ;
187
170
Box :: new ( lbox)
@@ -197,7 +180,7 @@ impl Expression {
197
180
}
198
181
// Render `e{1..b}` directly.
199
182
ExpressionKind :: RepeatRange ( e, Some ( 1 ) , Some ( b @ 2 ..) ) => {
200
- let n = e. render_railroad ( stack , link_map ) ?;
183
+ let n = e. render_railroad ( cx , stack ) ?;
201
184
let cmt = format ! ( "at most {b} more times" , b = b - 1 ) ;
202
185
let r = Repeat :: new ( n, Comment :: new ( cmt) ) ;
203
186
Box :: new ( r)
@@ -218,17 +201,17 @@ impl Expression {
218
201
state = ExpressionKind :: Sequence ( es) ;
219
202
break ' cont & state;
220
203
}
221
- ExpressionKind :: Nt ( nt) => node_for_nt ( link_map , nt) ,
204
+ ExpressionKind :: Nt ( nt) => node_for_nt ( cx , nt) ,
222
205
ExpressionKind :: Terminal ( t) => Box :: new ( Terminal :: new ( t. clone ( ) ) ) ,
223
206
ExpressionKind :: Prose ( s) => Box :: new ( Terminal :: new ( s. clone ( ) ) ) ,
224
207
ExpressionKind :: Break ( _) => return None ,
225
208
ExpressionKind :: Charset ( set) => {
226
- let ns: Vec < _ > = set. iter ( ) . map ( |c| c. render_railroad ( link_map ) ) . collect ( ) ;
209
+ let ns: Vec < _ > = set. iter ( ) . map ( |c| c. render_railroad ( cx ) ) . collect ( ) ;
227
210
Box :: new ( Choice :: < Box < dyn Node > > :: new ( ns) )
228
211
}
229
212
ExpressionKind :: NegExpression ( e) => {
230
- let n = e. render_railroad ( stack , link_map ) ?;
231
- let ch = node_for_nt ( link_map , "CHAR" ) ;
213
+ let n = e. render_railroad ( cx , stack ) ?;
214
+ let ch = node_for_nt ( cx , "CHAR" ) ;
232
215
Box :: new ( Except :: new ( Box :: new ( ch) , n) )
233
216
}
234
217
ExpressionKind :: Unicode ( s) => Box :: new ( Terminal :: new ( format ! ( "U+{}" , s) ) ) ,
@@ -248,17 +231,18 @@ impl Expression {
248
231
}
249
232
250
233
impl Characters {
251
- fn render_railroad ( & self , link_map : & HashMap < String , String > ) -> Box < dyn Node > {
234
+ fn render_railroad ( & self , cx : & RenderCtx ) -> Box < dyn Node > {
252
235
match self {
253
- Characters :: Named ( s) => node_for_nt ( link_map , s) ,
236
+ Characters :: Named ( s) => node_for_nt ( cx , s) ,
254
237
Characters :: Terminal ( s) => Box :: new ( Terminal :: new ( s. clone ( ) ) ) ,
255
238
Characters :: Range ( a, b) => Box :: new ( Terminal :: new ( format ! ( "{a}-{b}" ) ) ) ,
256
239
}
257
240
}
258
241
}
259
242
260
- fn node_for_nt ( link_map : & HashMap < String , String > , name : & str ) -> Box < dyn Node > {
261
- let dest = link_map
243
+ fn node_for_nt ( cx : & RenderCtx , name : & str ) -> Box < dyn Node > {
244
+ let dest = cx
245
+ . rr_link_map
262
246
. get ( name)
263
247
. map ( |path| path. to_string ( ) )
264
248
. unwrap_or_else ( || format ! ( "missing" ) ) ;
0 commit comments