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 loop = `loop_${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 ${loop};`;
94
95 child = js`
96 ${loop}: for (;;) {
97 ${assignIndex(depth)}
98 ${astChild(ast, depth, opts)}
99 }
100 `;
101 } else if (ast.quantifier === '?') {
102 opts.index = depth;
103 opts.abort = '';
104
105 child = js`
106 ${assignIndex(depth)}
107 ${astChild(ast, depth, opts)}
108 `;
109 } else {
110 child = astChild(ast, depth, opts);
111 }
112
113 if (ast.capture === '!') {
114 return js`
115 ${invert}: {
116 ${assignIndex(depth)}
117 ${child}
118 ${restoreIndex(index)}
119 ${abort}
120 }
121 `;
122 } else if (ast.capture === '=') {
123 return js`
124 ${assignIndex(depth)}
125 ${child}
126 ${restoreIndex(depth)}
127 `;
128 } else {
129 return child;
130 }
131};
132
133const astSequence = (ast, depth, opts) => {
134 const alternation = ast.alternation ? `alt_${depth}` : '';
135
136 let body = '';
137 for (; ast; ast = ast.alternation) {
138 const block = `block_${depth}`;
139
140 let childOpts = opts;
141 if (ast.alternation) {
142 childOpts = copy(opts);
143 childOpts.index = depth;
144 childOpts.abort = js`break ${block};`;
145 }
146
147 let sequence = '';
148 for (let i = 0; i < ast.length; i++)
149 sequence += astQuantifier(ast[i], depth, childOpts);
150
151 if (!ast.alternation) {
152 body += sequence;
153 } else {
154 body += js`
155 ${block}: {
156 ${assignIndex(depth)}
157 ${sequence}
158 break ${alternation};
159 }
160 `;
161 }
162 }
163
164 if (!alternation) return body;
165
166 return js`
167 ${alternation}: {
168 ${body}
169 }
170 `;
171};
172
173const astRoot = (ast, name, transform) => {
174 return js`
175 (function (${_state}) {
176 ${assignIndex(1)}
177 var ${_node} = [];
178 var ${_match};
179
180 ${astSequence(ast, 2, {
181 index: 1,
182 length: 0,
183 abort: js`return;`,
184 capture: true,
185 })}
186
187 ${_node}.tag = ${name};
188 return ${transform ? js`(${transform})(${_node})` : _node};
189 })
190 `;
191};
192
193export { astRoot };