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 assignIndex = (depth) => 13 depth ? js`var index_${depth} = ${_state}.index;` : ''; 14 15const restoreIndex = (depth) => 16 depth ? js`${_state}.index = index_${depth};` : ''; 17 18const abortOnCondition = (condition, hooks) => js` 19 if (${condition}) { 20 ${restoreIndex(opts.index)} 21 ${opts.abort} 22 } else { 23 ${opts.onAbort} 24 } 25`; 26 27const astExpression = (ast, depth, opts) => { 28 const abort = js` 29 ${opts.onAbort} 30 ${restoreIndex(opts.index)} 31 ${ 32 opts.length && opts.abort 33 ? js` 34 ${_node}.length = length_${opts.length}; 35 ` 36 : '' 37 } 38 ${opts.abort || ''} 39 `; 40 41 if (!opts.capturing) { 42 return js` 43 if (!(${ast.expression})) { 44 ${abort} 45 } 46 `; 47 } 48 49 return js` 50 if (${_match} = ${ast.expression}) { 51 ${_node}.push(${_match}); 52 } else { 53 ${abort} 54 } 55 `; 56}; 57 58const astGroup = (ast, depth, opts) => { 59 if (ast.sequence.length === 1) 60 return astExpression(ast.sequence[0], depth, opts); 61 62 const capturing = !!opts.capturing && !!ast.capturing; 63 64 let group = ''; 65 if (!opts.length && capturing) { 66 return js` 67 ${js`var length_${depth} = ${_node}.length;`} 68 ${astSequence(ast.sequence, depth + 1, { 69 ...opts, 70 length: depth, 71 capturing, 72 })} 73 `; 74 } 75 76 return astSequence(ast.sequence, depth + 1, { 77 ...opts, 78 capturing, 79 }); 80}; 81 82const astChild = (ast, depth, opts) => 83 ast.type === 'expression' 84 ? astExpression(ast, depth, opts) 85 : astGroup(ast, depth, opts); 86 87const astRepeating = (ast, depth, opts) => { 88 const label = `loop_${depth}`; 89 const count = `count_${depth}`; 90 return js` 91 ${label}: for (var ${count} = 0; true; ${count}++) { 92 ${assignIndex(depth)} 93 ${astChild(ast, depth, { 94 ...opts, 95 onAbort: js` 96 if (${count}) { 97 ${restoreIndex(depth)} 98 break ${label}; 99 } else { 100 ${opts.onAbort} 101 } 102 `, 103 })} 104 } 105 `; 106}; 107 108const astMultiple = (ast, depth, opts) => { 109 const label = `loop_${depth}`; 110 return js` 111 ${label}: while (true) { 112 ${assignIndex(depth)} 113 ${astChild(ast, depth, { 114 ...opts, 115 length: 0, 116 index: depth, 117 abort: js`break ${label};`, 118 onAbort: '', 119 })} 120 } 121 `; 122}; 123 124const astOptional = (ast, depth, opts) => js` 125 ${assignIndex(depth)} 126 ${astChild(ast, depth, { 127 ...opts, 128 index: depth, 129 abort: '', 130 onAbort: '', 131 })} 132`; 133 134const astQuantifier = (ast, depth, opts) => { 135 const { index, abort } = opts; 136 const label = `invert_${depth}`; 137 138 if (ast.lookahead === 'negative') { 139 opts = { 140 ...opts, 141 index: depth, 142 abort: js`break ${label};`, 143 }; 144 } 145 146 let child; 147 if (ast.quantifier === 'repeating') { 148 child = astRepeating(ast, depth, opts); 149 } else if (ast.quantifier === 'multiple') 150 child = astMultiple(ast, depth, opts); 151 else if (ast.quantifier === 'optional') child = astOptional(ast, depth, opts); 152 else child = astChild(ast, depth, opts); 153 154 if (ast.lookahead === 'negative') { 155 return js` 156 ${label}: { 157 ${assignIndex(depth)} 158 ${child} 159 ${restoreIndex(index)} 160 ${abort} 161 } 162 `; 163 } else if (ast.lookahead) { 164 return js` 165 ${assignIndex(depth)} 166 ${child} 167 ${restoreIndex(depth)} 168 `; 169 } else { 170 return child; 171 } 172}; 173 174const astSequence = (ast, depth, opts) => { 175 const alternation = ast.alternation ? `alternation_${depth}` : ''; 176 177 let body = ''; 178 for (; ast; ast = ast.alternation) { 179 const block = `block_${depth}`; 180 181 let childOpts = opts; 182 if (ast.alternation) { 183 childOpts = { 184 ...childOpts, 185 index: depth, 186 abort: js`break ${block};`, 187 onAbort: '', 188 }; 189 } 190 191 let sequence = ''; 192 for (let i = 0; i < ast.sequence.length; i++) 193 sequence += astQuantifier(ast.sequence[i], depth, childOpts); 194 195 if (!ast.alternation) { 196 body += sequence; 197 } else { 198 body += js` 199 ${block}: { 200 ${assignIndex(depth)} 201 ${sequence} 202 break ${alternation}; 203 } 204 `; 205 } 206 } 207 208 if (!alternation) return body; 209 210 return js` 211 ${alternation}: { 212 ${body} 213 } 214 `; 215}; 216 217const astRoot = (ast, name, transform) => js` 218 (function (${_state}) { 219 ${assignIndex(1)} 220 var ${_node} = []; 221 var ${_match}; 222 223 ${astSequence(ast, 2, { 224 index: 1, 225 length: 0, 226 onAbort: '', 227 abort: js`return;`, 228 capturing: true, 229 })} 230 231 ${_node}.tag = ${name}; 232 return ${transform ? js`(${transform})(${_node})` : _node}; 233 }) 234`; 235 236export { astRoot };