Mirror: The magical sticky regex-based parser generator 🧙

Implement initial tagged template parsing support

Changed files
+95 -77
src
+33 -33
src/babel/__snapshots__/plugin.test.js.snap
···
_node_expression2 = (0, _reghex._pattern)(2);
const node = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
if (x = (0, _reghex._exec)(state, _node_expression)) {
node.push(x);
} else {
-
state.index = idx1;
+
state.y = y1;
return;
}
if (x = (0, _reghex._exec)(state, _node_expression2)) {
node.push(x);
} else {
-
state.index = idx1;
+
state.y = y1;
return;
}
···
var _inner_expression = _pattern(/inner/);
const inner = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
if (x = _exec(state, _inner_expression)) {
node.push(x);
} else {
-
state.index = idx1;
+
state.y = y1;
return;
}
···
};
const node = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
if (x = inner(state)) {
node.push(x);
} else {
-
state.index = idx1;
+
state.y = y1;
return;
}
···
_node_expression3 = _pattern(3);
const node = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
if (x = _exec(state, _node_expression)) {
node.push(x);
} else {
-
state.index = idx1;
+
state.y = y1;
return;
}
···
alt_3: {
block_3: {
-
var idx3 = state.index;
+
var y3 = state.y;
if (x = _exec(state, _node_expression2)) {
node.push(x);
} else {
-
state.index = idx3;
+
state.y = y3;
node.length = ln2;
break block_3;
}
···
}
loop_3: for (var j3 = 0; 1; j3++) {
-
var idx3 = state.index;
+
var y3 = state.y;
if (!_exec(state, _node_expression3)) {
if (j3) {
-
state.index = idx3;
+
state.y = y3;
break loop_3;
} else {}
-
state.index = idx1;
+
state.y = y1;
node.length = ln2;
return;
}
···
"import { match, tag, _exec, _pattern } from 'reghex';
const inner = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
if (x = node(state)) {
node.push(x);
} else {
-
state.index = idx1;
+
state.y = y1;
return;
}
···
};
const node = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
if (x = inner(state)) {
node.push(x);
} else {
-
state.index = idx1;
+
state.y = y1;
return;
}
···
_node_expression5 = _pattern(5);
const node = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
alt_2: {
block_2: {
-
var idx2 = state.index;
+
var y2 = state.y;
loop_2: for (var j2 = 0; 1; j2++) {
-
var idx2 = state.index;
+
var y2 = state.y;
if (x = _exec(state, _node_expression)) {
node.push(x);
} else {
if (j2) {
-
state.index = idx2;
+
state.y = y2;
break loop_2;
} else {}
-
state.index = idx2;
+
state.y = y2;
break block_2;
}
}
···
}
loop_2: for (var j2 = 0; 1; j2++) {
-
var idx2 = state.index;
+
var y2 = state.y;
if (x = _exec(state, _node_expression2)) {
node.push(x);
} else {
if (j2) {
-
state.index = idx2;
+
state.y = y2;
break loop_2;
} else {}
-
state.index = idx1;
+
state.y = y1;
return;
}
}
loop_2: for (;;) {
-
var idx2 = state.index;
+
var y2 = state.y;
var ln2 = node.length;
if (x = _exec(state, _node_expression3)) {
node.push(x);
} else {
-
state.index = idx2;
+
state.y = y2;
node.length = ln2;
break loop_2;
}
-
var idx4 = state.index;
+
var y4 = state.y;
if (x = _exec(state, _node_expression4)) {
node.push(x);
} else {
-
state.index = idx4;
+
state.y = y4;
}
if (x = _exec(state, _node_expression5)) {
node.push(x);
} else {
-
state.index = idx2;
+
state.y = y2;
node.length = ln2;
break loop_2;
}
···
var _inner_transform = x => x;
const first = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
node.tag = 'inner';
···
const transform = x => x;
const second = function (state) {
-
var idx1 = state.index;
+
var y1 = state.y;
var node = [];
var x;
node.tag = 'node';
+27 -27
src/codegen.js
···
const _node = 'node';
const _match = 'x';
+
let _interpolations = 0;
+
function js(/* arguments */) {
let body = arguments[0][0];
for (let i = 1; i < arguments.length; i++)
···
};
const assignIndex = (depth) =>
-
depth ? js`var idx${depth} = ${_state}.index;` : '';
+
js`var y${depth} = ${_state}.y` +
+
(_interpolations ? js`, var x${depth} = ${_state}.x;` : ';');
const restoreIndex = (depth) =>
-
depth ? js`${_state}.index = idx${depth};` : '';
-
-
const abortOnCondition = (condition, hooks) => js`
-
if (${condition}) {
-
${restoreIndex(opts.index)}
-
${opts.abort}
-
} else {
-
${opts.onAbort}
-
}
-
`;
+
js`${_state}.y = y${depth}` +
+
(_interpolations ? js`, ${_state}.x = x${depth};` : ';');
const astExpression = (ast, depth, opts) => {
const restoreLength =
···
`;
};
-
const astRoot = (ast, name, transform) => js`
-
(function (${_state}) {
-
${assignIndex(1)}
-
var ${_node} = [];
-
var ${_match};
+
const astRoot = (ast, name, transform, interpolations) => {
+
_interpolations = interpolations;
+
+
return js`
+
(function (${_state}) {
+
${assignIndex(1)}
+
var ${_node} = [];
+
var ${_match};
-
${astSequence(ast, 2, {
-
index: 1,
-
length: 0,
-
onAbort: '',
-
abort: js`return;`,
-
capture: true,
-
})}
+
${astSequence(ast, 2, {
+
interpolations,
+
index: 1,
+
length: 0,
+
onAbort: '',
+
abort: js`return;`,
+
capture: true,
+
})}
-
${_node}.tag = ${name};
-
return ${transform ? js`(${transform})(${_node})` : _node};
-
})
-
`;
+
${_node}.tag = ${name};
+
return ${transform ? js`(${transform})(${_node})` : _node};
+
})
+
`;
+
};
export { astRoot };
+32 -14
src/core.js
···
return pattern(state);
}
-
pattern.lastIndex = state.index;
+
const input = state.quasis[state.x];
+
if (input && (pattern.lastIndex = state.y) < input.length) {
+
if (isStickySupported) {
+
if (pattern.test(input)) match = input.slice(state.y, pattern.lastIndex);
+
} else {
+
match = pattern.exec(input)[0] || match;
+
}
+
}
+
+
state.y = pattern.lastIndex;
+
return match;
+
};
-
if (isStickySupported) {
-
if (pattern.test(state.input))
-
match = state.input.slice(state.index, pattern.lastIndex);
-
} else {
-
match = pattern.exec(state.input)[0] || match;
+
export const interpolation = (state) => {
+
let match;
+
+
const input = state.quasis[state.x];
+
if (!input || state.y >= input.length) {
+
state.y = 0;
+
match = state.expressions[state.x++] || match;
}
-
state.index = pattern.lastIndex;
return match;
};
-
export const parse = (pattern) => (input) => {
-
const state = { input, index: 0 };
+
export const parse = (pattern) => (quasis, ...expressions) => {
+
if (typeof quasis === 'string') quasis = [quasis];
+
const state = { quasis, expressions, x: 0, y: 0 };
return pattern(state);
};
export const match = (name, transform) => (quasis, ...expressions) => {
+
let interpolations = 0;
+
const ast = parseDSL(
quasis,
-
expressions.map((expression, i) => ({
-
fn: typeof expression === 'function' && expression.length,
-
id: `_${i}`,
-
}))
+
expressions.map((expression, i) => {
+
if (expression === interpolation) interpolations++;
+
return {
+
fn: typeof expression === 'function' && expression.length,
+
id: `_${i}`,
+
};
+
})
);
const makeMatcher = new Function(
execId + ',_n,_t,' + expressions.map((_expression, i) => `_${i}`).join(','),
-
'return ' + astRoot(ast, '_n', transform ? '_t' : null)
+
'return ' + astRoot(ast, '_n', transform ? '_t' : null, interpolations)
);
return makeMatcher(_exec, name, transform, ...expressions.map(_pattern));
+3 -3
src/core.test.js
···
import { match } from './core';
const expectToParse = (node, input, result, lastIndex = 0) => {
-
const state = { input, index: 0 };
+
const state = { quasis: [input], expressions: [], x: 0, y: 0 };
if (result) result.tag = 'node';
expect(node(state)).toEqual(result);
// NOTE: After parsing we expect the current index to exactly match the
// sum amount of matched characters
if (result === undefined) {
-
expect(state.index).toBe(0);
+
expect(state.y).toBe(0);
} else {
const index = lastIndex || result.reduce((acc, x) => acc + x.length, 0);
-
expect(state.index).toBe(index);
+
expect(state.y).toBe(index);
}
};