Mirror: The magical sticky regex-based parser generator 🧙

Refactor to remove __private.exec in favour of just pattern functions

Changed files
+62 -78
src
+9 -6
src/babel/__snapshots__/plugin.test.js.snap
···
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`deduplicates hoisted expressions 1`] = `
-
"import { match, __private } from \\"reghex\\";
const re = /1/;
const str = '1';
-
var _re_expression = __private.pattern(re);
const a = function (state) {
var y1 = state.y,
···
var node = [];
var x;
-
if ((x = __private.exec(state, _re_expression)) != null) {
node.push(x);
} else {
state.y = y1;
···
return;
}
-
if ((x = __private.exec(state, str)) != null) {
node.push(x);
} else {
state.y = y1;
···
return node;
};
const b = function (state) {
var y1 = state.y,
x1 = state.x;
var node = [];
var x;
-
if ((x = __private.exec(state, _re_expression)) != null) {
node.push(x);
} else {
state.y = y1;
···
return;
}
-
if ((x = __private.exec(state, \\"2\\")) != null) {
node.push(x);
} else {
state.y = y1;
···
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`deduplicates hoisted expressions 1`] = `
+
"import { match, __pattern as _pattern } from \\"reghex\\";
const re = /1/;
const str = '1';
+
var _re_expression = _pattern(re),
+
_str_expression = _pattern(str);
const a = function (state) {
var y1 = state.y,
···
var node = [];
var x;
+
if ((x = _re_expression(state)) != null) {
node.push(x);
} else {
state.y = y1;
···
return;
}
+
if ((x = _str_expression(state)) != null) {
node.push(x);
} else {
state.y = y1;
···
return node;
};
+
var _b_expression = _pattern('2');
+
const b = function (state) {
var y1 = state.y,
x1 = state.x;
var node = [];
var x;
+
if ((x = _re_expression(state)) != null) {
node.push(x);
} else {
state.y = y1;
···
return;
}
+
if ((x = _b_expression(state)) != null) {
node.push(x);
} else {
state.y = y1;
+7 -11
src/babel/transform.js
···
-
import { astRoot, _private } from '../codegen';
import { parse } from '../parser';
export function makeHelpers({ types: t, template }) {
···
let _hasUpdatedImport = false;
let _matchId = t.identifier('match');
-
let _privateId = t.identifier(_private);
const _hoistedExpressions = new Map();
-
const privateMethod = (name) =>
-
t.memberExpression(t.identifier(_privateId.name), t.identifier(name));
-
return {
/** Adds the reghex import declaration to the Program scope */
updateImport(path) {
···
path.node.source = t.stringLiteral(importName);
}
-
path.node.specifiers.push(t.importSpecifier(_privateId, _privateId));
const tagImport = path.node.specifiers.find((node) => {
return t.isImportSpecifier(node) && node.imported.name === 'match';
···
t.isIdentifier(expression.body.body[0].argument)
) {
expression = expression.body.body[0].argument;
-
} else if (t.isStringLiteral(expression)) {
-
return expression;
}
const isBindingExpression =
···
if (t.isVariableDeclarator(binding.path.node)) {
const matchPath = binding.path.get('init');
if (this.isMatch(matchPath)) {
-
return expression;
-
} else if (t.isStringLiteral(matchPath)) {
return expression;
} else if (_hoistedExpressions.has(expression.name)) {
return t.identifier(_hoistedExpressions.get(expression.name));
···
variableDeclarators.push(
t.variableDeclarator(
id,
-
t.callExpression(privateMethod('pattern'), [expression])
)
);
···
+
import { astRoot } from '../codegen';
import { parse } from '../parser';
export function makeHelpers({ types: t, template }) {
···
let _hasUpdatedImport = false;
let _matchId = t.identifier('match');
+
let _patternId = t.identifier('__pattern');
const _hoistedExpressions = new Map();
return {
/** Adds the reghex import declaration to the Program scope */
updateImport(path) {
···
path.node.source = t.stringLiteral(importName);
}
+
_patternId = path.scope.generateUidIdentifier('_pattern');
+
path.node.specifiers.push(
+
t.importSpecifier(_patternId, t.identifier('__pattern'))
+
);
const tagImport = path.node.specifiers.find((node) => {
return t.isImportSpecifier(node) && node.imported.name === 'match';
···
t.isIdentifier(expression.body.body[0].argument)
) {
expression = expression.body.body[0].argument;
}
const isBindingExpression =
···
if (t.isVariableDeclarator(binding.path.node)) {
const matchPath = binding.path.get('init');
if (this.isMatch(matchPath)) {
return expression;
} else if (_hoistedExpressions.has(expression.name)) {
return t.identifier(_hoistedExpressions.get(expression.name));
···
variableDeclarators.push(
t.variableDeclarator(
id,
+
t.callExpression(t.identifier(_patternId.name), [expression])
)
);
+2 -7
src/codegen.js
···
-
export const _private = '__private';
-
const _state = 'state';
const _node = 'node';
const _match = 'x';
···
const restoreLength =
(opts.length && opts.abort && js`${_node}.length = ln${opts.length};`) ||
'';
-
const expression = ast.expression.fn
-
? `${ast.expression.id}(${_state})`
-
: `${_private}.exec(${_state}, ${ast.expression.id})`;
-
return js`
-
if ((${_match} = ${expression}) != null) {
${opts.capture ? js`${_node}.push(${_match})` : ''}
} else {
${opts.onAbort}
···
const _state = 'state';
const _node = 'node';
const _match = 'x';
···
const restoreLength =
(opts.length && opts.abort && js`${_node}.length = ln${opts.length};`) ||
'';
+
const expression = `${ast.expression.id}(${_state})`;
return js`
+
if ((${_match} = ${ast.expression.id}(${_state})) != null) {
${opts.capture ? js`${_node}.push(${_match})` : ''}
} else {
${opts.onAbort}
+44 -54
src/core.js
···
-
import { astRoot, _private as privateId } from './codegen';
import { parse as parseDSL } from './parser';
const isStickySupported = typeof /./g.sticky === 'boolean';
-
export const __private = {
-
pattern(input) {
-
if (typeof input === 'function' || typeof input === 'string') {
-
return input;
-
}
-
const source = typeof input !== 'string' ? input.source : input;
-
return isStickySupported
-
? new RegExp(source, 'y')
-
: new RegExp(source + '|()', 'g');
-
},
-
-
exec(state, pattern) {
-
let match;
-
-
if (typeof pattern === 'function') {
-
if (!pattern.length) pattern = pattern();
-
return pattern(state);
}
const input = state.quasis[state.x];
if (input && state.y < input.length) {
-
if (typeof pattern === 'string') {
-
const end = state.y + pattern.length;
-
const sub = input.slice(state.y, end);
-
if (sub === pattern) {
-
state.y = end;
-
match = sub;
-
}
-
} else {
-
pattern.lastIndex = state.y;
-
if (isStickySupported) {
-
if (pattern.test(input))
-
match = input.slice(state.y, pattern.lastIndex);
-
} else {
-
const x = pattern.exec(input);
-
match = x[1] == null ? x[0] : match;
-
}
-
state.y = pattern.lastIndex;
}
}
-
return match;
-
},
};
export const interpolation = (predicate) => (state) => {
···
export const match = (name, transform) => (quasis, ...expressions) => {
const ast = parseDSL(
quasis,
-
expressions.map((expression, i) => ({
-
fn: typeof expression === 'function' && expression.length,
-
id: `_${i}`,
-
}))
);
-
-
const makeMatcher = new Function(
-
privateId +
-
',_n,_t,' +
-
expressions.map((_expression, i) => `_${i}`).join(','),
'return ' + astRoot(ast, '_n', transform ? '_t' : null)
-
);
-
-
return makeMatcher(
-
__private,
-
name,
-
transform,
-
...expressions.map(__private.pattern)
-
);
};
···
+
import { astRoot } from './codegen';
import { parse as parseDSL } from './parser';
const isStickySupported = typeof /./g.sticky === 'boolean';
+
const execLambda = (pattern) => {
+
if (pattern.length) return pattern;
+
return (state) => pattern()(state);
+
};
+
const execString = (pattern) => (state) => {
+
const input = state.quasis[state.x];
+
if (input && state.y < input.length) {
+
const sub = input.slice(state.y, state.y + pattern.length);
+
if (sub === pattern) {
+
state.y += pattern.length;
+
return sub;
}
+
}
+
};
+
const execRegex = (pattern) => {
+
pattern = isStickySupported
+
? new RegExp(pattern.source, 'y')
+
: new RegExp(pattern.source + '|()', 'g');
+
return (state) => {
const input = state.quasis[state.x];
if (input && state.y < input.length) {
+
pattern.lastIndex = state.y;
+
let match;
+
if (isStickySupported) {
+
if (pattern.test(input))
+
match = input.slice(state.y, pattern.lastIndex);
+
} else {
+
const x = pattern.exec(input);
+
if (x[1] == null) match = x[0];
}
+
+
state.y = pattern.lastIndex;
+
return match;
}
+
};
+
};
+
export const __pattern = (input) => {
+
if (typeof input === 'function') {
+
return execLambda(input);
+
} else if (typeof input === 'string') {
+
return execString(input);
+
} else {
+
return execRegex(input);
+
}
};
export const interpolation = (predicate) => (state) => {
···
export const match = (name, transform) => (quasis, ...expressions) => {
const ast = parseDSL(
quasis,
+
expressions.map((_, i) => ({ id: `_${i}` }))
);
+
return new Function(
+
'_n,_t,' + expressions.map((_expression, i) => `_${i}`).join(','),
'return ' + astRoot(ast, '_n', transform ? '_t' : null)
+
)(name, transform, ...expressions.map(__pattern));
};