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