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