Mirror: The magical sticky regex-based parser generator 馃
at v1.0.1 11 kB view raw
1let t; 2let ids = {}; 3 4export function initGenerator(_ids, _t) { 5 ids = _ids; 6 t = _t; 7} 8 9/** var id = state.index; */ 10class AssignIndexNode { 11 constructor(id) { 12 this.id = id; 13 } 14 15 statement() { 16 const member = t.memberExpression(ids.state, t.identifier('index')); 17 return t.variableDeclaration('var', [ 18 t.variableDeclarator(this.id, member), 19 ]); 20 } 21} 22 23/** state.index = id; */ 24class RestoreIndexNode { 25 constructor(id) { 26 this.id = id; 27 } 28 29 statement() { 30 const expression = t.assignmentExpression( 31 '=', 32 t.memberExpression(ids.state, t.identifier('index')), 33 this.id 34 ); 35 36 return t.expressionStatement(expression); 37 } 38} 39 40/** var id = node.length; */ 41class AssignLengthNode { 42 constructor(id) { 43 this.id = id; 44 } 45 46 statement() { 47 return t.variableDeclaration('var', [ 48 t.variableDeclarator( 49 this.id, 50 t.memberExpression(ids.node, t.identifier('length')) 51 ), 52 ]); 53 } 54} 55 56/** node.length = id; */ 57class RestoreLengthNode { 58 constructor(id) { 59 this.id = id; 60 } 61 62 statement() { 63 const expression = t.assignmentExpression( 64 '=', 65 t.memberExpression(ids.node, t.identifier('length')), 66 this.id 67 ); 68 69 return t.expressionStatement(expression); 70 } 71} 72 73/** return; break id; */ 74class AbortNode { 75 constructor(id) { 76 this.id = id || null; 77 } 78 79 statement() { 80 const statement = this.id ? t.breakStatement(this.id) : t.returnStatement(); 81 return statement; 82 } 83} 84 85/** if (condition) { return; break id; } */ 86class AbortConditionNode { 87 constructor(condition, opts) { 88 this.condition = condition || null; 89 90 this.abort = opts.abort; 91 this.abortCondition = opts.abortCondition || null; 92 this.restoreIndex = opts.restoreIndex; 93 } 94 95 statement() { 96 return t.ifStatement( 97 this.condition, 98 t.blockStatement( 99 [this.restoreIndex.statement(), this.abort.statement()].filter(Boolean) 100 ), 101 this.abortCondition ? this.abortCondition.statement() : null 102 ); 103 } 104} 105 106/** Generates a full matcher for an expression */ 107class ExpressionNode { 108 constructor(ast, depth, opts) { 109 this.ast = ast; 110 this.depth = depth || 0; 111 this.capturing = !!opts.capturing; 112 this.restoreIndex = opts.restoreIndex; 113 this.restoreLength = opts.restoreLength || null; 114 this.abortCondition = opts.abortCondition || null; 115 this.abort = opts.abort || null; 116 } 117 118 statements() { 119 const execMatch = this.ast.expression; 120 const assignMatch = t.assignmentExpression('=', ids.match, execMatch); 121 122 const successNodes = t.blockStatement([ 123 t.expressionStatement( 124 t.callExpression(t.memberExpression(ids.node, t.identifier('push')), [ 125 ids.match, 126 ]) 127 ), 128 ]); 129 130 const abortNodes = t.blockStatement( 131 [ 132 this.abortCondition && this.abortCondition.statement(), 133 this.abort && this.restoreLength && this.restoreLength.statement(), 134 this.restoreIndex && this.restoreIndex.statement(), 135 this.abort && this.abort.statement(), 136 ].filter(Boolean) 137 ); 138 139 return [ 140 !this.capturing 141 ? t.ifStatement(t.unaryExpression('!', execMatch), abortNodes) 142 : t.ifStatement(assignMatch, successNodes, abortNodes), 143 ]; 144 } 145} 146 147/** Generates a full matcher for a group */ 148class GroupNode { 149 constructor(ast, depth, opts) { 150 this.ast = ast; 151 this.depth = depth || 0; 152 if (ast.sequence.length === 1) { 153 return new ExpressionNode(ast.sequence[0], depth, opts); 154 } 155 156 const lengthId = t.identifier(`length_${depth}`); 157 const childOpts = { 158 ...opts, 159 capturing: !!opts.capturing && !!ast.capturing, 160 }; 161 162 this.assignLength = null; 163 if (!childOpts.restoreLength && childOpts.capturing) { 164 this.assignLength = new AssignLengthNode(lengthId); 165 childOpts.restoreLength = new RestoreLengthNode(lengthId); 166 } 167 168 this.alternation = new AlternationNode(ast.sequence, depth + 1, childOpts); 169 } 170 171 statements() { 172 return [ 173 this.assignLength && this.assignLength.statement(), 174 ...this.alternation.statements(), 175 ].filter(Boolean); 176 } 177} 178 179/** Generates looping logic around another group or expression matcher */ 180class QuantifierNode { 181 constructor(ast, depth, opts) { 182 const { quantifier } = ast; 183 this.ast = ast; 184 this.depth = depth || 0; 185 186 const invertId = t.identifier(`invert_${this.depth}`); 187 const loopId = t.identifier(`loop_${this.depth}`); 188 const iterId = t.identifier(`iter_${this.depth}`); 189 const indexId = t.identifier(`index_${this.depth}`); 190 const ChildNode = ast.type === 'group' ? GroupNode : ExpressionNode; 191 const childOpts = { ...opts }; 192 193 this.assignIndex = null; 194 this.restoreIndex = null; 195 this.blockId = null; 196 this.abort = null; 197 if (ast.type === 'group' && !!ast.lookahead) { 198 this.restoreIndex = new RestoreIndexNode(indexId); 199 this.assignIndex = new AssignIndexNode(indexId); 200 childOpts.restoreIndex = null; 201 } 202 203 if (ast.type === 'group' && ast.lookahead === 'negative') { 204 this.blockId = invertId; 205 this.abort = opts.abort; 206 childOpts.abort = new AbortNode(invertId); 207 } 208 209 if (quantifier && !quantifier.singular && quantifier.required) { 210 childOpts.abortCondition = new AbortConditionNode(iterId, { 211 ...opts, 212 restoreIndex: new RestoreIndexNode(indexId), 213 abort: new AbortNode(loopId), 214 }); 215 } else if (quantifier && !quantifier.singular) { 216 childOpts.restoreLength = null; 217 childOpts.restoreIndex = new RestoreIndexNode(indexId); 218 childOpts.abort = new AbortNode(loopId); 219 childOpts.abortCondition = null; 220 } else if (quantifier && !quantifier.required) { 221 childOpts.restoreIndex = new RestoreIndexNode(indexId); 222 childOpts.abortCondition = null; 223 childOpts.abort = null; 224 } 225 226 this.childNode = new ChildNode(ast, depth, childOpts); 227 } 228 229 statements() { 230 const { quantifier } = this.ast; 231 const loopId = t.identifier(`loop_${this.depth}`); 232 const iterId = t.identifier(`iter_${this.depth}`); 233 const indexId = t.identifier(`index_${this.depth}`); 234 const assignIndex = new AssignIndexNode(indexId); 235 236 let statements; 237 if (quantifier && !quantifier.singular && quantifier.required) { 238 statements = [ 239 t.labeledStatement( 240 loopId, 241 t.forStatement( 242 t.variableDeclaration('var', [ 243 t.variableDeclarator(iterId, t.numericLiteral(0)), 244 ]), 245 t.booleanLiteral(true), 246 t.updateExpression('++', iterId), 247 t.blockStatement([ 248 assignIndex.statement(), 249 ...this.childNode.statements(), 250 ]) 251 ) 252 ), 253 ]; 254 } else if (quantifier && !quantifier.singular) { 255 statements = [ 256 t.labeledStatement( 257 loopId, 258 t.whileStatement( 259 t.booleanLiteral(true), 260 t.blockStatement([ 261 assignIndex.statement(), 262 ...this.childNode.statements(), 263 ]) 264 ) 265 ), 266 ]; 267 } else if (quantifier && !quantifier.required) { 268 statements = [assignIndex.statement(), ...this.childNode.statements()]; 269 } else { 270 statements = this.childNode.statements(); 271 } 272 273 if (this.restoreIndex && this.assignIndex) { 274 statements.unshift(this.assignIndex.statement()); 275 statements.push(this.restoreIndex.statement()); 276 } 277 278 if (this.blockId) { 279 statements = [ 280 t.labeledStatement( 281 this.blockId, 282 t.blockStatement([...statements, this.abort.statement()]) 283 ), 284 ]; 285 } 286 287 return statements; 288 } 289} 290 291/** Generates a matcher of a sequence of sub-matchers for a single sequence */ 292class SequenceNode { 293 constructor(ast, depth, opts) { 294 this.ast = ast; 295 this.depth = depth || 0; 296 297 const indexId = t.identifier(`index_${depth}`); 298 const blockId = t.identifier(`block_${this.depth}`); 299 300 this.returnStatement = opts.returnStatement; 301 this.assignIndex = ast.alternation ? new AssignIndexNode(indexId) : null; 302 303 this.quantifiers = ast.sequence.map((childAst) => { 304 return new QuantifierNode(childAst, depth, { 305 ...opts, 306 restoreIndex: ast.alternation 307 ? new RestoreIndexNode(indexId) 308 : opts.restoreIndex, 309 abortCondition: ast.alternation ? null : opts.abortCondition, 310 abort: ast.alternation ? new AbortNode(blockId) : opts.abort, 311 }); 312 }); 313 } 314 315 statements() { 316 const blockId = t.identifier(`block_${this.depth}`); 317 const alternationId = t.identifier(`alternation_${this.depth}`); 318 const statements = this.quantifiers.reduce((block, node) => { 319 block.push(...node.statements()); 320 return block; 321 }, []); 322 323 if (!this.ast.alternation) { 324 return statements; 325 } 326 327 const abortNode = 328 this.depth === 0 ? this.returnStatement : t.breakStatement(alternationId); 329 330 return [ 331 t.labeledStatement( 332 blockId, 333 t.blockStatement([ 334 this.assignIndex && this.assignIndex.statement(), 335 ...statements, 336 abortNode, 337 ]) 338 ), 339 ]; 340 } 341} 342 343/** Generates matchers for sequences with (or without) alternations */ 344class AlternationNode { 345 constructor(ast, depth, opts) { 346 this.ast = ast; 347 this.depth = depth || 0; 348 this.sequences = []; 349 for (let current = ast; current; current = current.alternation) { 350 this.sequences.push(new SequenceNode(current, depth, opts)); 351 } 352 } 353 354 statements() { 355 if (this.sequences.length === 1) { 356 return this.sequences[0].statements(); 357 } 358 359 const statements = []; 360 for (let i = 0; i < this.sequences.length; i++) { 361 statements.push(...this.sequences[i].statements()); 362 } 363 364 if (this.depth === 0) { 365 return statements; 366 } 367 368 const alternationId = t.identifier(`alternation_${this.depth}`); 369 return [t.labeledStatement(alternationId, t.blockStatement(statements))]; 370 } 371} 372 373export class RootNode { 374 constructor(ast, nameNode, transformNode) { 375 const indexId = t.identifier('last_index'); 376 const node = t.callExpression(ids.tag, [ids.node, nameNode]); 377 378 this.returnStatement = t.returnStatement( 379 transformNode ? t.callExpression(transformNode, [node]) : node 380 ); 381 382 this.assignIndex = new AssignIndexNode(indexId); 383 this.node = new AlternationNode(ast, 0, { 384 returnStatement: this.returnStatement, 385 restoreIndex: new RestoreIndexNode(indexId, true), 386 restoreLength: null, 387 abortCondition: null, 388 abort: new AbortNode(), 389 capturing: true, 390 }); 391 } 392 393 statements() { 394 return [ 395 this.assignIndex.statement(), 396 t.variableDeclaration('var', [ 397 t.variableDeclarator(ids.match), 398 t.variableDeclarator(ids.node, t.arrayExpression()), 399 ]), 400 ...this.node.statements(), 401 this.returnStatement, 402 ]; 403 } 404}