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