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