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 assignIndex = (depth) =>
13 depth ? js`var index_${depth} = ${_state}.index;` : '';
14
15const restoreIndex = (depth) =>
16 depth ? js`${_state}.index = index_${depth};` : '';
17
18const abortOnCondition = (condition, hooks) => js`
19 if (${condition}) {
20 ${restoreIndex(opts.index)}
21 ${opts.abort}
22 } else {
23 ${opts.onAbort}
24 }
25`;
26
27const astExpression = (ast, depth, opts) => {
28 const abort = js`
29 ${opts.onAbort}
30 ${restoreIndex(opts.index)}
31 ${
32 opts.length && opts.abort
33 ? js`
34 ${_node}.length = length_${opts.length};
35 `
36 : ''
37 }
38 ${opts.abort || ''}
39 `;
40
41 if (!opts.capturing) {
42 return js`
43 if (!(${ast.expression})) {
44 ${abort}
45 }
46 `;
47 }
48
49 return js`
50 if (${_match} = ${ast.expression}) {
51 ${_node}.push(${_match});
52 } else {
53 ${abort}
54 }
55 `;
56};
57
58const astGroup = (ast, depth, opts) => {
59 if (ast.sequence.length === 1)
60 return astExpression(ast.sequence[0], depth, opts);
61
62 const capturing = !!opts.capturing && !!ast.capturing;
63
64 let group = '';
65 if (!opts.length && capturing) {
66 return js`
67 ${js`var length_${depth} = ${_node}.length;`}
68 ${astSequence(ast.sequence, depth + 1, {
69 ...opts,
70 length: depth,
71 capturing,
72 })}
73 `;
74 }
75
76 return astSequence(ast.sequence, depth + 1, {
77 ...opts,
78 capturing,
79 });
80};
81
82const astChild = (ast, depth, opts) =>
83 ast.type === 'expression'
84 ? astExpression(ast, depth, opts)
85 : astGroup(ast, depth, opts);
86
87const astRepeating = (ast, depth, opts) => {
88 const label = `loop_${depth}`;
89 const count = `count_${depth}`;
90 return js`
91 ${label}: for (var ${count} = 0; true; ${count}++) {
92 ${assignIndex(depth)}
93 ${astChild(ast, depth, {
94 ...opts,
95 onAbort: js`
96 if (${count}) {
97 ${restoreIndex(depth)}
98 break ${label};
99 } else {
100 ${opts.onAbort}
101 }
102 `,
103 })}
104 }
105 `;
106};
107
108const astMultiple = (ast, depth, opts) => {
109 const label = `loop_${depth}`;
110 return js`
111 ${label}: while (true) {
112 ${assignIndex(depth)}
113 ${astChild(ast, depth, {
114 ...opts,
115 length: 0,
116 index: depth,
117 abort: js`break ${label};`,
118 onAbort: '',
119 })}
120 }
121 `;
122};
123
124const astOptional = (ast, depth, opts) => js`
125 ${assignIndex(depth)}
126 ${astChild(ast, depth, {
127 ...opts,
128 index: depth,
129 abort: '',
130 onAbort: '',
131 })}
132`;
133
134const astQuantifier = (ast, depth, opts) => {
135 const { index, abort } = opts;
136 const label = `invert_${depth}`;
137
138 if (ast.lookahead === 'negative') {
139 opts = {
140 ...opts,
141 index: depth,
142 abort: js`break ${label};`,
143 };
144 }
145
146 let child;
147 if (ast.quantifier === 'repeating') {
148 child = astRepeating(ast, depth, opts);
149 } else if (ast.quantifier === 'multiple')
150 child = astMultiple(ast, depth, opts);
151 else if (ast.quantifier === 'optional') child = astOptional(ast, depth, opts);
152 else child = astChild(ast, depth, opts);
153
154 if (ast.lookahead === 'negative') {
155 return js`
156 ${label}: {
157 ${assignIndex(depth)}
158 ${child}
159 ${restoreIndex(index)}
160 ${abort}
161 }
162 `;
163 } else if (ast.lookahead) {
164 return js`
165 ${assignIndex(depth)}
166 ${child}
167 ${restoreIndex(depth)}
168 `;
169 } else {
170 return child;
171 }
172};
173
174const astSequence = (ast, depth, opts) => {
175 const alternation = ast.alternation ? `alternation_${depth}` : '';
176
177 let body = '';
178 for (; ast; ast = ast.alternation) {
179 const block = `block_${depth}`;
180
181 let childOpts = opts;
182 if (ast.alternation) {
183 childOpts = {
184 ...childOpts,
185 index: depth,
186 abort: js`break ${block};`,
187 onAbort: '',
188 };
189 }
190
191 let sequence = '';
192 for (let i = 0; i < ast.sequence.length; i++)
193 sequence += astQuantifier(ast.sequence[i], depth, childOpts);
194
195 if (!ast.alternation) {
196 body += sequence;
197 } else {
198 body += js`
199 ${block}: {
200 ${assignIndex(depth)}
201 ${sequence}
202 break ${alternation};
203 }
204 `;
205 }
206 }
207
208 if (!alternation) return body;
209
210 return js`
211 ${alternation}: {
212 ${body}
213 }
214 `;
215};
216
217const astRoot = (ast, name, transform) => js`
218 (function (${_state}) {
219 ${assignIndex(1)}
220 var ${_node} = [];
221 var ${_match};
222
223 ${astSequence(ast, 2, {
224 index: 1,
225 length: 0,
226 onAbort: '',
227 abort: js`return;`,
228 capturing: true,
229 })}
230
231 ${_node}.tag = ${name};
232 return ${transform ? js`(${transform})(${_node})` : _node};
233 })
234`;
235
236export { astRoot };