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