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