1const _state = 'state';
2const _match = 'match';
3const _node = 'node';
4
5function js(/* arguments */) {
6 let body = arguments[0][0];
7 for (let i = 1; i < arguments.length; i++)
8 body = body + arguments[i] + arguments[0][i];
9 return body.trim();
10}
11
12const newOpts = (prev, next) => ({
13 index: next.index != null ? next.index : prev.index,
14 length: next.length != null ? next.length : prev.length,
15 onAbort: next.onAbort != null ? next.onAbort : prev.onAbort,
16 abort: next.abort != null ? next.abort : prev.abort,
17 capturing: next.capturing != null ? next.capturing : prev.capturing,
18});
19
20const assignIndex = (depth) =>
21 depth ? js`var index_${depth} = ${_state}.index;` : '';
22
23const restoreIndex = (depth) =>
24 depth ? js`${_state}.index = index_${depth};` : '';
25
26const abortOnCondition = (condition, hooks) => js`
27 if (${condition}) {
28 ${restoreIndex(opts.index)}
29 ${opts.abort || ''}
30 } else {
31 ${opts.onAbort || ''}
32 }
33`;
34
35const astExpression = (ast, depth, opts) => {
36 const restoreLength =
37 opts.length &&
38 opts.abort &&
39 js`
40 ${_node}.length = length_${opts.length};
41 `;
42
43 const abort = js`
44 ${opts.onAbort || ''}
45 ${restoreIndex(opts.index)}
46 ${restoreLength || ''}
47 ${opts.abort || ''}
48 `;
49
50 if (!opts.capturing) {
51 return js`
52 if (!(${ast.expression})) {
53 ${abort}
54 }
55 `;
56 }
57
58 return js`
59 if (${_match} = ${ast.expression}) {
60 ${_node}.push(${_match});
61 } else {
62 ${abort}
63 }
64 `;
65};
66
67const astGroup = (ast, depth, opts) => {
68 if (ast.sequence.length === 1)
69 return astExpression(ast.sequence[0], depth, opts);
70
71 const capturing = !!opts.capturing && !!ast.capturing;
72
73 let group = '';
74 if (!opts.length && capturing) {
75 return js`
76 ${js`var length_${depth} = ${_node}.length;`}
77 ${astSequence(
78 ast.sequence,
79 depth + 1,
80 newOpts(opts, {
81 length: depth,
82 capturing,
83 })
84 )}
85 `;
86 }
87
88 return astSequence(
89 ast.sequence,
90 depth + 1,
91 newOpts(opts, {
92 capturing,
93 })
94 );
95};
96
97const astChild = (ast, depth, opts) =>
98 ast.type === 'expression'
99 ? astExpression(ast, depth, opts)
100 : astGroup(ast, depth, opts);
101
102const astRepeating = (ast, depth, opts) => {
103 const label = `loop_${depth}`;
104 const count = `count_${depth}`;
105 return js`
106 ${label}: for (var ${count} = 0; true; ${count}++) {
107 ${assignIndex(depth)}
108 ${astChild(
109 ast,
110 depth,
111 newOpts(opts, {
112 onAbort: js`
113 if (${count}) {
114 ${restoreIndex(depth)}
115 break ${label};
116 } else {
117 ${opts.onAbort || ''}
118 }
119 `,
120 })
121 )}
122 }
123 `;
124};
125
126const astMultiple = (ast, depth, opts) => {
127 const label = `loop_${depth}`;
128 return js`
129 ${label}: while (true) {
130 ${assignIndex(depth)}
131 ${astChild(
132 ast,
133 depth,
134 newOpts(opts, {
135 length: 0,
136 index: depth,
137 abort: js`break ${label};`,
138 onAbort: '',
139 })
140 )}
141 }
142 `;
143};
144
145const astOptional = (ast, depth, opts) => js`
146 ${assignIndex(depth)}
147 ${astChild(
148 ast,
149 depth,
150 newOpts(opts, {
151 index: depth,
152 abort: '',
153 onAbort: '',
154 })
155 )}
156`;
157
158const astQuantifier = (ast, depth, opts) => {
159 const { index, abort } = opts;
160 const label = `invert_${depth}`;
161
162 if (ast.lookahead === 'negative') {
163 opts = newOpts(opts, {
164 index: depth,
165 abort: js`break ${label};`,
166 });
167 }
168
169 let child;
170 if (ast.quantifier === 'repeating') {
171 child = astRepeating(ast, depth, opts);
172 } else if (ast.quantifier === 'multiple')
173 child = astMultiple(ast, depth, opts);
174 else if (ast.quantifier === 'optional') child = astOptional(ast, depth, opts);
175 else child = astChild(ast, depth, opts);
176
177 if (ast.lookahead === 'negative') {
178 return js`
179 ${label}: {
180 ${assignIndex(depth)}
181 ${child}
182 ${restoreIndex(index)}
183 ${abort}
184 }
185 `;
186 } else if (ast.lookahead) {
187 return js`
188 ${assignIndex(depth)}
189 ${child}
190 ${restoreIndex(depth)}
191 `;
192 } else {
193 return child;
194 }
195};
196
197const astSequence = (ast, depth, opts) => {
198 const alternation = ast.alternation ? `alternation_${depth}` : '';
199
200 let body = '';
201 for (; ast; ast = ast.alternation) {
202 const block = `block_${depth}`;
203
204 let childOpts = opts;
205 if (ast.alternation) {
206 childOpts = newOpts(opts, {
207 index: depth,
208 abort: js`break ${block};`,
209 onAbort: '',
210 });
211 }
212
213 let sequence = '';
214 for (let i = 0; i < ast.sequence.length; i++)
215 sequence += astQuantifier(ast.sequence[i], depth, childOpts);
216
217 if (!ast.alternation) {
218 body += sequence;
219 } else {
220 body += js`
221 ${block}: {
222 ${assignIndex(depth)}
223 ${sequence}
224 break ${alternation};
225 }
226 `;
227 }
228 }
229
230 if (!alternation) return body;
231
232 return js`
233 ${alternation}: {
234 ${body}
235 }
236 `;
237};
238
239const astRoot = (ast, name, transform) => js`
240 (function (${_state}) {
241 ${assignIndex(1)}
242 var ${_node} = [];
243 var ${_match};
244
245 ${astSequence(ast, 2, {
246 index: 1,
247 length: 0,
248 onAbort: '',
249 abort: js`return;`,
250 capturing: true,
251 })}
252
253 ${_node}.tag = ${name};
254 return ${transform ? js`(${transform})(${_node})` : _node};
255 })
256`;
257
258export { astRoot };