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 capture: next.capture != null ? next.capture : prev.capture,
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.capture) {
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 capture = !!opts.capture && !ast.capture;
72
73 let group = '';
74 if (!opts.length && capture) {
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 capture,
83 })
84 )}
85 `;
86 }
87
88 return astSequence(
89 ast.sequence,
90 depth + 1,
91 newOpts(opts, {
92 capture,
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.capture === '!') {
163 opts = newOpts(opts, {
164 index: depth,
165 abort: js`break ${label};`,
166 });
167 }
168
169 let child;
170 if (ast.quantifier === '+') {
171 child = astRepeating(ast, depth, opts);
172 } else if (ast.quantifier === '*') child = astMultiple(ast, depth, opts);
173 else if (ast.quantifier === '?') child = astOptional(ast, depth, opts);
174 else child = astChild(ast, depth, opts);
175
176 if (ast.capture === '!') {
177 return js`
178 ${label}: {
179 ${assignIndex(depth)}
180 ${child}
181 ${restoreIndex(index)}
182 ${abort}
183 }
184 `;
185 } else if (ast.capture === '=') {
186 return js`
187 ${assignIndex(depth)}
188 ${child}
189 ${restoreIndex(depth)}
190 `;
191 } else {
192 return child;
193 }
194};
195
196const astSequence = (ast, depth, opts) => {
197 const alternation = ast.alternation ? `alternation_${depth}` : '';
198
199 let body = '';
200 for (; ast; ast = ast.alternation) {
201 const block = `block_${depth}`;
202
203 let childOpts = opts;
204 if (ast.alternation) {
205 childOpts = newOpts(opts, {
206 index: depth,
207 abort: js`break ${block};`,
208 onAbort: '',
209 });
210 }
211
212 let sequence = '';
213 for (let i = 0; i < ast.sequence.length; i++)
214 sequence += astQuantifier(ast.sequence[i], depth, childOpts);
215
216 if (!ast.alternation) {
217 body += sequence;
218 } else {
219 body += js`
220 ${block}: {
221 ${assignIndex(depth)}
222 ${sequence}
223 break ${alternation};
224 }
225 `;
226 }
227 }
228
229 if (!alternation) return body;
230
231 return js`
232 ${alternation}: {
233 ${body}
234 }
235 `;
236};
237
238const astRoot = (ast, name, transform) => js`
239 (function (${_state}) {
240 ${assignIndex(1)}
241 var ${_node} = [];
242 var ${_match};
243
244 ${astSequence(ast, 2, {
245 index: 1,
246 length: 0,
247 onAbort: '',
248 abort: js`return;`,
249 capture: true,
250 })}
251
252 ${_node}.tag = ${name};
253 return ${transform ? js`(${transform})(${_node})` : _node};
254 })
255`;
256
257export { astRoot };