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.expression ? astExpression(ast, depth, opts) : astGroup(ast, depth, opts); 99 100const astRepeating = (ast, depth, opts) => { 101 const label = `loop_${depth}`; 102 const count = `count_${depth}`; 103 return js` 104 ${label}: for (var ${count} = 0; true; ${count}++) { 105 ${assignIndex(depth)} 106 ${astChild( 107 ast, 108 depth, 109 newOpts(opts, { 110 onAbort: js` 111 if (${count}) { 112 ${restoreIndex(depth)} 113 break ${label}; 114 } else { 115 ${opts.onAbort || ''} 116 } 117 `, 118 }) 119 )} 120 } 121 `; 122}; 123 124const astMultiple = (ast, depth, opts) => { 125 const label = `loop_${depth}`; 126 return js` 127 ${label}: while (true) { 128 ${assignIndex(depth)} 129 ${astChild( 130 ast, 131 depth, 132 newOpts(opts, { 133 length: 0, 134 index: depth, 135 abort: js`break ${label};`, 136 onAbort: '', 137 }) 138 )} 139 } 140 `; 141}; 142 143const astOptional = (ast, depth, opts) => js` 144 ${assignIndex(depth)} 145 ${astChild( 146 ast, 147 depth, 148 newOpts(opts, { 149 index: depth, 150 abort: '', 151 onAbort: '', 152 }) 153 )} 154`; 155 156const astQuantifier = (ast, depth, opts) => { 157 const { index, abort } = opts; 158 const label = `invert_${depth}`; 159 160 if (ast.capture === '!') { 161 opts = newOpts(opts, { 162 index: depth, 163 abort: js`break ${label};`, 164 }); 165 } 166 167 let child; 168 if (ast.quantifier === '+') { 169 child = astRepeating(ast, depth, opts); 170 } else if (ast.quantifier === '*') child = astMultiple(ast, depth, opts); 171 else if (ast.quantifier === '?') child = astOptional(ast, depth, opts); 172 else child = astChild(ast, depth, opts); 173 174 if (ast.capture === '!') { 175 return js` 176 ${label}: { 177 ${assignIndex(depth)} 178 ${child} 179 ${restoreIndex(index)} 180 ${abort} 181 } 182 `; 183 } else if (ast.capture === '=') { 184 return js` 185 ${assignIndex(depth)} 186 ${child} 187 ${restoreIndex(depth)} 188 `; 189 } else { 190 return child; 191 } 192}; 193 194const astSequence = (ast, depth, opts) => { 195 const alternation = ast.alternation ? `alternation_${depth}` : ''; 196 197 let body = ''; 198 for (; ast; ast = ast.alternation) { 199 const block = `block_${depth}`; 200 201 let childOpts = opts; 202 if (ast.alternation) { 203 childOpts = newOpts(opts, { 204 index: depth, 205 abort: js`break ${block};`, 206 onAbort: '', 207 }); 208 } 209 210 let sequence = ''; 211 for (let i = 0; i < ast.sequence.length; i++) 212 sequence += astQuantifier(ast.sequence[i], depth, childOpts); 213 214 if (!ast.alternation) { 215 body += sequence; 216 } else { 217 body += js` 218 ${block}: { 219 ${assignIndex(depth)} 220 ${sequence} 221 break ${alternation}; 222 } 223 `; 224 } 225 } 226 227 if (!alternation) return body; 228 229 return js` 230 ${alternation}: { 231 ${body} 232 } 233 `; 234}; 235 236const astRoot = (ast, name, transform) => js` 237 (function (${_state}) { 238 ${assignIndex(1)} 239 var ${_node} = []; 240 var ${_match}; 241 242 ${astSequence(ast, 2, { 243 index: 1, 244 length: 0, 245 onAbort: '', 246 abort: js`return;`, 247 capture: true, 248 })} 249 250 ${_node}.tag = ${name}; 251 return ${transform ? js`(${transform})(${_node})` : _node}; 252 }) 253`; 254 255export { astRoot };