1let t;
2let ids = {};
3
4export function initGenerator(_ids, _t) {
5 ids = _ids;
6 t = _t;
7}
8
9/** var id = state.index; */
10class AssignIndexNode {
11 constructor(id) {
12 this.id = id;
13 }
14
15 statement() {
16 const member = t.memberExpression(ids.state, t.identifier('index'));
17 return t.variableDeclaration('var', [
18 t.variableDeclarator(this.id, member),
19 ]);
20 }
21}
22
23/** state.index = id; */
24class RestoreIndexNode {
25 constructor(id) {
26 this.id = id;
27 }
28
29 statement() {
30 const expression = t.assignmentExpression(
31 '=',
32 t.memberExpression(ids.state, t.identifier('index')),
33 this.id
34 );
35
36 return t.expressionStatement(expression);
37 }
38}
39
40/** var id = node.length; */
41class AssignLengthNode {
42 constructor(id) {
43 this.id = id;
44 }
45
46 statement() {
47 return t.variableDeclaration('var', [
48 t.variableDeclarator(
49 this.id,
50 t.memberExpression(ids.node, t.identifier('length'))
51 ),
52 ]);
53 }
54}
55
56/** node.length = id; */
57class RestoreLengthNode {
58 constructor(id) {
59 this.id = id;
60 }
61
62 statement() {
63 const expression = t.assignmentExpression(
64 '=',
65 t.memberExpression(ids.node, t.identifier('length')),
66 this.id
67 );
68
69 return t.expressionStatement(expression);
70 }
71}
72
73/** return; break id; */
74class AbortNode {
75 constructor(id) {
76 this.id = id || null;
77 }
78
79 statement() {
80 const statement = this.id ? t.breakStatement(this.id) : t.returnStatement();
81 return statement;
82 }
83}
84
85/** if (condition) { return; break id; } */
86class AbortConditionNode {
87 constructor(condition, opts) {
88 this.condition = condition || null;
89
90 this.abort = opts.abort;
91 this.abortCondition = opts.abortCondition || null;
92 this.restoreIndex = opts.restoreIndex;
93 }
94
95 statement() {
96 return t.ifStatement(
97 this.condition,
98 t.blockStatement(
99 [this.restoreIndex.statement(), this.abort.statement()].filter(Boolean)
100 ),
101 this.abortCondition ? this.abortCondition.statement() : null
102 );
103 }
104}
105
106/** Generates a full matcher for an expression */
107class ExpressionNode {
108 constructor(ast, depth, opts) {
109 this.ast = ast;
110 this.depth = depth || 0;
111 this.capturing = !!opts.capturing;
112 this.restoreIndex = opts.restoreIndex;
113 this.restoreLength = opts.restoreLength || null;
114 this.abortCondition = opts.abortCondition || null;
115 this.abort = opts.abort || null;
116 }
117
118 statements() {
119 const execMatch = this.ast.expression;
120 const assignMatch = t.assignmentExpression('=', ids.match, execMatch);
121
122 const successNodes = t.blockStatement([
123 t.expressionStatement(
124 t.callExpression(t.memberExpression(ids.node, t.identifier('push')), [
125 ids.match,
126 ])
127 ),
128 ]);
129
130 const abortNodes = t.blockStatement(
131 [
132 this.abortCondition && this.abortCondition.statement(),
133 this.abort && this.restoreLength && this.restoreLength.statement(),
134 this.restoreIndex && this.restoreIndex.statement(),
135 this.abort && this.abort.statement(),
136 ].filter(Boolean)
137 );
138
139 return [
140 !this.capturing
141 ? t.ifStatement(t.unaryExpression('!', execMatch), abortNodes)
142 : t.ifStatement(assignMatch, successNodes, abortNodes),
143 ];
144 }
145}
146
147/** Generates a full matcher for a group */
148class GroupNode {
149 constructor(ast, depth, opts) {
150 this.ast = ast;
151 this.depth = depth || 0;
152 if (ast.sequence.length === 1) {
153 return new ExpressionNode(ast.sequence[0], depth, opts);
154 }
155
156 const lengthId = t.identifier(`length_${depth}`);
157 const childOpts = {
158 ...opts,
159 capturing: !!opts.capturing && !!ast.capturing,
160 };
161
162 this.assignLength = null;
163 if (!childOpts.restoreLength && childOpts.capturing) {
164 this.assignLength = new AssignLengthNode(lengthId);
165 childOpts.restoreLength = new RestoreLengthNode(lengthId);
166 }
167
168 this.alternation = new AlternationNode(ast.sequence, depth + 1, childOpts);
169 }
170
171 statements() {
172 return [
173 this.assignLength && this.assignLength.statement(),
174 ...this.alternation.statements(),
175 ].filter(Boolean);
176 }
177}
178
179/** Generates looping logic around another group or expression matcher */
180class QuantifierNode {
181 constructor(ast, depth, opts) {
182 const { quantifier } = ast;
183 this.ast = ast;
184 this.depth = depth || 0;
185
186 const invertId = t.identifier(`invert_${this.depth}`);
187 const loopId = t.identifier(`loop_${this.depth}`);
188 const iterId = t.identifier(`iter_${this.depth}`);
189 const indexId = t.identifier(`index_${this.depth}`);
190 const ChildNode = ast.type === 'group' ? GroupNode : ExpressionNode;
191 const childOpts = { ...opts };
192
193 this.assignIndex = null;
194 this.restoreIndex = null;
195 this.blockId = null;
196 this.abort = null;
197
198 if (ast.type === 'group' && !!ast.lookahead) {
199 this.restoreIndex = new RestoreIndexNode(indexId);
200 this.assignIndex = new AssignIndexNode(indexId);
201 }
202
203 if (ast.type === 'group' && ast.lookahead === 'negative') {
204 childOpts.abort = new AbortNode(invertId);
205 childOpts.restoreIndex = this.restoreIndex;
206 this.restoreIndex = opts.restoreIndex;
207 this.blockId = invertId;
208 this.abort = opts.abort;
209 }
210
211 if (quantifier && !quantifier.singular && quantifier.required) {
212 childOpts.abortCondition = new AbortConditionNode(iterId, {
213 ...opts,
214 restoreIndex: new RestoreIndexNode(indexId),
215 abort: new AbortNode(loopId),
216 });
217 } else if (quantifier && !quantifier.singular) {
218 childOpts.restoreLength = null;
219 childOpts.restoreIndex = new RestoreIndexNode(indexId);
220 childOpts.abort = new AbortNode(loopId);
221 childOpts.abortCondition = null;
222 } else if (quantifier && !quantifier.required) {
223 childOpts.restoreIndex = new RestoreIndexNode(indexId);
224 childOpts.abortCondition = null;
225 childOpts.abort = null;
226 }
227
228 this.childNode = new ChildNode(ast, depth, childOpts);
229 }
230
231 statements() {
232 const { quantifier } = this.ast;
233 const loopId = t.identifier(`loop_${this.depth}`);
234 const iterId = t.identifier(`iter_${this.depth}`);
235 const indexId = t.identifier(`index_${this.depth}`);
236 const assignIndex = new AssignIndexNode(indexId);
237
238 let statements;
239 if (quantifier && !quantifier.singular && quantifier.required) {
240 statements = [
241 t.labeledStatement(
242 loopId,
243 t.forStatement(
244 t.variableDeclaration('var', [
245 t.variableDeclarator(iterId, t.numericLiteral(0)),
246 ]),
247 t.booleanLiteral(true),
248 t.updateExpression('++', iterId),
249 t.blockStatement([
250 assignIndex.statement(),
251 ...this.childNode.statements(),
252 ])
253 )
254 ),
255 ];
256 } else if (quantifier && !quantifier.singular) {
257 statements = [
258 t.labeledStatement(
259 loopId,
260 t.whileStatement(
261 t.booleanLiteral(true),
262 t.blockStatement([
263 assignIndex.statement(),
264 ...this.childNode.statements(),
265 ])
266 )
267 ),
268 ];
269 } else if (quantifier && !quantifier.required) {
270 statements = [assignIndex.statement(), ...this.childNode.statements()];
271 } else {
272 statements = this.childNode.statements();
273 }
274
275 if (this.blockId && this.assignIndex && this.restoreIndex) {
276 statements = [
277 t.labeledStatement(
278 this.blockId,
279 t.blockStatement(
280 [
281 this.assignIndex.statement(),
282 ...statements,
283 this.restoreIndex.statement(),
284 this.abort.statement(),
285 ].filter(Boolean)
286 )
287 ),
288 ].filter(Boolean);
289 } else if (this.assignIndex && this.restoreIndex) {
290 statements.unshift(this.assignIndex.statement());
291 statements.push(this.restoreIndex.statement());
292 }
293
294 return statements;
295 }
296}
297
298/** Generates a matcher of a sequence of sub-matchers for a single sequence */
299class SequenceNode {
300 constructor(ast, depth, opts) {
301 this.ast = ast;
302 this.depth = depth || 0;
303
304 const indexId = t.identifier(`index_${depth}`);
305 const blockId = t.identifier(`block_${this.depth}`);
306
307 this.returnStatement = opts.returnStatement;
308 this.assignIndex = ast.alternation ? new AssignIndexNode(indexId) : null;
309
310 this.quantifiers = ast.sequence.map((childAst) => {
311 return new QuantifierNode(childAst, depth, {
312 ...opts,
313 restoreIndex: ast.alternation
314 ? new RestoreIndexNode(indexId)
315 : opts.restoreIndex,
316 abortCondition: ast.alternation ? null : opts.abortCondition,
317 abort: ast.alternation ? new AbortNode(blockId) : opts.abort,
318 });
319 });
320 }
321
322 statements() {
323 const blockId = t.identifier(`block_${this.depth}`);
324 const alternationId = t.identifier(`alternation_${this.depth}`);
325 const statements = this.quantifiers.reduce((block, node) => {
326 block.push(...node.statements());
327 return block;
328 }, []);
329
330 if (!this.ast.alternation) {
331 return statements;
332 }
333
334 const abortNode =
335 this.depth === 0 ? this.returnStatement : t.breakStatement(alternationId);
336
337 return [
338 t.labeledStatement(
339 blockId,
340 t.blockStatement([
341 this.assignIndex && this.assignIndex.statement(),
342 ...statements,
343 abortNode,
344 ])
345 ),
346 ];
347 }
348}
349
350/** Generates matchers for sequences with (or without) alternations */
351class AlternationNode {
352 constructor(ast, depth, opts) {
353 this.ast = ast;
354 this.depth = depth || 0;
355 this.sequences = [];
356 for (let current = ast; current; current = current.alternation) {
357 this.sequences.push(new SequenceNode(current, depth, opts));
358 }
359 }
360
361 statements() {
362 if (this.sequences.length === 1) {
363 return this.sequences[0].statements();
364 }
365
366 const statements = [];
367 for (let i = 0; i < this.sequences.length; i++) {
368 statements.push(...this.sequences[i].statements());
369 }
370
371 if (this.depth === 0) {
372 return statements;
373 }
374
375 const alternationId = t.identifier(`alternation_${this.depth}`);
376 return [t.labeledStatement(alternationId, t.blockStatement(statements))];
377 }
378}
379
380export class RootNode {
381 constructor(ast, nameNode, transformNode) {
382 const indexId = t.identifier('last_index');
383 const node = t.callExpression(ids.tag, [ids.node, nameNode]);
384
385 this.returnStatement = t.returnStatement(
386 transformNode ? t.callExpression(transformNode, [node]) : node
387 );
388
389 this.assignIndex = new AssignIndexNode(indexId);
390 this.node = new AlternationNode(ast, 0, {
391 returnStatement: this.returnStatement,
392 restoreIndex: new RestoreIndexNode(indexId, true),
393 restoreLength: null,
394 abortCondition: null,
395 abort: new AbortNode(),
396 capturing: true,
397 });
398 }
399
400 statements() {
401 return [
402 this.assignIndex.statement(),
403 t.variableDeclaration('var', [
404 t.variableDeclarator(ids.match),
405 t.variableDeclarator(ids.node, t.arrayExpression()),
406 ]),
407 ...this.node.statements(),
408 this.returnStatement,
409 ];
410 }
411}