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