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