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