Mirror: The magical sticky regex-based parser generator 🧙

Replace Babel code generator with string-based code generator

+115 -103
src/babel/__snapshots__/plugin.test.js.snap
···
_node_expression2 = (0, _reghex._pattern)(2);
const node = function _node(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
var index_1 = state.index;
+
var node = [];
+
var match;
if (match = (0, _reghex._exec)(state, _node_expression)) {
node.push(match);
} else {
-
state.index = last_index;
+
state.index = index_1;
return;
}
if (match = (0, _reghex._exec)(state, _node_expression2)) {
node.push(match);
} else {
-
state.index = last_index;
+
state.index = index_1;
return;
}
-
return (0, _reghex.tag)(node, 'node');
+
node.tag = 'node';
+
return node;
};"
`;
exports[`works with local recursion 1`] = `
-
"import { tag, _exec, _substr, _pattern } from 'reghex';
+
"import { tag, _exec, _pattern } from 'reghex';
const inner = function _inner(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
var index_1 = state.index;
+
var node = [];
+
var match;
-
if (match = _substr(state, \\"inner\\")) {
+
if (match = _exec(state, \\"inner\\")) {
node.push(match);
} else {
-
state.index = last_index;
+
state.index = index_1;
return;
}
-
return tag(node, 'inner');
+
node.tag = 'inner';
+
return node;
};
const node = function _node(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
var index_1 = state.index;
+
var node = [];
+
var match;
if (match = inner(state)) {
node.push(match);
} else {
-
state.index = last_index;
+
state.index = index_1;
return;
}
-
return tag(node, 'node');
+
node.tag = 'node';
+
return node;
};"
`;
exports[`works with non-capturing groups 1`] = `
-
"import { _exec, _substr, _pattern, tag as _tag } from 'reghex';
+
"import { _exec, _pattern, tag as _tag } from 'reghex';
var _node_expression = _pattern(1),
_node_expression2 = _pattern(2),
_node_expression3 = _pattern(3);
const node = function _node(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
var index_1 = state.index;
+
var node = [];
+
var match;
if (match = _exec(state, _node_expression)) {
node.push(match);
} else {
-
state.index = last_index;
+
state.index = index_1;
return;
}
-
var length_0 = node.length;
+
var length_2 = node.length;
-
alternation_1: {
-
block_1: {
-
var index_1 = state.index;
+
alternation_3: {
+
block_3: {
+
var index_3 = state.index;
if (match = _exec(state, _node_expression2)) {
node.push(match);
} else {
-
node.length = length_0;
-
state.index = index_1;
-
break block_1;
+
state.index = index_3;
+
node.length = length_2;
+
break block_3;
}
-
break alternation_1;
+
break alternation_3;
}
-
loop_1: for (var iter_1 = 0; true; iter_1++) {
-
var index_1 = state.index;
+
loop_3: for (var count_3 = 0; true; count_3++) {
+
var index_3 = state.index;
if (!_exec(state, _node_expression3)) {
-
if (iter_1) {
-
state.index = index_1;
-
break loop_1;
-
}
+
if (count_3) {
+
state.index = index_3;
+
break loop_3;
+
} else {}
-
node.length = length_0;
-
state.index = last_index;
+
state.index = index_1;
+
node.length = length_2;
return;
}
}
}
-
return _tag(node, 'node');
+
node.tag = 'node';
+
return node;
};"
`;
exports[`works with standard features 1`] = `
-
"import { _exec, _substr, _pattern, tag as _tag } from \\"reghex\\";
+
"import { _exec, _pattern, tag as _tag } from \\"reghex\\";
var _node_expression = _pattern(1),
_node_expression2 = _pattern(2),
···
_node_expression5 = _pattern(5);
const node = function _node(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
var index_1 = state.index;
+
var node = [];
+
var match;
-
block_0: {
-
var index_0 = state.index;
+
alternation_2: {
+
block_2: {
+
var index_2 = state.index;
-
loop_0: for (var iter_0 = 0; true; iter_0++) {
-
var index_0 = state.index;
+
loop_2: for (var count_2 = 0; true; count_2++) {
+
var index_2 = state.index;
-
if (match = _exec(state, _node_expression)) {
-
node.push(match);
-
} else {
-
if (iter_0) {
-
state.index = index_0;
-
break loop_0;
-
}
+
if (match = _exec(state, _node_expression)) {
+
node.push(match);
+
} else {
+
if (count_2) {
+
state.index = index_2;
+
break loop_2;
+
} else {}
-
state.index = index_0;
-
break block_0;
+
state.index = index_2;
+
break block_2;
+
}
}
+
+
break alternation_2;
}
-
return _tag(node, 'node');
-
}
+
loop_2: for (var count_2 = 0; true; count_2++) {
+
var index_2 = state.index;
-
loop_0: for (var iter_0 = 0; true; iter_0++) {
-
var index_0 = state.index;
+
if (match = _exec(state, _node_expression2)) {
+
node.push(match);
+
} else {
+
if (count_2) {
+
state.index = index_2;
+
break loop_2;
+
} else {}
-
if (match = _exec(state, _node_expression2)) {
-
node.push(match);
-
} else {
-
if (iter_0) {
-
state.index = index_0;
-
break loop_0;
+
state.index = index_1;
+
return;
}
-
-
state.index = last_index;
-
return;
}
-
}
-
loop_0: while (true) {
-
var index_0 = state.index;
-
var length_0 = node.length;
+
loop_2: while (true) {
+
var index_2 = state.index;
+
var length_2 = node.length;
-
if (match = _exec(state, _node_expression3)) {
-
node.push(match);
-
} else {
-
node.length = length_0;
-
state.index = index_0;
-
break loop_0;
-
}
+
if (match = _exec(state, _node_expression3)) {
+
node.push(match);
+
} else {
+
state.index = index_2;
+
node.length = length_2;
+
break loop_2;
+
}
-
var index_2 = state.index;
+
var index_4 = state.index;
-
if (match = _exec(state, _node_expression4)) {
-
node.push(match);
-
} else {
-
state.index = index_2;
-
}
+
if (match = _exec(state, _node_expression4)) {
+
node.push(match);
+
} else {
+
state.index = index_4;
+
}
-
if (match = _exec(state, _node_expression5)) {
-
node.push(match);
-
} else {
-
node.length = length_0;
-
state.index = index_0;
-
break loop_0;
+
if (match = _exec(state, _node_expression5)) {
+
node.push(match);
+
} else {
+
state.index = index_2;
+
node.length = length_2;
+
break loop_2;
+
}
}
}
-
return _tag(node, 'node');
+
node.tag = 'node';
+
return node;
};"
`;
exports[`works with transform functions 1`] = `
-
"import { _exec, _substr, _pattern, tag as _tag } from 'reghex';
+
"import { _exec, _pattern, tag as _tag } from 'reghex';
var _inner_transform = x => x;
const first = function _inner(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
-
return _inner_transform(_tag(node, 'inner'));
+
var index_1 = state.index;
+
var node = [];
+
var match;
+
node.tag = 'inner';
+
return;
+
+
_inner_transform(node);
};
const transform = x => x;
const second = function _node(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
-
return transform(_tag(node, 'node'));
+
var index_1 = state.index;
+
var node = [];
+
var match;
+
node.tag = 'node';
+
return;
+
transform(node);
};"
`;
+11 -9
src/babel/__tests__/suite.js
···
-
import * as reghex from '../../..';
+
import * as reghex from '../../../src/core';
import * as types from '@babel/types';
+
import template from '@babel/template';
import { transform } from '@babel/core';
import { makeHelpers } from '../transform';
const match = (name) => (quasis, ...expressions) => {
-
const helpers = makeHelpers(types);
+
const helpers = makeHelpers({ types, template });
let str = '';
for (let i = 0; i < quasis.length; i++) {
···
if (i < expressions.length) str += '${' + expressions[i].toString() + '}';
}
-
const template = `(function () { return match('${name}')\`${str}\`; })()`;
-
const testPlugin = () => ({
visitor: {
TaggedTemplateExpression(path) {
···
},
});
-
const { code } = transform(template, {
-
babelrc: false,
-
presets: [],
-
plugins: [testPlugin],
-
});
+
const { code } = transform(
+
`(function () { return match('${name}')\`${str}\`; })()`,
+
{
+
babelrc: false,
+
presets: [],
+
plugins: [testPlugin],
+
}
+
);
const argKeys = Object.keys(reghex).filter((x) => {
return x.startsWith('_') || x === 'tag';
-411
src/babel/generator.js
···
-
let t;
-
let ids = {};
-
-
export function initGenerator(_ids, _t) {
-
ids = _ids;
-
t = _t;
-
}
-
-
/** var id = state.index; */
-
class AssignIndexNode {
-
constructor(id) {
-
this.id = id;
-
}
-
-
statement() {
-
const member = t.memberExpression(ids.state, t.identifier('index'));
-
return t.variableDeclaration('var', [
-
t.variableDeclarator(this.id, member),
-
]);
-
}
-
}
-
-
/** state.index = id; */
-
class RestoreIndexNode {
-
constructor(id) {
-
this.id = id;
-
}
-
-
statement() {
-
const expression = t.assignmentExpression(
-
'=',
-
t.memberExpression(ids.state, t.identifier('index')),
-
this.id
-
);
-
-
return t.expressionStatement(expression);
-
}
-
}
-
-
/** var id = node.length; */
-
class AssignLengthNode {
-
constructor(id) {
-
this.id = id;
-
}
-
-
statement() {
-
return t.variableDeclaration('var', [
-
t.variableDeclarator(
-
this.id,
-
t.memberExpression(ids.node, t.identifier('length'))
-
),
-
]);
-
}
-
}
-
-
/** node.length = id; */
-
class RestoreLengthNode {
-
constructor(id) {
-
this.id = id;
-
}
-
-
statement() {
-
const expression = t.assignmentExpression(
-
'=',
-
t.memberExpression(ids.node, t.identifier('length')),
-
this.id
-
);
-
-
return t.expressionStatement(expression);
-
}
-
}
-
-
/** return; break id; */
-
class AbortNode {
-
constructor(id) {
-
this.id = id || null;
-
}
-
-
statement() {
-
const statement = this.id ? t.breakStatement(this.id) : t.returnStatement();
-
return statement;
-
}
-
}
-
-
/** if (condition) { return; break id; } */
-
class AbortConditionNode {
-
constructor(condition, opts) {
-
this.condition = condition || null;
-
-
this.abort = opts.abort;
-
this.abortCondition = opts.abortCondition || null;
-
this.restoreIndex = opts.restoreIndex;
-
}
-
-
statement() {
-
return t.ifStatement(
-
this.condition,
-
t.blockStatement(
-
[this.restoreIndex.statement(), this.abort.statement()].filter(Boolean)
-
),
-
this.abortCondition ? this.abortCondition.statement() : null
-
);
-
}
-
}
-
-
/** Generates a full matcher for an expression */
-
class ExpressionNode {
-
constructor(ast, depth, opts) {
-
this.ast = ast;
-
this.depth = depth || 0;
-
this.capturing = !!opts.capturing;
-
this.restoreIndex = opts.restoreIndex;
-
this.restoreLength = opts.restoreLength || null;
-
this.abortCondition = opts.abortCondition || null;
-
this.abort = opts.abort || null;
-
}
-
-
statements() {
-
const execMatch = this.ast.expression;
-
const assignMatch = t.assignmentExpression('=', ids.match, execMatch);
-
-
const successNodes = t.blockStatement([
-
t.expressionStatement(
-
t.callExpression(t.memberExpression(ids.node, t.identifier('push')), [
-
ids.match,
-
])
-
),
-
]);
-
-
const abortNodes = t.blockStatement(
-
[
-
this.abortCondition && this.abortCondition.statement(),
-
this.abort && this.restoreLength && this.restoreLength.statement(),
-
this.restoreIndex && this.restoreIndex.statement(),
-
this.abort && this.abort.statement(),
-
].filter(Boolean)
-
);
-
-
return [
-
!this.capturing
-
? t.ifStatement(t.unaryExpression('!', execMatch), abortNodes)
-
: t.ifStatement(assignMatch, successNodes, abortNodes),
-
];
-
}
-
}
-
-
/** Generates a full matcher for a group */
-
class GroupNode {
-
constructor(ast, depth, opts) {
-
this.ast = ast;
-
this.depth = depth || 0;
-
if (ast.sequence.length === 1) {
-
return new ExpressionNode(ast.sequence[0], depth, opts);
-
}
-
-
const lengthId = t.identifier(`length_${depth}`);
-
const childOpts = {
-
...opts,
-
capturing: !!opts.capturing && !!ast.capturing,
-
};
-
-
this.assignLength = null;
-
if (!childOpts.restoreLength && childOpts.capturing) {
-
this.assignLength = new AssignLengthNode(lengthId);
-
childOpts.restoreLength = new RestoreLengthNode(lengthId);
-
}
-
-
this.alternation = new AlternationNode(ast.sequence, depth + 1, childOpts);
-
}
-
-
statements() {
-
return [
-
this.assignLength && this.assignLength.statement(),
-
...this.alternation.statements(),
-
].filter(Boolean);
-
}
-
}
-
-
/** Generates looping logic around another group or expression matcher */
-
class QuantifierNode {
-
constructor(ast, depth, opts) {
-
const { quantifier } = ast;
-
this.ast = ast;
-
this.depth = depth || 0;
-
-
const invertId = t.identifier(`invert_${this.depth}`);
-
const loopId = t.identifier(`loop_${this.depth}`);
-
const iterId = t.identifier(`iter_${this.depth}`);
-
const indexId = t.identifier(`index_${this.depth}`);
-
const ChildNode = ast.type === 'group' ? GroupNode : ExpressionNode;
-
const childOpts = { ...opts };
-
-
this.assignIndex = null;
-
this.restoreIndex = null;
-
this.blockId = null;
-
this.abort = null;
-
-
if (ast.type === 'group' && !!ast.lookahead) {
-
this.restoreIndex = new RestoreIndexNode(indexId);
-
this.assignIndex = new AssignIndexNode(indexId);
-
}
-
-
if (ast.type === 'group' && ast.lookahead === 'negative') {
-
childOpts.abort = new AbortNode(invertId);
-
childOpts.restoreIndex = this.restoreIndex;
-
this.restoreIndex = opts.restoreIndex;
-
this.blockId = invertId;
-
this.abort = opts.abort;
-
}
-
-
if (quantifier && !quantifier.singular && quantifier.required) {
-
childOpts.abortCondition = new AbortConditionNode(iterId, {
-
...opts,
-
restoreIndex: new RestoreIndexNode(indexId),
-
abort: new AbortNode(loopId),
-
});
-
} else if (quantifier && !quantifier.singular) {
-
childOpts.restoreLength = null;
-
childOpts.restoreIndex = new RestoreIndexNode(indexId);
-
childOpts.abort = new AbortNode(loopId);
-
childOpts.abortCondition = null;
-
} else if (quantifier && !quantifier.required) {
-
childOpts.restoreIndex = new RestoreIndexNode(indexId);
-
childOpts.abortCondition = null;
-
childOpts.abort = null;
-
}
-
-
this.childNode = new ChildNode(ast, depth, childOpts);
-
}
-
-
statements() {
-
const { quantifier } = this.ast;
-
const loopId = t.identifier(`loop_${this.depth}`);
-
const iterId = t.identifier(`iter_${this.depth}`);
-
const indexId = t.identifier(`index_${this.depth}`);
-
const assignIndex = new AssignIndexNode(indexId);
-
-
let statements;
-
if (quantifier && !quantifier.singular && quantifier.required) {
-
statements = [
-
t.labeledStatement(
-
loopId,
-
t.forStatement(
-
t.variableDeclaration('var', [
-
t.variableDeclarator(iterId, t.numericLiteral(0)),
-
]),
-
t.booleanLiteral(true),
-
t.updateExpression('++', iterId),
-
t.blockStatement([
-
assignIndex.statement(),
-
...this.childNode.statements(),
-
])
-
)
-
),
-
];
-
} else if (quantifier && !quantifier.singular) {
-
statements = [
-
t.labeledStatement(
-
loopId,
-
t.whileStatement(
-
t.booleanLiteral(true),
-
t.blockStatement([
-
assignIndex.statement(),
-
...this.childNode.statements(),
-
])
-
)
-
),
-
];
-
} else if (quantifier && !quantifier.required) {
-
statements = [assignIndex.statement(), ...this.childNode.statements()];
-
} else {
-
statements = this.childNode.statements();
-
}
-
-
if (this.blockId && this.assignIndex && this.restoreIndex) {
-
statements = [
-
t.labeledStatement(
-
this.blockId,
-
t.blockStatement(
-
[
-
this.assignIndex.statement(),
-
...statements,
-
this.restoreIndex.statement(),
-
this.abort.statement(),
-
].filter(Boolean)
-
)
-
),
-
].filter(Boolean);
-
} else if (this.assignIndex && this.restoreIndex) {
-
statements.unshift(this.assignIndex.statement());
-
statements.push(this.restoreIndex.statement());
-
}
-
-
return statements;
-
}
-
}
-
-
/** Generates a matcher of a sequence of sub-matchers for a single sequence */
-
class SequenceNode {
-
constructor(ast, depth, opts) {
-
this.ast = ast;
-
this.depth = depth || 0;
-
-
const indexId = t.identifier(`index_${depth}`);
-
const blockId = t.identifier(`block_${this.depth}`);
-
-
this.returnStatement = opts.returnStatement;
-
this.assignIndex = ast.alternation ? new AssignIndexNode(indexId) : null;
-
-
this.quantifiers = ast.sequence.map((childAst) => {
-
return new QuantifierNode(childAst, depth, {
-
...opts,
-
restoreIndex: ast.alternation
-
? new RestoreIndexNode(indexId)
-
: opts.restoreIndex,
-
abortCondition: ast.alternation ? null : opts.abortCondition,
-
abort: ast.alternation ? new AbortNode(blockId) : opts.abort,
-
});
-
});
-
}
-
-
statements() {
-
const blockId = t.identifier(`block_${this.depth}`);
-
const alternationId = t.identifier(`alternation_${this.depth}`);
-
const statements = this.quantifiers.reduce((block, node) => {
-
block.push(...node.statements());
-
return block;
-
}, []);
-
-
if (!this.ast.alternation) {
-
return statements;
-
}
-
-
const abortNode =
-
this.depth === 0 ? this.returnStatement : t.breakStatement(alternationId);
-
-
return [
-
t.labeledStatement(
-
blockId,
-
t.blockStatement([
-
this.assignIndex && this.assignIndex.statement(),
-
...statements,
-
abortNode,
-
])
-
),
-
];
-
}
-
}
-
-
/** Generates matchers for sequences with (or without) alternations */
-
class AlternationNode {
-
constructor(ast, depth, opts) {
-
this.ast = ast;
-
this.depth = depth || 0;
-
this.sequences = [];
-
for (let current = ast; current; current = current.alternation) {
-
this.sequences.push(new SequenceNode(current, depth, opts));
-
}
-
}
-
-
statements() {
-
if (this.sequences.length === 1) {
-
return this.sequences[0].statements();
-
}
-
-
const statements = [];
-
for (let i = 0; i < this.sequences.length; i++) {
-
statements.push(...this.sequences[i].statements());
-
}
-
-
if (this.depth === 0) {
-
return statements;
-
}
-
-
const alternationId = t.identifier(`alternation_${this.depth}`);
-
return [t.labeledStatement(alternationId, t.blockStatement(statements))];
-
}
-
}
-
-
export class RootNode {
-
constructor(ast, nameNode, transformNode) {
-
const indexId = t.identifier('last_index');
-
const node = t.callExpression(ids.tag, [ids.node, nameNode]);
-
-
this.returnStatement = t.returnStatement(
-
transformNode ? t.callExpression(transformNode, [node]) : node
-
);
-
-
this.assignIndex = new AssignIndexNode(indexId);
-
this.node = new AlternationNode(ast, 0, {
-
returnStatement: this.returnStatement,
-
restoreIndex: new RestoreIndexNode(indexId, true),
-
restoreLength: null,
-
abortCondition: null,
-
abort: new AbortNode(),
-
capturing: true,
-
});
-
}
-
-
statements() {
-
return [
-
this.assignIndex.statement(),
-
t.variableDeclaration('var', [
-
t.variableDeclarator(ids.match),
-
t.variableDeclarator(ids.node, t.arrayExpression()),
-
]),
-
...this.node.statements(),
-
this.returnStatement,
-
];
-
}
-
}
+2 -2
src/babel/macro.js
···
import { createMacro } from 'babel-plugin-macros';
import { makeHelpers } from './transform';
-
function reghexMacro({ references, babel: { types: t } }) {
-
const helpers = makeHelpers(t);
+
function reghexMacro({ references, babel }) {
+
const helpers = makeHelpers(babel);
const defaultRefs = references.default || [];
defaultRefs.forEach((ref) => {
+2 -2
src/babel/plugin.js
···
import { makeHelpers } from './transform';
-
export default function reghexPlugin({ types }) {
+
export default function reghexPlugin(babel) {
let helpers;
return {
name: 'reghex',
visitor: {
Program() {
-
helpers = makeHelpers(types);
+
helpers = makeHelpers(babel);
},
ImportDeclaration(path) {
helpers.updateImport(path);
-17
src/babel/sharedIds.js
···
constructor(t) {
this.t = t;
this.execId = t.identifier('_exec');
-
this.substrId = t.identifier('_substr');
this.patternId = t.identifier('_pattern');
this.tagId = t.identifier('tag');
}
-
get node() {
-
return this.t.identifier('node');
-
}
-
-
get match() {
-
return this.t.identifier('match');
-
}
-
-
get state() {
-
return this.t.identifier('state');
-
}
-
get exec() {
return this.t.identifier(this.execId.name);
-
}
-
-
get substr() {
-
return this.t.identifier(this.substrId.name);
}
get pattern() {
+18 -29
src/babel/transform.js
···
import { parse } from '../parser';
+
import { astRoot } from '../codegen';
import { SharedIds } from './sharedIds';
-
import { initGenerator, RootNode } from './generator';
-
export function makeHelpers(t) {
+
export function makeHelpers({ types: t, template }) {
const regexPatternsRe = /^[()\[\]|.+?*]|[^\\][()\[\]|.+?*$^]|\\[wdsWDS]/;
const importSourceRe = /reghex$|^reghex\/macro/;
const importName = 'reghex';
const ids = new SharedIds(t);
-
initGenerator(ids, t);
let _hasUpdatedImport = false;
···
t.importSpecifier(
(ids.execId = path.scope.generateUidIdentifier('exec')),
t.identifier('_exec')
-
),
-
t.importSpecifier(
-
(ids.substrId = path.scope.generateUidIdentifier('substr')),
-
t.identifier('_substr')
),
t.importSpecifier(
(ids.patternId = path.scope.generateUidIdentifier('pattern')),
···
}
return hoistedExpressions.map((id) => {
-
// Use _substr helper instead if the expression is a string
-
if (t.isStringLiteral(id)) {
-
return t.callExpression(ids.substr, [ids.state, id]);
-
}
-
-
// Directly call expression if it's sure to be another matcher
const binding = path.scope.getBinding(id.name);
if (binding && t.isVariableDeclarator(binding.path.node)) {
const matchPath = binding.path.get('init');
-
if (this.isMatch(matchPath)) {
-
return t.callExpression(id, [ids.state]);
-
}
+
if (this.isMatch(matchPath)) return `${id.name}(state)`;
}
-
return t.callExpression(ids.exec, [ids.state, id]);
+
const input = t.isStringLiteral(id)
+
? JSON.stringify(id.value)
+
: id.name;
+
return `${ids.exec.name}(state, ${input})`;
});
},
_prepareTransform(path) {
const transformNode = path.node.tag.arguments[1];
+
if (!transformNode) return null;
-
if (t.isIdentifier(transformNode)) return transformNode;
+
if (t.isIdentifier(transformNode)) return transformNode.name;
const matchName = this.getMatchName(path);
const id = path.scope.generateUidIdentifier(`${matchName}_transform`);
···
path
.getStatementParent()
.insertBefore(t.variableDeclaration('var', [declarator]));
-
return id;
+
+
return id.name;
},
transformMatch(path) {
···
}
const matchName = this.getMatchName(path);
-
const nameNode = path.node.tag.arguments[0];
+
const name = path.node.tag.arguments[0];
const quasis = path.node.quasi.quasis.map((x) => x.value.cooked);
const expressions = this._prepareExpressions(path);
-
const transformNode = this._prepareTransform(path);
+
const transform = this._prepareTransform(path);
let ast;
try {
···
throw path.get('quasi').buildCodeFrameError(error.message);
}
-
const generator = new RootNode(ast, nameNode, transformNode);
-
const body = t.blockStatement(generator.statements());
-
const matchFunctionId = path.scope.generateUidIdentifier(matchName);
-
const matchFunction = t.functionExpression(
-
matchFunctionId,
-
[ids.state],
-
body
+
const id = path.scope.generateUidIdentifier(matchName).name;
+
const code = astRoot(ast, id, '%%name%%', transform && '%%transform%%');
+
+
path.replaceWith(
+
template.expression(code)(transform ? { name, transform } : { name })
);
-
path.replaceWith(matchFunction);
},
};
}
+238
src/codegen.js
···
+
const _state = 'state';
+
const _match = 'match';
+
const _node = 'node';
+
+
function js(/* arguments */) {
+
let body = arguments[0][0];
+
for (let i = 1; i < arguments.length; i++)
+
body = body + arguments[i] + (arguments[0][i] || '');
+
return '\n' + body.trim();
+
}
+
+
const assignIndex = (depth) =>
+
depth ? js`var index_${depth} = ${_state}.index;` : '';
+
+
const restoreIndex = (depth) =>
+
depth ? js`${_state}.index = index_${depth};` : '';
+
+
const abortOnCondition = (condition, hooks) => js`
+
if (${condition}) {
+
${restoreIndex(opts.index)}
+
${opts.abort}
+
} else {
+
${opts.onAbort}
+
}
+
`;
+
+
const astExpression = (ast, depth, opts) => {
+
const abort = js`
+
${opts.onAbort}
+
${restoreIndex(opts.index)}
+
${
+
opts.length && opts.abort
+
? js`
+
${_node}.length = length_${opts.length};
+
`
+
: ''
+
}
+
${opts.abort || ''}
+
`;
+
+
if (!opts.capturing) {
+
return js`
+
if (!(${ast.expression})) {
+
${abort}
+
}
+
`;
+
}
+
+
return js`
+
if (${_match} = ${ast.expression}) {
+
${_node}.push(${_match});
+
} else {
+
${abort}
+
}
+
`;
+
};
+
+
const astGroup = (ast, depth, opts) => {
+
if (ast.sequence.length === 1)
+
return astExpression(ast.sequence[0], depth, opts);
+
+
const capturing = !!opts.capturing && !!ast.capturing;
+
+
let group = '';
+
if (!opts.length && capturing) {
+
return js`
+
${js`var length_${depth} = ${_node}.length;`}
+
${astAlternation(ast.sequence, depth + 1, {
+
...opts,
+
length: depth,
+
capturing,
+
})}
+
`;
+
}
+
+
return astAlternation(ast.sequence, depth + 1, {
+
...opts,
+
capturing,
+
});
+
};
+
+
const astChild = (ast, depth, opts) =>
+
ast.type === 'expression'
+
? astExpression(ast, depth, opts)
+
: astGroup(ast, depth, opts);
+
+
const astRepeating = (ast, depth, opts) => {
+
const label = `loop_${depth}`;
+
const count = `count_${depth}`;
+
return js`
+
${label}: for (var ${count} = 0; true; ${count}++) {
+
${assignIndex(depth)}
+
${astChild(ast, depth, {
+
...opts,
+
onAbort: js`
+
if (${count}) {
+
${restoreIndex(depth)}
+
break ${label};
+
} else {
+
${opts.onAbort}
+
}
+
`,
+
})}
+
}
+
`;
+
};
+
+
const astMultiple = (ast, depth, opts) => {
+
const label = `loop_${depth}`;
+
return js`
+
${label}: while (true) {
+
${assignIndex(depth)}
+
${astChild(ast, depth, {
+
...opts,
+
length: 0,
+
index: depth,
+
abort: js`break ${label};`,
+
onAbort: '',
+
})}
+
}
+
`;
+
};
+
+
const astOptional = (ast, depth, opts) => js`
+
${assignIndex(depth)}
+
${astChild(ast, depth, {
+
...opts,
+
index: depth,
+
abort: '',
+
onAbort: '',
+
})}
+
`;
+
+
const astQuantifier = (ast, depth, opts) => {
+
const { index, abort } = opts;
+
const label = `invert_${depth}`;
+
+
if (ast.lookahead === 'negative') {
+
opts = {
+
...opts,
+
index: depth,
+
abort: js`break ${label};`,
+
};
+
}
+
+
let child;
+
if (ast.quantifier && !ast.quantifier.singular && ast.quantifier.required) {
+
child = astRepeating(ast, depth, opts);
+
} else if (ast.quantifier && !ast.quantifier.singular)
+
child = astMultiple(ast, depth, opts);
+
else if (ast.quantifier && !ast.quantifier.required)
+
child = astOptional(ast, depth, opts);
+
else child = astChild(ast, depth, opts);
+
+
if (ast.lookahead === 'negative') {
+
return js`
+
${label}: {
+
${assignIndex(depth)}
+
${child}
+
${restoreIndex(index)}
+
${abort}
+
}
+
`;
+
} else if (ast.lookahead) {
+
return js`
+
${assignIndex(depth)}
+
${child}
+
${restoreIndex(depth)}
+
`;
+
} else {
+
return child;
+
}
+
};
+
+
const astSequence = (ast, depth, opts) => {
+
const block = `block_${depth}`;
+
const alternation = `alternation_${depth}`;
+
+
if (ast.alternation) {
+
opts = {
+
...opts,
+
index: depth,
+
abort: js`break ${block};`,
+
onAbort: '',
+
};
+
}
+
+
let sequence = '';
+
for (let i = 0; i < ast.sequence.length; i++)
+
sequence += astQuantifier(ast.sequence[i], depth, opts);
+
+
if (!ast.alternation) {
+
return sequence;
+
}
+
+
return js`
+
${block}: {
+
${assignIndex(depth)}
+
${sequence}
+
break ${alternation};
+
}
+
`;
+
};
+
+
const astAlternation = (ast, depth, opts) => {
+
if (!ast.alternation) return astSequence(ast, depth, opts);
+
+
let sequence = '';
+
for (let child = ast; child; child = child.alternation)
+
sequence += astSequence(child, depth, opts);
+
+
return js`
+
alternation_${depth}: {
+
${sequence}
+
}
+
`;
+
};
+
+
const astRoot = (ast, id, name, transform) => js`
+
function ${id}(${_state}) {
+
${assignIndex(1)}
+
var ${_node} = [];
+
var ${_match};
+
+
${astAlternation(ast, 2, {
+
index: 1,
+
length: 0,
+
onAbort: '',
+
abort: js`return;`,
+
capturing: true,
+
})}
+
+
${_node}.tag = ${name};
+
return ${transform ? js`(${transform})(${_node})` : _node};
+
}
+
`;
+
+
export { astRoot };
+11 -12
src/core.js
···
: new RegExp(`^(?:${source})`, 'g');
};
-
export const _substr = (state, pattern) => {
-
const end = state.index + pattern.length;
-
const sub = state.input.slice(state.index, end);
-
if (sub === pattern) {
-
state.index = end;
-
return sub;
-
}
-
};
-
export const _exec = (state, pattern) => {
-
if (typeof pattern === 'function') return pattern();
+
let match;
-
let match;
-
if (isStickySupported) {
+
if (typeof pattern === 'function') {
+
return pattern(state);
+
} else if (typeof pattern === 'string') {
+
const end = state.index + pattern.length;
+
const sub = state.input.slice(state.index, end);
+
if (sub === pattern) {
+
state.index = end;
+
match = sub;
+
}
+
} else if (isStickySupported) {
pattern.lastIndex = state.index;
if (pattern.test(state.input)) {
match = state.input.slice(state.index, pattern.lastIndex);