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 const capture = !!opts.capture && !ast.capture;
69
70 let group = '';
71 if (!opts.length && capture) {
72 return js`
73 ${js`var length_${depth} = ${_node}.length;`}
74 ${astSequence(
75 ast.sequence,
76 depth + 1,
77 newOpts(opts, {
78 length: depth,
79 capture,
80 })
81 )}
82 `;
83 }
84
85 return astSequence(
86 ast.sequence,
87 depth + 1,
88 newOpts(opts, {
89 capture,
90 })
91 );
92};
93
94const astChild = (ast, depth, opts) =>
95 ast.expression ? astExpression(ast, depth, opts) : astGroup(ast, depth, opts);
96
97const astRepeating = (ast, depth, opts) => {
98 const label = `loop_${depth}`;
99 const count = `count_${depth}`;
100 return js`
101 ${label}: for (var ${count} = 0; true; ${count}++) {
102 ${assignIndex(depth)}
103 ${astChild(
104 ast,
105 depth,
106 newOpts(opts, {
107 onAbort: js`
108 if (${count}) {
109 ${restoreIndex(depth)}
110 break ${label};
111 } else {
112 ${opts.onAbort || ''}
113 }
114 `,
115 })
116 )}
117 }
118 `;
119};
120
121const astMultiple = (ast, depth, opts) => {
122 const label = `loop_${depth}`;
123 return js`
124 ${label}: while (true) {
125 ${assignIndex(depth)}
126 ${astChild(
127 ast,
128 depth,
129 newOpts(opts, {
130 length: 0,
131 index: depth,
132 abort: js`break ${label};`,
133 onAbort: '',
134 })
135 )}
136 }
137 `;
138};
139
140const astOptional = (ast, depth, opts) => js`
141 ${assignIndex(depth)}
142 ${astChild(
143 ast,
144 depth,
145 newOpts(opts, {
146 index: depth,
147 abort: '',
148 onAbort: '',
149 })
150 )}
151`;
152
153const astQuantifier = (ast, depth, opts) => {
154 const { index, abort } = opts;
155 const label = `invert_${depth}`;
156
157 if (ast.capture === '!') {
158 opts = newOpts(opts, {
159 index: depth,
160 abort: js`break ${label};`,
161 });
162 }
163
164 let child;
165 if (ast.quantifier === '+') {
166 child = astRepeating(ast, depth, opts);
167 } else if (ast.quantifier === '*') child = astMultiple(ast, depth, opts);
168 else if (ast.quantifier === '?') child = astOptional(ast, depth, opts);
169 else child = astChild(ast, depth, opts);
170
171 if (ast.capture === '!') {
172 return js`
173 ${label}: {
174 ${assignIndex(depth)}
175 ${child}
176 ${restoreIndex(index)}
177 ${abort}
178 }
179 `;
180 } else if (ast.capture === '=') {
181 return js`
182 ${assignIndex(depth)}
183 ${child}
184 ${restoreIndex(depth)}
185 `;
186 } else {
187 return child;
188 }
189};
190
191const astSequence = (ast, depth, opts) => {
192 const alternation = ast.alternation ? `alternation_${depth}` : '';
193
194 let body = '';
195 for (; ast; ast = ast.alternation) {
196 const block = `block_${depth}`;
197
198 let childOpts = opts;
199 if (ast.alternation) {
200 childOpts = newOpts(opts, {
201 index: depth,
202 abort: js`break ${block};`,
203 onAbort: '',
204 });
205 }
206
207 let sequence = '';
208 for (let i = 0; i < ast.length; i++)
209 sequence += astQuantifier(ast[i], depth, childOpts);
210
211 if (!ast.alternation) {
212 body += sequence;
213 } else {
214 body += js`
215 ${block}: {
216 ${assignIndex(depth)}
217 ${sequence}
218 break ${alternation};
219 }
220 `;
221 }
222 }
223
224 if (!alternation) return body;
225
226 return js`
227 ${alternation}: {
228 ${body}
229 }
230 `;
231};
232
233const astRoot = (ast, name, transform) => js`
234 (function (${_state}) {
235 ${assignIndex(1)}
236 var ${_node} = [];
237 var ${_match};
238
239 ${astSequence(ast, 2, {
240 index: 1,
241 length: 0,
242 onAbort: '',
243 abort: js`return;`,
244 capture: true,
245 })}
246
247 ${_node}.tag = ${name};
248 return ${transform ? js`(${transform})(${_node})` : _node};
249 })
250`;
251
252export { astRoot };