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