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