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