Mirror: The magical sticky regex-based parser generator 🧙
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add optimised path for trivial regex patterns

When a RegExp is a simple string match, then we're
able to replace it with such a string match instead.

+57 -27
+5 -7
src/babel/__snapshots__/plugin.test.js.snap
··· 32 32 `; 33 33 34 34 exports[`works with local recursion 1`] = ` 35 - "import { tag, _exec, _pattern } from 'reghex'; 36 - 37 - var _inner_expression = _pattern(/inner/); 35 + "import { tag, _exec, _substr, _pattern } from 'reghex'; 38 36 39 37 const inner = function _inner(state) { 40 38 var last_index = state.index; 41 39 var match, 42 40 node = []; 43 41 44 - if (match = _exec(state, _inner_expression)) { 42 + if (match = _substr(state, \\"inner\\")) { 45 43 node.push(match); 46 44 } else { 47 45 state.index = last_index; ··· 68 66 `; 69 67 70 68 exports[`works with non-capturing groups 1`] = ` 71 - "import { _exec, _pattern, tag as _tag } from 'reghex'; 69 + "import { _exec, _substr, _pattern, tag as _tag } from 'reghex'; 72 70 73 71 var _node_expression = _pattern(1), 74 72 _node_expression2 = _pattern(2), ··· 124 122 `; 125 123 126 124 exports[`works with standard features 1`] = ` 127 - "import { _exec, _pattern, tag as _tag } from \\"reghex\\"; 125 + "import { _exec, _substr, _pattern, tag as _tag } from \\"reghex\\"; 128 126 129 127 var _node_expression = _pattern(1), 130 128 _node_expression2 = _pattern(2), ··· 209 207 `; 210 208 211 209 exports[`works with transform functions 1`] = ` 212 - "import { _exec, _pattern, tag as _tag } from 'reghex'; 210 + "import { _exec, _substr, _pattern, tag as _tag } from 'reghex'; 213 211 214 212 var _inner_transform = x => x; 215 213
+5
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'); 5 6 this.patternId = t.identifier('_pattern'); 6 7 this.tagId = t.identifier('tag'); 7 8 } ··· 20 21 21 22 get exec() { 22 23 return this.t.identifier(this.execId.name); 24 + } 25 + 26 + get substr() { 27 + return this.t.identifier(this.substrId.name); 23 28 } 24 29 25 30 get pattern() {
+38 -20
src/babel/transform.js
··· 3 3 import { initGenerator, RootNode } from './generator'; 4 4 5 5 export function makeHelpers(t) { 6 + const regexPatternsRe = /^[()\[\]|.+?*]|[^\\][()\[\]|.+?*$^]|\\[wdsWDS]/; 6 7 const importSourceRe = /reghex$|^reghex\/macro/; 7 8 const importName = 'reghex'; 8 9 const ids = new SharedIds(t); ··· 33 34 t.importSpecifier( 34 35 (ids.execId = path.scope.generateUidIdentifier('exec')), 35 36 t.identifier('_exec') 37 + ), 38 + t.importSpecifier( 39 + (ids.substrId = path.scope.generateUidIdentifier('substr')), 40 + t.identifier('_substr') 36 41 ), 37 42 t.importSpecifier( 38 43 (ids.patternId = path.scope.generateUidIdentifier('pattern')), ··· 105 110 }, 106 111 107 112 /** Given a match, hoists its expressions in front of the match's statement */ 108 - _hoistExpressions(path) { 113 + _prepareExpressions(path) { 109 114 t.assertTaggedTemplateExpression(path.node); 110 115 111 116 const variableDeclarators = []; 112 117 const matchName = this.getMatchName(path); 113 118 114 119 const hoistedExpressions = path.node.quasi.expressions.map( 115 - (expression) => { 120 + (expression, i) => { 116 121 if ( 117 122 t.isIdentifier(expression) && 118 123 path.scope.hasBinding(expression.name) ··· 122 127 const matchPath = binding.path.get('init'); 123 128 if (this.isMatch(matchPath)) return expression; 124 129 } 130 + } else if ( 131 + t.isRegExpLiteral(expression) && 132 + !regexPatternsRe.test(expression.pattern) 133 + ) { 134 + // NOTE: This is an optimisation path, where the pattern regex is inlined 135 + // and has determined to be "simple" enough to be turned into a string 136 + return t.stringLiteral( 137 + expression.pattern.replace(/\\./g, (x) => x[1]) 138 + ); 125 139 } 126 140 127 141 const id = path.scope.generateUidIdentifier( 128 142 `${matchName}_expression` 129 143 ); 144 + 130 145 variableDeclarators.push( 131 146 t.variableDeclarator( 132 147 id, 133 148 t.callExpression(ids.pattern, [expression]) 134 149 ) 135 150 ); 151 + 136 152 return id; 137 153 } 138 154 ); ··· 143 159 .insertBefore(t.variableDeclaration('var', variableDeclarators)); 144 160 } 145 161 146 - return hoistedExpressions; 162 + 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 + const binding = path.scope.getBinding(id.name); 170 + if (binding && t.isVariableDeclarator(binding.path.node)) { 171 + const matchPath = binding.path.get('init'); 172 + if (this.isMatch(matchPath)) { 173 + return t.callExpression(id, [ids.state]); 174 + } 175 + } 176 + 177 + return t.callExpression(ids.exec, [ids.state, id]); 178 + }); 147 179 }, 148 180 149 - _hoistTransform(path) { 181 + _prepareTransform(path) { 150 182 const transformNode = path.node.tag.arguments[1]; 151 183 if (!transformNode) return null; 152 184 if (t.isIdentifier(transformNode)) return transformNode; ··· 174 206 const nameNode = path.node.tag.arguments[0]; 175 207 const quasis = path.node.quasi.quasis.map((x) => x.value.cooked); 176 208 177 - // Hoist expressions and wrap them in an execPattern call 178 - const expressions = this._hoistExpressions(path).map((id) => { 179 - // Directly call expression if it's sure to be another matcher 180 - const binding = path.scope.getBinding(id.name); 181 - if (binding && t.isVariableDeclarator(binding.path.node)) { 182 - const matchPath = binding.path.get('init'); 183 - if (this.isMatch(matchPath)) { 184 - return t.callExpression(id, [ids.state]); 185 - } 186 - } 187 - 188 - return t.callExpression(ids.exec, [ids.state, id]); 189 - }); 190 - 191 - // Hoist transform argument if necessary 192 - const transformNode = this._hoistTransform(path); 209 + const expressions = this._prepareExpressions(path); 210 + const transformNode = this._prepareTransform(path); 193 211 194 212 let ast; 195 213 try {
+9
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 + 12 21 export const _exec = (state, pattern) => { 13 22 if (typeof pattern === 'function') return pattern(); 14 23