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 capture: next.capture != null ? next.capture : prev.capture, 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.capture) { 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 capture = !!opts.capture && !ast.capture; 72 73 let group = ''; 74 if (!opts.length && capture) { 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 capture, 83 }) 84 )} 85 `; 86 } 87 88 return astSequence( 89 ast.sequence, 90 depth + 1, 91 newOpts(opts, { 92 capture, 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.capture === '!') { 163 opts = newOpts(opts, { 164 index: depth, 165 abort: js`break ${label};`, 166 }); 167 } 168 169 let child; 170 if (ast.quantifier === '+') { 171 child = astRepeating(ast, depth, opts); 172 } else if (ast.quantifier === '*') child = astMultiple(ast, depth, opts); 173 else if (ast.quantifier === '?') child = astOptional(ast, depth, opts); 174 else child = astChild(ast, depth, opts); 175 176 if (ast.capture === '!') { 177 return js` 178 ${label}: { 179 ${assignIndex(depth)} 180 ${child} 181 ${restoreIndex(index)} 182 ${abort} 183 } 184 `; 185 } else if (ast.capture === '=') { 186 return js` 187 ${assignIndex(depth)} 188 ${child} 189 ${restoreIndex(depth)} 190 `; 191 } else { 192 return child; 193 } 194}; 195 196const astSequence = (ast, depth, opts) => { 197 const alternation = ast.alternation ? `alternation_${depth}` : ''; 198 199 let body = ''; 200 for (; ast; ast = ast.alternation) { 201 const block = `block_${depth}`; 202 203 let childOpts = opts; 204 if (ast.alternation) { 205 childOpts = newOpts(opts, { 206 index: depth, 207 abort: js`break ${block};`, 208 onAbort: '', 209 }); 210 } 211 212 let sequence = ''; 213 for (let i = 0; i < ast.sequence.length; i++) 214 sequence += astQuantifier(ast.sequence[i], depth, childOpts); 215 216 if (!ast.alternation) { 217 body += sequence; 218 } else { 219 body += js` 220 ${block}: { 221 ${assignIndex(depth)} 222 ${sequence} 223 break ${alternation}; 224 } 225 `; 226 } 227 } 228 229 if (!alternation) return body; 230 231 return js` 232 ${alternation}: { 233 ${body} 234 } 235 `; 236}; 237 238const astRoot = (ast, name, transform) => js` 239 (function (${_state}) { 240 ${assignIndex(1)} 241 var ${_node} = []; 242 var ${_match}; 243 244 ${astSequence(ast, 2, { 245 index: 1, 246 length: 0, 247 onAbort: '', 248 abort: js`return;`, 249 capture: true, 250 })} 251 252 ${_node}.tag = ${name}; 253 return ${transform ? js`(${transform})(${_node})` : _node}; 254 }) 255`; 256 257export { astRoot };