Mirror: The magical sticky regex-based parser generator 🧙

Compare changes

Choose any two refs to compare.

Changed files
+194 -46
.github
workflows
src
+26
.github/workflows/mirror.yml
···
+
# Mirrors to https://tangled.sh/@kitten.sh (knot.kitten.sh)
+
name: Mirror (Git Backup)
+
on:
+
push:
+
branches:
+
- main
+
jobs:
+
mirror:
+
runs-on: ubuntu-latest
+
steps:
+
- name: Checkout repository
+
uses: actions/checkout@v4
+
with:
+
fetch-depth: 0
+
fetch-tags: true
+
- name: Mirror
+
env:
+
MIRROR_SSH_KEY: ${{ secrets.MIRROR_SSH_KEY }}
+
GIT_SSH_COMMAND: 'ssh -o StrictHostKeyChecking=yes'
+
run: |
+
mkdir -p ~/.ssh
+
echo "$MIRROR_SSH_KEY" > ~/.ssh/id_rsa
+
chmod 600 ~/.ssh/id_rsa
+
ssh-keyscan -H knot.kitten.sh >> ~/.ssh/known_hosts
+
git remote add mirror "git@knot.kitten.sh:kitten.sh/${GITHUB_REPOSITORY#*/}"
+
git push --mirror mirror
+1 -1
package.json
···
{
"name": "reghex",
-
"version": "3.0.1",
+
"version": "3.0.2",
"description": "The magical sticky regex-based parser generator 🧙",
"author": "Phil Pluckthun <phil@kitten.sh>",
"license": "MIT",
+123 -22
src/babel/__snapshots__/plugin.test.js.snap
···
return;
}
-
node.tag = 'a';
+
if ('a') node.tag = 'a';
return node;
};
···
return;
}
-
node.tag = 'b';
+
if ('b') node.tag = 'b';
return node;
};"
`;
···
return;
}
-
node.tag = 'node';
+
if ('node') node.tag = 'node';
return node;
};"
`;
···
return;
}
-
node.tag = 'inner';
+
if ('inner') node.tag = 'inner';
return node;
};
···
return;
}
-
node.tag = 'node';
+
if ('node') node.tag = 'node';
+
return node;
+
};"
+
`;
+
+
exports[`works with nameless matchers 1`] = `
+
"import { match, __pattern as _pattern } from \\"reghex\\";
+
+
var _objectObject_expression = _pattern(1),
+
_objectObject_expression2 = _pattern(2),
+
_objectObject_expression3 = _pattern(3),
+
_objectObject_expression4 = _pattern(4),
+
_objectObject_expression5 = _pattern(5);
+
+
const node = function (state) {
+
var y1 = state.y,
+
x1 = state.x;
+
var node = [];
+
var x;
+
+
alt_2: {
+
block_2: {
+
var y2 = state.y,
+
x2 = state.x;
+
+
if ((x = _objectObject_expression(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y2;
+
state.x = x2;
+
break block_2;
+
}
+
+
group_2: for (;;) {
+
var y2 = state.y,
+
x2 = state.x;
+
+
if ((x = _objectObject_expression(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y2;
+
state.x = x2;
+
break group_2;
+
}
+
}
+
+
break alt_2;
+
}
+
+
if ((x = _objectObject_expression2(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y1;
+
state.x = x1;
+
return;
+
}
+
+
group_2: for (;;) {
+
var y2 = state.y,
+
x2 = state.x;
+
+
if ((x = _objectObject_expression2(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y2;
+
state.x = x2;
+
break group_2;
+
}
+
}
+
+
group_2: for (;;) {
+
var y2 = state.y,
+
x2 = state.x;
+
var ln2 = node.length;
+
+
if ((x = _objectObject_expression3(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y2;
+
state.x = x2;
+
node.length = ln2;
+
break group_2;
+
}
+
+
var y4 = state.y,
+
x4 = state.x;
+
+
if ((x = _objectObject_expression4(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y4;
+
state.x = x4;
+
}
+
+
if ((x = _objectObject_expression5(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y2;
+
state.x = x2;
+
node.length = ln2;
+
break group_2;
+
}
+
}
+
}
+
+
if (null) node.tag = null;
return node;
};"
`;
···
}
}
-
node.tag = 'node';
+
if ('node') node.tag = 'node';
return node;
};"
`;
···
return;
}
-
node.tag = 'inner';
+
if ('inner') node.tag = 'inner';
return node;
};
···
return;
}
-
node.tag = 'node';
+
if ('node') node.tag = 'node';
return node;
};"
`;
···
break group_2;
}
-
group_4: {
-
var y4 = state.y,
-
x4 = state.x;
+
var y4 = state.y,
+
x4 = state.x;
-
if ((x = _node_expression4(state)) != null) {
-
node.push(x);
-
} else {
-
state.y = y4;
-
state.x = x4;
-
node.length = ln2;
-
break group_4;
-
}
+
if ((x = _node_expression4(state)) != null) {
+
node.push(x);
+
} else {
+
state.y = y4;
+
state.x = x4;
}
if ((x = _node_expression5(state)) != null) {
···
}
}
-
node.tag = 'node';
+
if ('node') node.tag = 'node';
return node;
};"
`;
···
x1 = state.x;
var node = [];
var x;
-
node.tag = 'inner';
+
if ('inner') node.tag = 'inner';
return _inner_transform(node);
};
···
x1 = state.x;
var node = [];
var x;
-
node.tag = 'node';
+
if ('node') node.tag = 'node';
return transform(node);
};"
`;
+15
src/babel/plugin.test.js
···
).toMatchSnapshot();
});
+
it('works with nameless matchers', () => {
+
const code = `
+
import { match } from 'reghex/macro';
+
+
const node = match()\`
+
\${1}+ | \${2}+ (\${3} ( \${4}? \${5} ) )*
+
\`;
+
`;
+
+
expect(
+
transform(code, { babelrc: false, presets: [], plugins: [reghexPlugin] })
+
.code
+
).toMatchSnapshot();
+
});
+
it('works while only minifying', () => {
const code = `
import { match } from 'reghex/macro';
+12 -22
src/babel/transform.js
···
getMatchName(path) {
t.assertTaggedTemplateExpression(path.node);
const nameArgumentPath = path.get('tag.arguments.0');
-
const { confident, value } = nameArgumentPath.evaluate();
-
if (!confident && t.isIdentifier(nameArgumentPath.node)) {
-
return nameArgumentPath.node.name;
-
} else if (confident && typeof value === 'string') {
-
return value;
-
} else {
-
return path.scope.generateUidIdentifierBasedOnNode(path.node);
+
if (nameArgumentPath) {
+
const { confident, value } = nameArgumentPath.evaluate();
+
if (!confident && t.isIdentifier(nameArgumentPath.node)) {
+
return nameArgumentPath.node.name;
+
} else if (confident && typeof value === 'string') {
+
return value;
+
}
}
+
+
return path.scope.generateUidIdentifierBasedOnNode(path.node);
},
/** Given a match, hoists its expressions in front of the match's statement */
···
},
minifyMatch(path) {
-
if (!path.node.tag.arguments.length) {
-
throw path
-
.get('tag')
-
.buildCodeFrameError(
-
'match() must at least be called with a node name'
-
);
-
}
-
const quasis = path.node.quasi.quasis.map((x) =>
t.stringLiteral(x.value.cooked.replace(/\s*/g, ''))
);
···
},
transformMatch(path) {
-
if (!path.node.tag.arguments.length) {
-
throw path
-
.get('tag')
-
.buildCodeFrameError(
-
'match() must at least be called with a node name'
-
);
+
let name = path.node.tag.arguments[0];
+
if (!name) {
+
name = t.nullLiteral();
}
-
const name = path.node.tag.arguments[0];
const quasis = path.node.quasi.quasis.map((x) => x.value.cooked);
const expressions = this._prepareExpressions(path);
+9 -1
src/codegen.js
···
${astChild(ast, depth, opts)}
}
`;
+
} else if (ast.quantifier === '?' && ast.expression) {
+
opts.index = depth;
+
opts.abort = '';
+
+
child = js`
+
${assignIndex(depth)}
+
${astChild(ast, depth, opts)}
+
`;
} else if (ast.quantifier === '?') {
opts.index = depth;
opts.abort = js`break ${group}`;
···
capture: true,
})}
-
${_node}.tag = ${name};
+
if (${name}) ${_node}.tag = ${name};
return ${transform ? js`(${transform})(${_node})` : _node};
})
`;
+8
src/core.test.js
···
}
};
+
describe('can create nameless matchers', () => {
+
it('matches without tagging', () => {
+
const state = { quasis: ['1'], expressions: [], x: 0, y: 0 };
+
const node = match(null)`${/1/}`;
+
expect(node(state)).toEqual(['1']);
+
});
+
});
+
describe('required matcher', () => {
const node = match('node')`${/1/}`;
it.each`