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 if (ast.type === 'group' && !!ast.lookahead) {
198 this.restoreIndex = new RestoreIndexNode(indexId);
199 this.assignIndex = new AssignIndexNode(indexId);
200 childOpts.restoreIndex = null;
201 }
202
203 if (ast.type === 'group' && ast.lookahead === 'negative') {
204 this.blockId = invertId;
205 this.abort = opts.abort;
206 childOpts.abort = new AbortNode(invertId);
207 }
208
209 if (quantifier && !quantifier.singular && quantifier.required) {
210 childOpts.abortCondition = new AbortConditionNode(iterId, {
211 ...opts,
212 restoreIndex: new RestoreIndexNode(indexId),
213 abort: new AbortNode(loopId),
214 });
215 } else if (quantifier && !quantifier.singular) {
216 childOpts.restoreLength = null;
217 childOpts.restoreIndex = new RestoreIndexNode(indexId);
218 childOpts.abort = new AbortNode(loopId);
219 childOpts.abortCondition = null;
220 } else if (quantifier && !quantifier.required) {
221 childOpts.restoreIndex = new RestoreIndexNode(indexId);
222 childOpts.abortCondition = null;
223 childOpts.abort = null;
224 }
225
226 this.childNode = new ChildNode(ast, depth, childOpts);
227 }
228
229 statements() {
230 const { quantifier } = this.ast;
231 const loopId = t.identifier(`loop_${this.depth}`);
232 const iterId = t.identifier(`iter_${this.depth}`);
233 const indexId = t.identifier(`index_${this.depth}`);
234 const assignIndex = new AssignIndexNode(indexId);
235
236 let statements;
237 if (quantifier && !quantifier.singular && quantifier.required) {
238 statements = [
239 t.labeledStatement(
240 loopId,
241 t.forStatement(
242 t.variableDeclaration('var', [
243 t.variableDeclarator(iterId, t.numericLiteral(0)),
244 ]),
245 t.booleanLiteral(true),
246 t.updateExpression('++', iterId),
247 t.blockStatement([
248 assignIndex.statement(),
249 ...this.childNode.statements(),
250 ])
251 )
252 ),
253 ];
254 } else if (quantifier && !quantifier.singular) {
255 statements = [
256 t.labeledStatement(
257 loopId,
258 t.whileStatement(
259 t.booleanLiteral(true),
260 t.blockStatement([
261 assignIndex.statement(),
262 ...this.childNode.statements(),
263 ])
264 )
265 ),
266 ];
267 } else if (quantifier && !quantifier.required) {
268 statements = [assignIndex.statement(), ...this.childNode.statements()];
269 } else {
270 statements = this.childNode.statements();
271 }
272
273 if (this.restoreIndex && this.assignIndex) {
274 statements.unshift(this.assignIndex.statement());
275 statements.push(this.restoreIndex.statement());
276 }
277
278 if (this.blockId) {
279 statements = [
280 t.labeledStatement(
281 this.blockId,
282 t.blockStatement([...statements, this.abort.statement()])
283 ),
284 ];
285 }
286
287 return statements;
288 }
289}
290
291/** Generates a matcher of a sequence of sub-matchers for a single sequence */
292class SequenceNode {
293 constructor(ast, depth, opts) {
294 this.ast = ast;
295 this.depth = depth || 0;
296
297 const indexId = t.identifier(`index_${depth}`);
298 const blockId = t.identifier(`block_${this.depth}`);
299
300 this.returnStatement = opts.returnStatement;
301 this.assignIndex = ast.alternation ? new AssignIndexNode(indexId) : null;
302
303 this.quantifiers = ast.sequence.map((childAst) => {
304 return new QuantifierNode(childAst, depth, {
305 ...opts,
306 restoreIndex: ast.alternation
307 ? new RestoreIndexNode(indexId)
308 : opts.restoreIndex,
309 abortCondition: ast.alternation ? null : opts.abortCondition,
310 abort: ast.alternation ? new AbortNode(blockId) : opts.abort,
311 });
312 });
313 }
314
315 statements() {
316 const blockId = t.identifier(`block_${this.depth}`);
317 const alternationId = t.identifier(`alternation_${this.depth}`);
318 const statements = this.quantifiers.reduce((block, node) => {
319 block.push(...node.statements());
320 return block;
321 }, []);
322
323 if (!this.ast.alternation) {
324 return statements;
325 }
326
327 const abortNode =
328 this.depth === 0 ? this.returnStatement : t.breakStatement(alternationId);
329
330 return [
331 t.labeledStatement(
332 blockId,
333 t.blockStatement([
334 this.assignIndex && this.assignIndex.statement(),
335 ...statements,
336 abortNode,
337 ])
338 ),
339 ];
340 }
341}
342
343/** Generates matchers for sequences with (or without) alternations */
344class AlternationNode {
345 constructor(ast, depth, opts) {
346 this.ast = ast;
347 this.depth = depth || 0;
348 this.sequences = [];
349 for (let current = ast; current; current = current.alternation) {
350 this.sequences.push(new SequenceNode(current, depth, opts));
351 }
352 }
353
354 statements() {
355 if (this.sequences.length === 1) {
356 return this.sequences[0].statements();
357 }
358
359 const statements = [];
360 for (let i = 0; i < this.sequences.length; i++) {
361 statements.push(...this.sequences[i].statements());
362 }
363
364 if (this.depth === 0) {
365 return statements;
366 }
367
368 const alternationId = t.identifier(`alternation_${this.depth}`);
369 return [t.labeledStatement(alternationId, t.blockStatement(statements))];
370 }
371}
372
373export class RootNode {
374 constructor(ast, nameNode, transformNode) {
375 const indexId = t.identifier('last_index');
376 const node = t.callExpression(ids.tag, [ids.node, nameNode]);
377
378 this.returnStatement = t.returnStatement(
379 transformNode ? t.callExpression(transformNode, [node]) : node
380 );
381
382 this.assignIndex = new AssignIndexNode(indexId);
383 this.node = new AlternationNode(ast, 0, {
384 returnStatement: this.returnStatement,
385 restoreIndex: new RestoreIndexNode(indexId, true),
386 restoreLength: null,
387 abortCondition: null,
388 abort: new AbortNode(),
389 capturing: true,
390 });
391 }
392
393 statements() {
394 return [
395 this.assignIndex.statement(),
396 t.variableDeclaration('var', [
397 t.variableDeclarator(ids.match),
398 t.variableDeclarator(ids.node, t.arrayExpression()),
399 ]),
400 ...this.node.statements(),
401 this.returnStatement,
402 ];
403 }
404}