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