Mirror: The magical sticky regex-based parser generator 🧙

Replace Babel code generator with string-based code generator

+397 -585
+115 -103
src/babel/__snapshots__/plugin.test.js.snap
··· 9 9 _node_expression2 = (0, _reghex._pattern)(2); 10 10 11 11 const node = function _node(state) { 12 - var last_index = state.index; 13 - var match, 14 - node = []; 12 + var index_1 = state.index; 13 + var node = []; 14 + var match; 15 15 16 16 if (match = (0, _reghex._exec)(state, _node_expression)) { 17 17 node.push(match); 18 18 } else { 19 - state.index = last_index; 19 + state.index = index_1; 20 20 return; 21 21 } 22 22 23 23 if (match = (0, _reghex._exec)(state, _node_expression2)) { 24 24 node.push(match); 25 25 } else { 26 - state.index = last_index; 26 + state.index = index_1; 27 27 return; 28 28 } 29 29 30 - return (0, _reghex.tag)(node, 'node'); 30 + node.tag = 'node'; 31 + return node; 31 32 };" 32 33 `; 33 34 34 35 exports[`works with local recursion 1`] = ` 35 - "import { tag, _exec, _substr, _pattern } from 'reghex'; 36 + "import { tag, _exec, _pattern } from 'reghex'; 36 37 37 38 const inner = function _inner(state) { 38 - var last_index = state.index; 39 - var match, 40 - node = []; 39 + var index_1 = state.index; 40 + var node = []; 41 + var match; 41 42 42 - if (match = _substr(state, \\"inner\\")) { 43 + if (match = _exec(state, \\"inner\\")) { 43 44 node.push(match); 44 45 } else { 45 - state.index = last_index; 46 + state.index = index_1; 46 47 return; 47 48 } 48 49 49 - return tag(node, 'inner'); 50 + node.tag = 'inner'; 51 + return node; 50 52 }; 51 53 52 54 const node = function _node(state) { 53 - var last_index = state.index; 54 - var match, 55 - node = []; 55 + var index_1 = state.index; 56 + var node = []; 57 + var match; 56 58 57 59 if (match = inner(state)) { 58 60 node.push(match); 59 61 } else { 60 - state.index = last_index; 62 + state.index = index_1; 61 63 return; 62 64 } 63 65 64 - return tag(node, 'node'); 66 + node.tag = 'node'; 67 + return node; 65 68 };" 66 69 `; 67 70 68 71 exports[`works with non-capturing groups 1`] = ` 69 - "import { _exec, _substr, _pattern, tag as _tag } from 'reghex'; 72 + "import { _exec, _pattern, tag as _tag } from 'reghex'; 70 73 71 74 var _node_expression = _pattern(1), 72 75 _node_expression2 = _pattern(2), 73 76 _node_expression3 = _pattern(3); 74 77 75 78 const node = function _node(state) { 76 - var last_index = state.index; 77 - var match, 78 - node = []; 79 + var index_1 = state.index; 80 + var node = []; 81 + var match; 79 82 80 83 if (match = _exec(state, _node_expression)) { 81 84 node.push(match); 82 85 } else { 83 - state.index = last_index; 86 + state.index = index_1; 84 87 return; 85 88 } 86 89 87 - var length_0 = node.length; 90 + var length_2 = node.length; 88 91 89 - alternation_1: { 90 - block_1: { 91 - var index_1 = state.index; 92 + alternation_3: { 93 + block_3: { 94 + var index_3 = state.index; 92 95 93 96 if (match = _exec(state, _node_expression2)) { 94 97 node.push(match); 95 98 } else { 96 - node.length = length_0; 97 - state.index = index_1; 98 - break block_1; 99 + state.index = index_3; 100 + node.length = length_2; 101 + break block_3; 99 102 } 100 103 101 - break alternation_1; 104 + break alternation_3; 102 105 } 103 106 104 - loop_1: for (var iter_1 = 0; true; iter_1++) { 105 - var index_1 = state.index; 107 + loop_3: for (var count_3 = 0; true; count_3++) { 108 + var index_3 = state.index; 106 109 107 110 if (!_exec(state, _node_expression3)) { 108 - if (iter_1) { 109 - state.index = index_1; 110 - break loop_1; 111 - } 111 + if (count_3) { 112 + state.index = index_3; 113 + break loop_3; 114 + } else {} 112 115 113 - node.length = length_0; 114 - state.index = last_index; 116 + state.index = index_1; 117 + node.length = length_2; 115 118 return; 116 119 } 117 120 } 118 121 } 119 122 120 - return _tag(node, 'node'); 123 + node.tag = 'node'; 124 + return node; 121 125 };" 122 126 `; 123 127 124 128 exports[`works with standard features 1`] = ` 125 - "import { _exec, _substr, _pattern, tag as _tag } from \\"reghex\\"; 129 + "import { _exec, _pattern, tag as _tag } from \\"reghex\\"; 126 130 127 131 var _node_expression = _pattern(1), 128 132 _node_expression2 = _pattern(2), ··· 131 135 _node_expression5 = _pattern(5); 132 136 133 137 const node = function _node(state) { 134 - var last_index = state.index; 135 - var match, 136 - node = []; 138 + var index_1 = state.index; 139 + var node = []; 140 + var match; 137 141 138 - block_0: { 139 - var index_0 = state.index; 142 + alternation_2: { 143 + block_2: { 144 + var index_2 = state.index; 140 145 141 - loop_0: for (var iter_0 = 0; true; iter_0++) { 142 - var index_0 = state.index; 146 + loop_2: for (var count_2 = 0; true; count_2++) { 147 + var index_2 = state.index; 143 148 144 - if (match = _exec(state, _node_expression)) { 145 - node.push(match); 146 - } else { 147 - if (iter_0) { 148 - state.index = index_0; 149 - break loop_0; 150 - } 149 + if (match = _exec(state, _node_expression)) { 150 + node.push(match); 151 + } else { 152 + if (count_2) { 153 + state.index = index_2; 154 + break loop_2; 155 + } else {} 151 156 152 - state.index = index_0; 153 - break block_0; 157 + state.index = index_2; 158 + break block_2; 159 + } 154 160 } 161 + 162 + break alternation_2; 155 163 } 156 164 157 - return _tag(node, 'node'); 158 - } 165 + loop_2: for (var count_2 = 0; true; count_2++) { 166 + var index_2 = state.index; 159 167 160 - loop_0: for (var iter_0 = 0; true; iter_0++) { 161 - var index_0 = state.index; 168 + if (match = _exec(state, _node_expression2)) { 169 + node.push(match); 170 + } else { 171 + if (count_2) { 172 + state.index = index_2; 173 + break loop_2; 174 + } else {} 162 175 163 - if (match = _exec(state, _node_expression2)) { 164 - node.push(match); 165 - } else { 166 - if (iter_0) { 167 - state.index = index_0; 168 - break loop_0; 176 + state.index = index_1; 177 + return; 169 178 } 170 - 171 - state.index = last_index; 172 - return; 173 179 } 174 - } 175 180 176 - loop_0: while (true) { 177 - var index_0 = state.index; 178 - var length_0 = node.length; 181 + loop_2: while (true) { 182 + var index_2 = state.index; 183 + var length_2 = node.length; 179 184 180 - if (match = _exec(state, _node_expression3)) { 181 - node.push(match); 182 - } else { 183 - node.length = length_0; 184 - state.index = index_0; 185 - break loop_0; 186 - } 185 + if (match = _exec(state, _node_expression3)) { 186 + node.push(match); 187 + } else { 188 + state.index = index_2; 189 + node.length = length_2; 190 + break loop_2; 191 + } 187 192 188 - var index_2 = state.index; 193 + var index_4 = state.index; 189 194 190 - if (match = _exec(state, _node_expression4)) { 191 - node.push(match); 192 - } else { 193 - state.index = index_2; 194 - } 195 + if (match = _exec(state, _node_expression4)) { 196 + node.push(match); 197 + } else { 198 + state.index = index_4; 199 + } 195 200 196 - if (match = _exec(state, _node_expression5)) { 197 - node.push(match); 198 - } else { 199 - node.length = length_0; 200 - state.index = index_0; 201 - break loop_0; 201 + if (match = _exec(state, _node_expression5)) { 202 + node.push(match); 203 + } else { 204 + state.index = index_2; 205 + node.length = length_2; 206 + break loop_2; 207 + } 202 208 } 203 209 } 204 210 205 - return _tag(node, 'node'); 211 + node.tag = 'node'; 212 + return node; 206 213 };" 207 214 `; 208 215 209 216 exports[`works with transform functions 1`] = ` 210 - "import { _exec, _substr, _pattern, tag as _tag } from 'reghex'; 217 + "import { _exec, _pattern, tag as _tag } from 'reghex'; 211 218 212 219 var _inner_transform = x => x; 213 220 214 221 const first = function _inner(state) { 215 - var last_index = state.index; 216 - var match, 217 - node = []; 218 - return _inner_transform(_tag(node, 'inner')); 222 + var index_1 = state.index; 223 + var node = []; 224 + var match; 225 + node.tag = 'inner'; 226 + return; 227 + 228 + _inner_transform(node); 219 229 }; 220 230 221 231 const transform = x => x; 222 232 223 233 const second = function _node(state) { 224 - var last_index = state.index; 225 - var match, 226 - node = []; 227 - return transform(_tag(node, 'node')); 234 + var index_1 = state.index; 235 + var node = []; 236 + var match; 237 + node.tag = 'node'; 238 + return; 239 + transform(node); 228 240 };" 229 241 `;
+11 -9
src/babel/__tests__/suite.js
··· 1 - import * as reghex from '../../..'; 1 + import * as reghex from '../../../src/core'; 2 2 import * as types from '@babel/types'; 3 + import template from '@babel/template'; 3 4 import { transform } from '@babel/core'; 4 5 import { makeHelpers } from '../transform'; 5 6 6 7 const match = (name) => (quasis, ...expressions) => { 7 - const helpers = makeHelpers(types); 8 + const helpers = makeHelpers({ types, template }); 8 9 9 10 let str = ''; 10 11 for (let i = 0; i < quasis.length; i++) { ··· 12 13 if (i < expressions.length) str += '${' + expressions[i].toString() + '}'; 13 14 } 14 15 15 - const template = `(function () { return match('${name}')\`${str}\`; })()`; 16 - 17 16 const testPlugin = () => ({ 18 17 visitor: { 19 18 TaggedTemplateExpression(path) { ··· 22 21 }, 23 22 }); 24 23 25 - const { code } = transform(template, { 26 - babelrc: false, 27 - presets: [], 28 - plugins: [testPlugin], 29 - }); 24 + const { code } = transform( 25 + `(function () { return match('${name}')\`${str}\`; })()`, 26 + { 27 + babelrc: false, 28 + presets: [], 29 + plugins: [testPlugin], 30 + } 31 + ); 30 32 31 33 const argKeys = Object.keys(reghex).filter((x) => { 32 34 return x.startsWith('_') || x === 'tag';
-411
src/babel/generator.js
··· 1 - let t; 2 - let ids = {}; 3 - 4 - export function initGenerator(_ids, _t) { 5 - ids = _ids; 6 - t = _t; 7 - } 8 - 9 - /** var id = state.index; */ 10 - class 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; */ 24 - class 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; */ 41 - class 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; */ 57 - class 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; */ 74 - class 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; } */ 86 - class 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 */ 107 - class 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 */ 148 - class 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 */ 180 - class 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 - 198 - if (ast.type === 'group' && !!ast.lookahead) { 199 - this.restoreIndex = new RestoreIndexNode(indexId); 200 - this.assignIndex = new AssignIndexNode(indexId); 201 - } 202 - 203 - if (ast.type === 'group' && ast.lookahead === 'negative') { 204 - childOpts.abort = new AbortNode(invertId); 205 - childOpts.restoreIndex = this.restoreIndex; 206 - this.restoreIndex = opts.restoreIndex; 207 - this.blockId = invertId; 208 - this.abort = opts.abort; 209 - } 210 - 211 - if (quantifier && !quantifier.singular && quantifier.required) { 212 - childOpts.abortCondition = new AbortConditionNode(iterId, { 213 - ...opts, 214 - restoreIndex: new RestoreIndexNode(indexId), 215 - abort: new AbortNode(loopId), 216 - }); 217 - } else if (quantifier && !quantifier.singular) { 218 - childOpts.restoreLength = null; 219 - childOpts.restoreIndex = new RestoreIndexNode(indexId); 220 - childOpts.abort = new AbortNode(loopId); 221 - childOpts.abortCondition = null; 222 - } else if (quantifier && !quantifier.required) { 223 - childOpts.restoreIndex = new RestoreIndexNode(indexId); 224 - childOpts.abortCondition = null; 225 - childOpts.abort = null; 226 - } 227 - 228 - this.childNode = new ChildNode(ast, depth, childOpts); 229 - } 230 - 231 - statements() { 232 - const { quantifier } = this.ast; 233 - const loopId = t.identifier(`loop_${this.depth}`); 234 - const iterId = t.identifier(`iter_${this.depth}`); 235 - const indexId = t.identifier(`index_${this.depth}`); 236 - const assignIndex = new AssignIndexNode(indexId); 237 - 238 - let statements; 239 - if (quantifier && !quantifier.singular && quantifier.required) { 240 - statements = [ 241 - t.labeledStatement( 242 - loopId, 243 - t.forStatement( 244 - t.variableDeclaration('var', [ 245 - t.variableDeclarator(iterId, t.numericLiteral(0)), 246 - ]), 247 - t.booleanLiteral(true), 248 - t.updateExpression('++', iterId), 249 - t.blockStatement([ 250 - assignIndex.statement(), 251 - ...this.childNode.statements(), 252 - ]) 253 - ) 254 - ), 255 - ]; 256 - } else if (quantifier && !quantifier.singular) { 257 - statements = [ 258 - t.labeledStatement( 259 - loopId, 260 - t.whileStatement( 261 - t.booleanLiteral(true), 262 - t.blockStatement([ 263 - assignIndex.statement(), 264 - ...this.childNode.statements(), 265 - ]) 266 - ) 267 - ), 268 - ]; 269 - } else if (quantifier && !quantifier.required) { 270 - statements = [assignIndex.statement(), ...this.childNode.statements()]; 271 - } else { 272 - statements = this.childNode.statements(); 273 - } 274 - 275 - if (this.blockId && this.assignIndex && this.restoreIndex) { 276 - statements = [ 277 - t.labeledStatement( 278 - this.blockId, 279 - t.blockStatement( 280 - [ 281 - this.assignIndex.statement(), 282 - ...statements, 283 - this.restoreIndex.statement(), 284 - this.abort.statement(), 285 - ].filter(Boolean) 286 - ) 287 - ), 288 - ].filter(Boolean); 289 - } else if (this.assignIndex && this.restoreIndex) { 290 - statements.unshift(this.assignIndex.statement()); 291 - statements.push(this.restoreIndex.statement()); 292 - } 293 - 294 - return statements; 295 - } 296 - } 297 - 298 - /** Generates a matcher of a sequence of sub-matchers for a single sequence */ 299 - class SequenceNode { 300 - constructor(ast, depth, opts) { 301 - this.ast = ast; 302 - this.depth = depth || 0; 303 - 304 - const indexId = t.identifier(`index_${depth}`); 305 - const blockId = t.identifier(`block_${this.depth}`); 306 - 307 - this.returnStatement = opts.returnStatement; 308 - this.assignIndex = ast.alternation ? new AssignIndexNode(indexId) : null; 309 - 310 - this.quantifiers = ast.sequence.map((childAst) => { 311 - return new QuantifierNode(childAst, depth, { 312 - ...opts, 313 - restoreIndex: ast.alternation 314 - ? new RestoreIndexNode(indexId) 315 - : opts.restoreIndex, 316 - abortCondition: ast.alternation ? null : opts.abortCondition, 317 - abort: ast.alternation ? new AbortNode(blockId) : opts.abort, 318 - }); 319 - }); 320 - } 321 - 322 - statements() { 323 - const blockId = t.identifier(`block_${this.depth}`); 324 - const alternationId = t.identifier(`alternation_${this.depth}`); 325 - const statements = this.quantifiers.reduce((block, node) => { 326 - block.push(...node.statements()); 327 - return block; 328 - }, []); 329 - 330 - if (!this.ast.alternation) { 331 - return statements; 332 - } 333 - 334 - const abortNode = 335 - this.depth === 0 ? this.returnStatement : t.breakStatement(alternationId); 336 - 337 - return [ 338 - t.labeledStatement( 339 - blockId, 340 - t.blockStatement([ 341 - this.assignIndex && this.assignIndex.statement(), 342 - ...statements, 343 - abortNode, 344 - ]) 345 - ), 346 - ]; 347 - } 348 - } 349 - 350 - /** Generates matchers for sequences with (or without) alternations */ 351 - class AlternationNode { 352 - constructor(ast, depth, opts) { 353 - this.ast = ast; 354 - this.depth = depth || 0; 355 - this.sequences = []; 356 - for (let current = ast; current; current = current.alternation) { 357 - this.sequences.push(new SequenceNode(current, depth, opts)); 358 - } 359 - } 360 - 361 - statements() { 362 - if (this.sequences.length === 1) { 363 - return this.sequences[0].statements(); 364 - } 365 - 366 - const statements = []; 367 - for (let i = 0; i < this.sequences.length; i++) { 368 - statements.push(...this.sequences[i].statements()); 369 - } 370 - 371 - if (this.depth === 0) { 372 - return statements; 373 - } 374 - 375 - const alternationId = t.identifier(`alternation_${this.depth}`); 376 - return [t.labeledStatement(alternationId, t.blockStatement(statements))]; 377 - } 378 - } 379 - 380 - export class RootNode { 381 - constructor(ast, nameNode, transformNode) { 382 - const indexId = t.identifier('last_index'); 383 - const node = t.callExpression(ids.tag, [ids.node, nameNode]); 384 - 385 - this.returnStatement = t.returnStatement( 386 - transformNode ? t.callExpression(transformNode, [node]) : node 387 - ); 388 - 389 - this.assignIndex = new AssignIndexNode(indexId); 390 - this.node = new AlternationNode(ast, 0, { 391 - returnStatement: this.returnStatement, 392 - restoreIndex: new RestoreIndexNode(indexId, true), 393 - restoreLength: null, 394 - abortCondition: null, 395 - abort: new AbortNode(), 396 - capturing: true, 397 - }); 398 - } 399 - 400 - statements() { 401 - return [ 402 - this.assignIndex.statement(), 403 - t.variableDeclaration('var', [ 404 - t.variableDeclarator(ids.match), 405 - t.variableDeclarator(ids.node, t.arrayExpression()), 406 - ]), 407 - ...this.node.statements(), 408 - this.returnStatement, 409 - ]; 410 - } 411 - }
+2 -2
src/babel/macro.js
··· 1 1 import { createMacro } from 'babel-plugin-macros'; 2 2 import { makeHelpers } from './transform'; 3 3 4 - function reghexMacro({ references, babel: { types: t } }) { 5 - const helpers = makeHelpers(t); 4 + function reghexMacro({ references, babel }) { 5 + const helpers = makeHelpers(babel); 6 6 const defaultRefs = references.default || []; 7 7 8 8 defaultRefs.forEach((ref) => {
+2 -2
src/babel/plugin.js
··· 1 1 import { makeHelpers } from './transform'; 2 2 3 - export default function reghexPlugin({ types }) { 3 + export default function reghexPlugin(babel) { 4 4 let helpers; 5 5 6 6 return { 7 7 name: 'reghex', 8 8 visitor: { 9 9 Program() { 10 - helpers = makeHelpers(types); 10 + helpers = makeHelpers(babel); 11 11 }, 12 12 ImportDeclaration(path) { 13 13 helpers.updateImport(path);
-17
src/babel/sharedIds.js
··· 2 2 constructor(t) { 3 3 this.t = t; 4 4 this.execId = t.identifier('_exec'); 5 - this.substrId = t.identifier('_substr'); 6 5 this.patternId = t.identifier('_pattern'); 7 6 this.tagId = t.identifier('tag'); 8 7 } 9 8 10 - get node() { 11 - return this.t.identifier('node'); 12 - } 13 - 14 - get match() { 15 - return this.t.identifier('match'); 16 - } 17 - 18 - get state() { 19 - return this.t.identifier('state'); 20 - } 21 - 22 9 get exec() { 23 10 return this.t.identifier(this.execId.name); 24 - } 25 - 26 - get substr() { 27 - return this.t.identifier(this.substrId.name); 28 11 } 29 12 30 13 get pattern() {
+18 -29
src/babel/transform.js
··· 1 1 import { parse } from '../parser'; 2 + import { astRoot } from '../codegen'; 2 3 import { SharedIds } from './sharedIds'; 3 - import { initGenerator, RootNode } from './generator'; 4 4 5 - export function makeHelpers(t) { 5 + export function makeHelpers({ types: t, template }) { 6 6 const regexPatternsRe = /^[()\[\]|.+?*]|[^\\][()\[\]|.+?*$^]|\\[wdsWDS]/; 7 7 const importSourceRe = /reghex$|^reghex\/macro/; 8 8 const importName = 'reghex'; 9 9 const ids = new SharedIds(t); 10 - initGenerator(ids, t); 11 10 12 11 let _hasUpdatedImport = false; 13 12 ··· 34 33 t.importSpecifier( 35 34 (ids.execId = path.scope.generateUidIdentifier('exec')), 36 35 t.identifier('_exec') 37 - ), 38 - t.importSpecifier( 39 - (ids.substrId = path.scope.generateUidIdentifier('substr')), 40 - t.identifier('_substr') 41 36 ), 42 37 t.importSpecifier( 43 38 (ids.patternId = path.scope.generateUidIdentifier('pattern')), ··· 160 155 } 161 156 162 157 return hoistedExpressions.map((id) => { 163 - // Use _substr helper instead if the expression is a string 164 - if (t.isStringLiteral(id)) { 165 - return t.callExpression(ids.substr, [ids.state, id]); 166 - } 167 - 168 - // Directly call expression if it's sure to be another matcher 169 158 const binding = path.scope.getBinding(id.name); 170 159 if (binding && t.isVariableDeclarator(binding.path.node)) { 171 160 const matchPath = binding.path.get('init'); 172 - if (this.isMatch(matchPath)) { 173 - return t.callExpression(id, [ids.state]); 174 - } 161 + if (this.isMatch(matchPath)) return `${id.name}(state)`; 175 162 } 176 163 177 - return t.callExpression(ids.exec, [ids.state, id]); 164 + const input = t.isStringLiteral(id) 165 + ? JSON.stringify(id.value) 166 + : id.name; 167 + return `${ids.exec.name}(state, ${input})`; 178 168 }); 179 169 }, 180 170 181 171 _prepareTransform(path) { 182 172 const transformNode = path.node.tag.arguments[1]; 173 + 183 174 if (!transformNode) return null; 184 - if (t.isIdentifier(transformNode)) return transformNode; 175 + if (t.isIdentifier(transformNode)) return transformNode.name; 185 176 186 177 const matchName = this.getMatchName(path); 187 178 const id = path.scope.generateUidIdentifier(`${matchName}_transform`); ··· 190 181 path 191 182 .getStatementParent() 192 183 .insertBefore(t.variableDeclaration('var', [declarator])); 193 - return id; 184 + 185 + return id.name; 194 186 }, 195 187 196 188 transformMatch(path) { ··· 203 195 } 204 196 205 197 const matchName = this.getMatchName(path); 206 - const nameNode = path.node.tag.arguments[0]; 198 + const name = path.node.tag.arguments[0]; 207 199 const quasis = path.node.quasi.quasis.map((x) => x.value.cooked); 208 200 209 201 const expressions = this._prepareExpressions(path); 210 - const transformNode = this._prepareTransform(path); 202 + const transform = this._prepareTransform(path); 211 203 212 204 let ast; 213 205 try { ··· 217 209 throw path.get('quasi').buildCodeFrameError(error.message); 218 210 } 219 211 220 - const generator = new RootNode(ast, nameNode, transformNode); 221 - const body = t.blockStatement(generator.statements()); 222 - const matchFunctionId = path.scope.generateUidIdentifier(matchName); 223 - const matchFunction = t.functionExpression( 224 - matchFunctionId, 225 - [ids.state], 226 - body 212 + const id = path.scope.generateUidIdentifier(matchName).name; 213 + const code = astRoot(ast, id, '%%name%%', transform && '%%transform%%'); 214 + 215 + path.replaceWith( 216 + template.expression(code)(transform ? { name, transform } : { name }) 227 217 ); 228 - path.replaceWith(matchFunction); 229 218 }, 230 219 }; 231 220 }
+238
src/codegen.js
··· 1 + const _state = 'state'; 2 + const _match = 'match'; 3 + const _node = 'node'; 4 + 5 + function 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 '\n' + body.trim(); 10 + } 11 + 12 + const assignIndex = (depth) => 13 + depth ? js`var index_${depth} = ${_state}.index;` : ''; 14 + 15 + const restoreIndex = (depth) => 16 + depth ? js`${_state}.index = index_${depth};` : ''; 17 + 18 + const abortOnCondition = (condition, hooks) => js` 19 + if (${condition}) { 20 + ${restoreIndex(opts.index)} 21 + ${opts.abort} 22 + } else { 23 + ${opts.onAbort} 24 + } 25 + `; 26 + 27 + const astExpression = (ast, depth, opts) => { 28 + const abort = js` 29 + ${opts.onAbort} 30 + ${restoreIndex(opts.index)} 31 + ${ 32 + opts.length && opts.abort 33 + ? js` 34 + ${_node}.length = length_${opts.length}; 35 + ` 36 + : '' 37 + } 38 + ${opts.abort || ''} 39 + `; 40 + 41 + if (!opts.capturing) { 42 + return js` 43 + if (!(${ast.expression})) { 44 + ${abort} 45 + } 46 + `; 47 + } 48 + 49 + return js` 50 + if (${_match} = ${ast.expression}) { 51 + ${_node}.push(${_match}); 52 + } else { 53 + ${abort} 54 + } 55 + `; 56 + }; 57 + 58 + const astGroup = (ast, depth, opts) => { 59 + if (ast.sequence.length === 1) 60 + return astExpression(ast.sequence[0], depth, opts); 61 + 62 + const capturing = !!opts.capturing && !!ast.capturing; 63 + 64 + let group = ''; 65 + if (!opts.length && capturing) { 66 + return js` 67 + ${js`var length_${depth} = ${_node}.length;`} 68 + ${astAlternation(ast.sequence, depth + 1, { 69 + ...opts, 70 + length: depth, 71 + capturing, 72 + })} 73 + `; 74 + } 75 + 76 + return astAlternation(ast.sequence, depth + 1, { 77 + ...opts, 78 + capturing, 79 + }); 80 + }; 81 + 82 + const astChild = (ast, depth, opts) => 83 + ast.type === 'expression' 84 + ? astExpression(ast, depth, opts) 85 + : astGroup(ast, depth, opts); 86 + 87 + const astRepeating = (ast, depth, opts) => { 88 + const label = `loop_${depth}`; 89 + const count = `count_${depth}`; 90 + return js` 91 + ${label}: for (var ${count} = 0; true; ${count}++) { 92 + ${assignIndex(depth)} 93 + ${astChild(ast, depth, { 94 + ...opts, 95 + onAbort: js` 96 + if (${count}) { 97 + ${restoreIndex(depth)} 98 + break ${label}; 99 + } else { 100 + ${opts.onAbort} 101 + } 102 + `, 103 + })} 104 + } 105 + `; 106 + }; 107 + 108 + const astMultiple = (ast, depth, opts) => { 109 + const label = `loop_${depth}`; 110 + return js` 111 + ${label}: while (true) { 112 + ${assignIndex(depth)} 113 + ${astChild(ast, depth, { 114 + ...opts, 115 + length: 0, 116 + index: depth, 117 + abort: js`break ${label};`, 118 + onAbort: '', 119 + })} 120 + } 121 + `; 122 + }; 123 + 124 + const astOptional = (ast, depth, opts) => js` 125 + ${assignIndex(depth)} 126 + ${astChild(ast, depth, { 127 + ...opts, 128 + index: depth, 129 + abort: '', 130 + onAbort: '', 131 + })} 132 + `; 133 + 134 + const astQuantifier = (ast, depth, opts) => { 135 + const { index, abort } = opts; 136 + const label = `invert_${depth}`; 137 + 138 + if (ast.lookahead === 'negative') { 139 + opts = { 140 + ...opts, 141 + index: depth, 142 + abort: js`break ${label};`, 143 + }; 144 + } 145 + 146 + let child; 147 + if (ast.quantifier && !ast.quantifier.singular && ast.quantifier.required) { 148 + child = astRepeating(ast, depth, opts); 149 + } else if (ast.quantifier && !ast.quantifier.singular) 150 + child = astMultiple(ast, depth, opts); 151 + else if (ast.quantifier && !ast.quantifier.required) 152 + 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 + 175 + const astSequence = (ast, depth, opts) => { 176 + const block = `block_${depth}`; 177 + const alternation = `alternation_${depth}`; 178 + 179 + if (ast.alternation) { 180 + opts = { 181 + ...opts, 182 + index: depth, 183 + abort: js`break ${block};`, 184 + onAbort: '', 185 + }; 186 + } 187 + 188 + let sequence = ''; 189 + for (let i = 0; i < ast.sequence.length; i++) 190 + sequence += astQuantifier(ast.sequence[i], depth, opts); 191 + 192 + if (!ast.alternation) { 193 + return sequence; 194 + } 195 + 196 + return js` 197 + ${block}: { 198 + ${assignIndex(depth)} 199 + ${sequence} 200 + break ${alternation}; 201 + } 202 + `; 203 + }; 204 + 205 + const astAlternation = (ast, depth, opts) => { 206 + if (!ast.alternation) return astSequence(ast, depth, opts); 207 + 208 + let sequence = ''; 209 + for (let child = ast; child; child = child.alternation) 210 + sequence += astSequence(child, depth, opts); 211 + 212 + return js` 213 + alternation_${depth}: { 214 + ${sequence} 215 + } 216 + `; 217 + }; 218 + 219 + const astRoot = (ast, id, name, transform) => js` 220 + function ${id}(${_state}) { 221 + ${assignIndex(1)} 222 + var ${_node} = []; 223 + var ${_match}; 224 + 225 + ${astAlternation(ast, 2, { 226 + index: 1, 227 + length: 0, 228 + onAbort: '', 229 + abort: js`return;`, 230 + capturing: true, 231 + })} 232 + 233 + ${_node}.tag = ${name}; 234 + return ${transform ? js`(${transform})(${_node})` : _node}; 235 + } 236 + `; 237 + 238 + export { astRoot };
+11 -12
src/core.js
··· 9 9 : new RegExp(`^(?:${source})`, 'g'); 10 10 }; 11 11 12 - export const _substr = (state, pattern) => { 13 - const end = state.index + pattern.length; 14 - const sub = state.input.slice(state.index, end); 15 - if (sub === pattern) { 16 - state.index = end; 17 - return sub; 18 - } 19 - }; 20 - 21 12 export const _exec = (state, pattern) => { 22 - if (typeof pattern === 'function') return pattern(); 13 + let match; 23 14 24 - let match; 25 - if (isStickySupported) { 15 + if (typeof pattern === 'function') { 16 + return pattern(state); 17 + } else if (typeof pattern === 'string') { 18 + const end = state.index + pattern.length; 19 + const sub = state.input.slice(state.index, end); 20 + if (sub === pattern) { 21 + state.index = end; 22 + match = sub; 23 + } 24 + } else if (isStickySupported) { 26 25 pattern.lastIndex = state.index; 27 26 if (pattern.test(state.input)) { 28 27 match = state.input.slice(state.index, pattern.lastIndex);