Mirror: The magical sticky regex-based parser generator 🧙

Merge pull request #6 from kitten/feat/jit-runtime

Reimplement with JIT codegen for runtime-only support

+47 -18
README.md
···
<br />
</div>
-
Leveraging the power of sticky regexes and Babel code generation, `reghex` allows
+
Leveraging the power of sticky regexes and JS code generation, `reghex` allows
you to code parsers quickly, by surrounding regular expressions with a regex-like
[DSL](https://en.wikipedia.org/wiki/Domain-specific_language).
···
npm install --save reghex
```
-
##### 2. Add the plugin to your Babel configuration (`.babelrc`, `babel.config.js`, or `package.json:babel`)
+
##### 2. Add the plugin to your Babel configuration _(optional)_
+
+
In your `.babelrc`, `babel.config.js`, or `package.json:babel` add:
```json
{
···
Alternatively, you can set up [`babel-plugin-macros`](https://github.com/kentcdodds/babel-plugin-macros) and
import `reghex` from `"reghex/macro"` instead.
+
This step is **optional**. `reghex` can also generate its optimised JS code during runtime.
+
This will only incur a tiny parsing cost on initialisation, but due to the JIT of modern
+
JS engines there won't be any difference in performance between pre-compiled and compiled
+
versions otherwise.
+
+
Since the `reghex` runtime is rather small, for larger grammars it may even make sense not
+
to precompile the matchers at all. For this case you may pass the `{ "codegen": false }`
+
option to the Babel plugin, which will minify the `reghex` matcher templates without
+
precompiling them.
+
##### 3. Have fun writing parsers!
```js
-
import match, { parse } from 'reghex';
+
import { match, parse } from 'reghex';
const name = match('name')`
${/\w+/}
···
## Authoring Guide
-
You can write "matchers" by importing the default import from `reghex` and
+
You can write "matchers" by importing the `match` import from `reghex` and
using it to write a matcher expression.
```js
-
import match from 'reghex';
+
import { match } from 'reghex';
const name = match('name')`
${/\w+/}
`;
```
-
As can be seen above, the `match` function, which is what we've called the
-
default import, is called with a "node name" and is then called as a tagged
-
template. This template is our **parsing definition**.
+
As can be seen above, the `match` function, is called with a "node name" and
+
is then called as a tagged template. This template is our **parsing definition**.
`reghex` functions only with its Babel plugin, which will detect `match('name')`
and replace the entire tag with a parsing function, which may then look like
···
Let's extend our original example;
```js
-
import match from 'reghex';
+
import { match } from 'reghex';
const name = match('name')`
${/\w+/}
···
*/
```
+
Furthermore, interpolations don't have to just be RegHex matchers. They can
+
also be functions returning matchers or completely custom matching functions.
+
This is useful when your DSL becomes _self-referential_, i.e. when one matchers
+
start referencing each other forming a loop. To fix this we can create a
+
function that returns our root matcher:
+
+
```js
+
import { match } from 'reghex';
+
+
const value = match('value')`
+
(${/\w+/} | ${() => root})+
+
`;
+
+
const root = match('root')`
+
${/root/}+ ${value}
+
`;
+
```
+
### Regex-like DSL
We've seen in the previous examples that matchers are authored using tagged
···
in the parsed string. This is just one feature of the regex-like DSL. The
available operators are the following:
-
| Operator | Example | Description |
-
| -------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-
| `?` | `${/1/}?` | An **optional** may be used to make an interpolation optional. This means that the interpolation may or may not match. |
-
| `*` | `${/1/}*` | A **star** can be used to match an arbitrary amount of interpolation or none at all. This means that the interpolation may repeat itself or may not be matched at all. |
-
| `+` | `${/1/}+` | A **plus** is used like `*` and must match one or more times. When the matcher doesn't match, that's considered a failing case, since the match isn't optional. |
-
| `\|` | `${/1/} \| ${/2/}` | An **alternation** can be used to match either one thing or another, falling back when the first interpolation fails. |
-
| `()` | `(${/1/} ${/2/})+` | A **group** can be used to apply one of the other operators to an entire group of interpolations. |
+
| Operator | Example | Description |
+
| -------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+
| `?` | `${/1/}?` | An **optional** may be used to make an interpolation optional. This means that the interpolation may or may not match. |
+
| `*` | `${/1/}*` | A **star** can be used to match an arbitrary amount of interpolation or none at all. This means that the interpolation may repeat itself or may not be matched at all. |
+
| `+` | `${/1/}+` | A **plus** is used like `*` and must match one or more times. When the matcher doesn't match, that's considered a failing case, since the match isn't optional. |
+
| `\|` | `${/1/} \| ${/2/}` | An **alternation** can be used to match either one thing or another, falling back when the first interpolation fails. |
+
| `()` | `(${/1/} ${/2/})+` | A **group** can be used to apply one of the other operators to an entire group of interpolations. |
| `(?: )` | `(?: ${/1/})` | A **non-capturing group** is like a regular group, but the interpolations matched inside it don't appear in the parser's output. |
-
| `(?= )` | `(?= ${/1/})` | A **positive lookahead** checks whether interpolations match, and if so continues the matcher without changing the input. If it matches, it's essentially ignored. |
+
| `(?= )` | `(?= ${/1/})` | A **positive lookahead** checks whether interpolations match, and if so continues the matcher without changing the input. If it matches, it's essentially ignored. |
| `(?! )` | `(?! ${/1/})` | A **negative lookahead** checks whether interpolations _don't_ match, and if so continues the matcher without changing the input. If the interpolations do match the matcher is aborted. |
We can combine and compose these operators to create more complex matchers.
···
matched, which would cause other matchers to treat it like a mismatch!
```js
-
import match, { parse } from 'reghex';
+
import { match, parse } from 'reghex';
const name = match('name')((x) => {
return x[0] !== 'tim' ? x : undefined;
+3 -2
package.json
···
{
"name": "reghex",
-
"version": "1.0.2",
+
"version": "2.0.0-beta.4",
"description": "The magical sticky regex-based parser generator 🧙",
"author": "Phil Pluckthun <phil@kitten.sh>",
"license": "MIT",
···
"url": "https://github.com/kitten/reghex/issues"
},
"devDependencies": {
+
"@ampproject/rollup-plugin-closure-compiler": "^0.26.0",
"@babel/core": "7.9.6",
"@babel/plugin-transform-modules-commonjs": "^7.9.6",
-
"@babel/plugin-transform-object-assign": "^7.8.3",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-node-resolve": "^7.1.3",
+
"@rollup/pluginutils": "^4.1.0",
"babel-jest": "^26.0.1",
"babel-plugin-closure-elimination": "^1.3.1",
"husky": "^4.2.5",
+29 -12
rollup.config.js
···
import resolve from '@rollup/plugin-node-resolve';
import buble from '@rollup/plugin-buble';
import babel from 'rollup-plugin-babel';
+
import compiler from '@ampproject/rollup-plugin-closure-compiler';
+
+
import simplifyJSTags from './scripts/simplify-jstags-plugin.js';
const plugins = [
commonjs({
···
transforms: {
unicodeRegExp: false,
dangerousForOf: true,
-
dangerousTaggedTemplateString: true,
+
templateString: false,
},
-
objectAssign: 'Object.assign',
exclude: 'node_modules/**',
}),
babel({
···
extensions: ['ts', 'tsx', 'js'],
exclude: 'node_modules/**',
presets: [],
-
plugins: [
-
'@babel/plugin-transform-object-assign',
-
'babel-plugin-closure-elimination',
-
],
+
plugins: ['babel-plugin-closure-elimination'],
}),
];
···
freeze: false,
strict: false,
format,
+
plugins: [
+
simplifyJSTags(),
+
compiler({
+
formatting: 'PRETTY_PRINT',
+
compilation_level: 'SIMPLE_OPTIMIZATIONS',
+
}),
+
],
});
-
export default {
-
input: {
-
core: './src/core.js',
-
babel: './src/babel/plugin.js',
-
macro: './src/babel/macro.js',
-
},
+
const base = {
onwarn: () => {},
external: () => false,
treeshake: {
···
plugins,
output: [output('cjs', '.js'), output('esm', '.mjs')],
};
+
+
export default [
+
{
+
...base,
+
input: {
+
core: './src/core.js',
+
},
+
},
+
{
+
...base,
+
input: {
+
babel: './src/babel/plugin.js',
+
macro: './src/babel/macro.js',
+
},
+
},
+
];
+54
scripts/simplify-jstags-plugin.js
···
+
import { transformSync as transform } from '@babel/core';
+
import { createFilter } from '@rollup/pluginutils';
+
+
const simplifyJSTags = ({ types: t }) => ({
+
visitor: {
+
TaggedTemplateExpression(path) {
+
if (path.node.tag.name !== 'js') return;
+
+
const expressions = path.node.quasi.expressions;
+
+
const quasis = path.node.quasi.quasis.map((x) =>
+
x.value.cooked
+
.replace(/\s*[=(){},;:!]\s*/g, (x) => x.trim())
+
.replace(/\s+/g, ' ')
+
.replace(/^\s+$/g, '')
+
);
+
+
const concat = expressions.reduceRight(
+
(prev, node, i) =>
+
t.binaryExpression(
+
'+',
+
t.stringLiteral(quasis[i]),
+
t.binaryExpression('+', node, prev)
+
),
+
t.stringLiteral(quasis[quasis.length - 1])
+
);
+
+
path.replaceWith(concat);
+
},
+
},
+
});
+
+
function simplifyJSTagsPlugin(opts = {}) {
+
const filter = createFilter(opts.include, opts.exclude, {
+
resolve: false,
+
});
+
+
return {
+
name: 'cleanup',
+
+
renderChunk(code, chunk) {
+
if (!filter(chunk.fileName)) {
+
return null;
+
}
+
+
return transform(code, {
+
plugins: [simplifyJSTags],
+
babelrc: false,
+
});
+
},
+
};
+
}
+
+
export default simplifyJSTagsPlugin;
+162 -110
src/babel/__snapshots__/plugin.test.js.snap
···
var _node_expression = (0, _reghex._pattern)(1),
_node_expression2 = (0, _reghex._pattern)(2);
-
const node = function _node(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
const node = function (state) {
+
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 while only minifying 1`] = `
+
"import { match } from 'reghex/macro';
+
const node = match('node')([\\"\\", \\"+|\\", \\"+(\\", \\"(\\", \\"?\\", \\"))*\\"], 1, 2, 3, 4, 5);"
+
`;
+
exports[`works with local recursion 1`] = `
-
"import { tag, _exec, _substr, _pattern } from 'reghex';
+
"import { match as m, tag, _exec, _pattern } from 'reghex';
+
+
var _inner_expression = _pattern(/inner/);
-
const inner = function _inner(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
const inner = function (state) {
+
var index_1 = state.index;
+
var node = [];
+
var match;
-
if (match = _substr(state, \\"inner\\")) {
+
if (match = _exec(state, _inner_expression)) {
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 = [];
+
const node = function (state) {
+
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 { match, _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 = [];
+
const node = function (state) {
+
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 self-referential thunks 1`] = `
+
"import { match, tag, _exec, _pattern } from 'reghex';
+
+
const inner = function (state) {
+
var index_1 = state.index;
+
var node = [];
+
var match;
+
+
if (match = node(state)) {
+
node.push(match);
+
} else {
+
state.index = index_1;
+
return;
+
}
+
+
node.tag = 'inner';
+
return node;
+
};
+
+
const node = function (state) {
+
var index_1 = state.index;
+
var node = [];
+
var match;
+
+
if (match = inner(state)) {
+
node.push(match);
+
} else {
+
state.index = index_1;
+
return;
+
}
+
+
node.tag = 'node';
+
return node;
};"
`;
exports[`works with standard features 1`] = `
-
"import { _exec, _substr, _pattern, tag as _tag } from \\"reghex\\";
+
"import { match, _exec, _pattern, tag as _tag } from \\"reghex\\";
var _node_expression = _pattern(1),
_node_expression2 = _pattern(2),
···
_node_expression4 = _pattern(4),
_node_expression5 = _pattern(5);
-
const node = function _node(state) {
-
var last_index = state.index;
-
var match,
-
node = [];
+
const node = function (state) {
+
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 { match, _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'));
+
const first = function (state) {
+
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'));
+
const second = function (state) {
+
var index_1 = state.index;
+
var node = [];
+
var match;
+
node.tag = 'node';
+
return transform(node);
};"
`;
+3 -40
src/babel/__tests__/suite.js src/core.test.js
···
-
import * as reghex from '../../..';
-
import * as types from '@babel/types';
-
import { transform } from '@babel/core';
-
import { makeHelpers } from '../transform';
-
-
const match = (name) => (quasis, ...expressions) => {
-
const helpers = makeHelpers(types);
-
-
let str = '';
-
for (let i = 0; i < quasis.length; i++) {
-
str += quasis[i];
-
if (i < expressions.length) str += '${' + expressions[i].toString() + '}';
-
}
-
-
const template = `(function () { return match('${name}')\`${str}\`; })()`;
-
-
const testPlugin = () => ({
-
visitor: {
-
TaggedTemplateExpression(path) {
-
helpers.transformMatch(path);
-
},
-
},
-
});
-
-
const { code } = transform(template, {
-
babelrc: false,
-
presets: [],
-
plugins: [testPlugin],
-
});
-
-
const argKeys = Object.keys(reghex).filter((x) => {
-
return x.startsWith('_') || x === 'tag';
-
});
-
-
const args = argKeys.map((key) => reghex[key]);
-
return new Function(...argKeys, 'return ' + code)(...args);
-
};
+
import { match } from './core';
const expectToParse = (node, input, result, lastIndex = 0) => {
const state = { input, index: 0 };
-
expect(node(state)).toEqual(
-
result === undefined ? result : reghex.tag(result, 'node')
-
);
+
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
-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) => {
+8 -3
src/babel/plugin.js
···
import { makeHelpers } from './transform';
-
export default function reghexPlugin({ types }) {
+
export default function reghexPlugin(babel, opts = {}) {
let helpers;
return {
name: 'reghex',
visitor: {
Program() {
-
helpers = makeHelpers(types);
+
helpers = makeHelpers(babel);
},
ImportDeclaration(path) {
+
if (opts.codegen === false) return;
helpers.updateImport(path);
},
TaggedTemplateExpression(path) {
if (helpers.isMatch(path) && helpers.getMatchImport(path)) {
-
helpers.transformMatch(path);
+
if (opts.codegen === false) {
+
helpers.minifyMatch(path);
+
} else {
+
helpers.transformMatch(path);
+
}
}
},
},
+44 -7
src/babel/plugin.test.js
···
it('works with standard features', () => {
const code = `
-
import match from 'reghex/macro';
+
import { match } from 'reghex/macro';
const node = match('node')\`
\${1}+ | \${2}+ (\${3} ( \${4}? \${5} ) )*
···
).toMatchSnapshot();
});
+
it('works while only minifying', () => {
+
const code = `
+
import { match } from 'reghex/macro';
+
+
const node = match('node')\`
+
\${1}+ | \${2}+ (\${3} ( \${4}? \${5} ) )*
+
\`;
+
`;
+
+
expect(
+
transform(code, {
+
babelrc: false,
+
presets: [],
+
plugins: [[reghexPlugin, { codegen: false }]],
+
}).code
+
).toMatchSnapshot();
+
});
+
it('works with local recursion', () => {
// NOTE: A different default name is allowed
const code = `
-
import match_rec, { tag } from 'reghex';
+
import { match as m, tag } from 'reghex';
-
const inner = match_rec('inner')\`
+
const inner = m('inner')\`
\${/inner/}
\`;
-
const node = match_rec('node')\`
+
const node = m('node')\`
+
\${inner}
+
\`;
+
`;
+
+
expect(
+
transform(code, { babelrc: false, presets: [], plugins: [reghexPlugin] })
+
.code
+
).toMatchSnapshot();
+
});
+
+
it('works with self-referential thunks', () => {
+
const code = `
+
import { match, tag } from 'reghex';
+
+
const inner = match('inner')\`
+
\${() => node}
+
\`;
+
+
const node = match('node')\`
\${inner}
\`;
`;
···
it('works with transform functions', () => {
const code = `
-
import match from 'reghex';
+
import { match } from 'reghex';
const first = match('inner', x => x)\`\`;
···
it('works with non-capturing groups', () => {
const code = `
-
import match from 'reghex';
+
import { match } from 'reghex';
const node = match('node')\`
\${1} (\${2} | (?: \${3})+)
···
it('works together with @babel/plugin-transform-modules-commonjs', () => {
const code = `
-
import match from 'reghex';
+
import { match } from 'reghex';
const node = match('node')\`
\${1} \${2}
-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() {
+58 -48
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;
···
if (!importSourceRe.test(path.node.source.value)) return;
_hasUpdatedImport = true;
-
const defaultSpecifierIndex = path.node.specifiers.findIndex((node) => {
-
return t.isImportDefaultSpecifier(node);
-
});
-
-
if (defaultSpecifierIndex > -1) {
-
path.node.specifiers.splice(defaultSpecifierIndex, 1);
-
}
-
if (path.node.source.value !== importName) {
path.node.source = t.stringLiteral(importName);
}
···
t.identifier('_exec')
),
t.importSpecifier(
-
(ids.substrId = path.scope.generateUidIdentifier('substr')),
-
t.identifier('_substr')
-
),
-
t.importSpecifier(
(ids.patternId = path.scope.generateUidIdentifier('pattern')),
t.identifier('_pattern')
)
···
const tagImport = path.node.specifiers.find((node) => {
return t.isImportSpecifier(node) && node.imported.name === 'tag';
});
+
if (!tagImport) {
path.node.specifiers.push(
t.importSpecifier(
···
binding.kind !== 'module' ||
!t.isImportDeclaration(binding.path.parent) ||
!importSourceRe.test(binding.path.parent.source.value) ||
-
!t.isImportDefaultSpecifier(binding.path.node)
+
!t.isImportSpecifier(binding.path.node)
) {
return null;
}
···
const hoistedExpressions = path.node.quasi.expressions.map(
(expression, i) => {
if (
+
t.isArrowFunctionExpression(expression) &&
+
t.isIdentifier(expression.body)
+
) {
+
expression = expression.body;
+
} else if (
+
(t.isFunctionExpression(expression) ||
+
t.isArrowFunctionExpression(expression)) &&
+
t.isBlockStatement(expression.body) &&
+
expression.body.body.length === 1 &&
+
t.isReturnStatement(expression.body.body[0]) &&
+
t.isIdentifier(expression.body.body[0].argument)
+
) {
+
expression = expression.body.body[0].argument;
+
}
+
+
if (
t.isIdentifier(expression) &&
path.scope.hasBinding(expression.name)
) {
···
const matchPath = binding.path.get('init');
if (this.isMatch(matchPath)) return expression;
}
-
} else if (
-
t.isRegExpLiteral(expression) &&
-
!regexPatternsRe.test(expression.pattern)
-
) {
-
// NOTE: This is an optimisation path, where the pattern regex is inlined
-
// and has determined to be "simple" enough to be turned into a string
-
return t.stringLiteral(
-
expression.pattern.replace(/\\./g, (x) => x[1])
-
);
}
const id = path.scope.generateUidIdentifier(
···
}
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;
+
},
+
+
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, ''))
+
);
+
const expressions = path.node.quasi.expressions;
+
const transform = this._prepareTransform(path);
+
+
path.replaceWith(
+
t.callExpression(path.node.tag, [
+
t.arrayExpression(quasis),
+
...expressions,
+
])
+
);
},
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 code = astRoot(ast, '%%name%%', transform && '%%transform%%');
+
+
path.replaceWith(
+
template.expression(code)(transform ? { name, transform } : { name })
);
-
path.replaceWith(matchFunction);
},
};
}
+252
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 body.trim();
+
}
+
+
const newOpts = (prev, next) => ({
+
index: next.index != null ? next.index : prev.index,
+
length: next.length != null ? next.length : prev.length,
+
onAbort: next.onAbort != null ? next.onAbort : prev.onAbort,
+
abort: next.abort != null ? next.abort : prev.abort,
+
capture: next.capture != null ? next.capture : prev.capture,
+
});
+
+
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 restoreLength =
+
opts.length &&
+
opts.abort &&
+
js`
+
${_node}.length = length_${opts.length};
+
`;
+
+
const abort = js`
+
${opts.onAbort || ''}
+
${restoreIndex(opts.index)}
+
${restoreLength || ''}
+
${opts.abort || ''}
+
`;
+
+
if (!opts.capture) {
+
return js`
+
if (!(${ast.expression})) {
+
${abort}
+
}
+
`;
+
}
+
+
return js`
+
if (${_match} = ${ast.expression}) {
+
${_node}.push(${_match});
+
} else {
+
${abort}
+
}
+
`;
+
};
+
+
const astGroup = (ast, depth, opts) => {
+
const capture = !!opts.capture && !ast.capture;
+
+
let group = '';
+
if (!opts.length && capture) {
+
return js`
+
${js`var length_${depth} = ${_node}.length;`}
+
${astSequence(
+
ast.sequence,
+
depth + 1,
+
newOpts(opts, {
+
length: depth,
+
capture,
+
})
+
)}
+
`;
+
}
+
+
return astSequence(
+
ast.sequence,
+
depth + 1,
+
newOpts(opts, {
+
capture,
+
})
+
);
+
};
+
+
const astChild = (ast, depth, opts) =>
+
ast.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,
+
newOpts(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,
+
newOpts(opts, {
+
length: 0,
+
index: depth,
+
abort: js`break ${label};`,
+
onAbort: '',
+
})
+
)}
+
}
+
`;
+
};
+
+
const astOptional = (ast, depth, opts) => js`
+
${assignIndex(depth)}
+
${astChild(
+
ast,
+
depth,
+
newOpts(opts, {
+
index: depth,
+
abort: '',
+
onAbort: '',
+
})
+
)}
+
`;
+
+
const astQuantifier = (ast, depth, opts) => {
+
const { index, abort } = opts;
+
const label = `invert_${depth}`;
+
+
if (ast.capture === '!') {
+
opts = newOpts(opts, {
+
index: depth,
+
abort: js`break ${label};`,
+
});
+
}
+
+
let child;
+
if (ast.quantifier === '+') {
+
child = astRepeating(ast, depth, opts);
+
} else if (ast.quantifier === '*') child = astMultiple(ast, depth, opts);
+
else if (ast.quantifier === '?') child = astOptional(ast, depth, opts);
+
else child = astChild(ast, depth, opts);
+
+
if (ast.capture === '!') {
+
return js`
+
${label}: {
+
${assignIndex(depth)}
+
${child}
+
${restoreIndex(index)}
+
${abort}
+
}
+
`;
+
} else if (ast.capture === '=') {
+
return js`
+
${assignIndex(depth)}
+
${child}
+
${restoreIndex(depth)}
+
`;
+
} else {
+
return child;
+
}
+
};
+
+
const astSequence = (ast, depth, opts) => {
+
const alternation = ast.alternation ? `alternation_${depth}` : '';
+
+
let body = '';
+
for (; ast; ast = ast.alternation) {
+
const block = `block_${depth}`;
+
+
let childOpts = opts;
+
if (ast.alternation) {
+
childOpts = newOpts(opts, {
+
index: depth,
+
abort: js`break ${block};`,
+
onAbort: '',
+
});
+
}
+
+
let sequence = '';
+
for (let i = 0; i < ast.length; i++)
+
sequence += astQuantifier(ast[i], depth, childOpts);
+
+
if (!ast.alternation) {
+
body += sequence;
+
} else {
+
body += js`
+
${block}: {
+
${assignIndex(depth)}
+
${sequence}
+
break ${alternation};
+
}
+
`;
+
}
+
}
+
+
if (!alternation) return body;
+
+
return js`
+
${alternation}: {
+
${body}
+
}
+
`;
+
};
+
+
const astRoot = (ast, name, transform) => js`
+
(function (${_state}) {
+
${assignIndex(1)}
+
var ${_node} = [];
+
var ${_match};
+
+
${astSequence(ast, 2, {
+
index: 1,
+
length: 0,
+
onAbort: '',
+
abort: js`return;`,
+
capture: true,
+
})}
+
+
${_node}.tag = ${name};
+
return ${transform ? js`(${transform})(${_node})` : _node};
+
})
+
`;
+
+
export { astRoot };
+29 -31
src/core.js
···
+
import { astRoot } from './codegen';
+
import { parse as parseDSL } from './parser';
+
const isStickySupported = typeof /./g.sticky === 'boolean';
export const _pattern = (input) => {
if (typeof input === 'function') return input;
-
const source = typeof input !== 'string' ? input.source : input;
return isStickySupported
? new RegExp(source, 'y')
-
: new RegExp(`^(?:${source})`, 'g');
+
: 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) => {
+
let match;
+
+
if (typeof pattern === 'function') {
+
if (!pattern.length) pattern = pattern();
+
return pattern(state);
}
-
};
-
export const _exec = (state, pattern) => {
-
if (typeof pattern === 'function') return pattern();
+
pattern.lastIndex = state.index;
-
let match;
if (isStickySupported) {
-
pattern.lastIndex = state.index;
-
if (pattern.test(state.input)) {
+
if (pattern.test(state.input))
match = state.input.slice(state.index, pattern.lastIndex);
-
state.index = pattern.lastIndex;
-
}
} else {
-
pattern.lastIndex = 0;
-
if (pattern.test(state.input.slice(state.index))) {
-
const lastIndex = state.index + pattern.lastIndex;
-
match = state.input.slice(state.index, lastIndex);
-
state.index = lastIndex;
-
}
+
match = pattern.exec(state.input)[0] || match;
}
+
state.index = pattern.lastIndex;
return match;
};
-
export const tag = (array, tag) => {
-
array.tag = tag;
-
return array;
-
};
-
export const parse = (pattern) => (input) => {
const state = { input, index: 0 };
return pattern(state);
};
-
export const match = (_name) => {
-
throw new TypeError(
-
'This match() function was not transformed. ' +
-
'Ensure that the Babel plugin is set up correctly and try again.'
+
export const match = (name, transform) => (quasis, ...expressions) => {
+
const ast = parseDSL(
+
quasis,
+
expressions.map((expression, i) =>
+
typeof expression === 'function' && expression.length
+
? `_${i}(state)`
+
: `_e(state, _${i})`
+
)
);
+
+
const makeMatcher = new Function(
+
'_e,_n,_t,' + expressions.map((_expression, i) => `_${i}`).join(','),
+
'return ' + astRoot(ast, '_n', transform ? '_t' : null)
+
);
+
+
return makeMatcher(_exec, name, transform, ...expressions.map(_pattern));
};
+27 -77
src/parser.js
···
+
const syntaxError = (char) => {
+
throw new SyntaxError('Unexpected token "' + char + '"');
+
};
+
export const parse = (quasis, expressions) => {
let quasiIndex = 0;
let stackIndex = 0;
const sequenceStack = [];
-
const rootSequence = {
-
type: 'sequence',
-
sequence: [],
-
alternation: null,
-
};
+
const rootSequence = [];
let currentGroup = null;
let lastMatch;
let currentSequence = rootSequence;
-
while (stackIndex < quasis.length + expressions.length) {
+
for (
+
let quasiIndex = 0, stackIndex = 0;
+
stackIndex < quasis.length + expressions.length;
+
stackIndex++
+
) {
if (stackIndex % 2 !== 0) {
-
const expression = expressions[stackIndex++ >> 1];
-
-
currentSequence.sequence.push({
-
type: 'expression',
-
expression,
-
quantifier: null,
+
currentSequence.push({
+
expression: expressions[stackIndex++ >> 1],
});
}
const quasi = quasis[stackIndex >> 1];
-
while (quasiIndex < quasi.length) {
+
for (quasiIndex = 0; quasiIndex < quasi.length; ) {
const char = quasi[quasiIndex++];
-
if (char === ' ' || char === '\t' || char === '\r' || char === '\n') {
-
continue;
-
} else if (char === '|' && currentSequence.sequence.length > 0) {
-
currentSequence = currentSequence.alternation = {
-
type: 'sequence',
-
sequence: [],
-
alternation: null,
-
};
-
-
continue;
-
} else if (char === ')' && currentSequence.sequence.length > 0) {
+
} else if (char === '|' && currentSequence.length) {
+
currentSequence = currentSequence.alternation = [];
+
} else if (char === ')' && currentSequence.length) {
currentGroup = null;
currentSequence = sequenceStack.pop();
-
if (currentSequence) continue;
+
if (!currentSequence) syntaxError(char);
} else if (char === '(') {
-
currentGroup = {
-
type: 'group',
-
sequence: {
-
type: 'sequence',
-
sequence: [],
-
alternation: null,
-
},
-
capturing: true,
-
lookahead: null,
-
quantifier: null,
-
};
-
sequenceStack.push(currentSequence);
-
currentSequence.sequence.push(currentGroup);
+
currentSequence.push((currentGroup = { sequence: [] }));
currentSequence = currentGroup.sequence;
-
continue;
-
} else if (
-
char === '?' &&
-
currentSequence.sequence.length === 0 &&
-
currentGroup
-
) {
+
} else if (char === '?' && !currentSequence.length && currentGroup) {
const nextChar = quasi[quasiIndex++];
-
if (!nextChar) {
-
throw new SyntaxError('Unexpected end of input after ' + char);
-
}
-
-
if (nextChar === ':') {
-
currentGroup.capturing = false;
-
continue;
-
} else if (nextChar === '=') {
-
currentGroup.capturing = false;
-
currentGroup.lookahead = 'positive';
-
continue;
-
} else if (nextChar === '!') {
-
currentGroup.capturing = false;
-
currentGroup.lookahead = 'negative';
-
continue;
+
if (nextChar === ':' || nextChar === '=' || nextChar === '!') {
+
currentGroup.capture = nextChar;
+
} else {
+
syntaxError(char);
}
} else if (
(char === '?' || char === '+' || char === '*') &&
-
(lastMatch =
-
currentSequence.sequence[currentSequence.sequence.length - 1])
+
(lastMatch = currentSequence[currentSequence.length - 1])
) {
-
if (lastMatch.type === 'group' && lastMatch.lookahead) {
-
throw new SyntaxError('Unexpected quantifier on lookahead group');
-
}
-
-
lastMatch.quantifier = {
-
type: 'quantifier',
-
required: char === '+',
-
singular: char === '?',
-
};
-
-
continue;
+
lastMatch.quantifier = char;
+
} else {
+
syntaxError(char);
}
-
-
throw new SyntaxError('Unexpected token ' + char);
}
-
-
stackIndex++;
-
quasiIndex = 0;
}
return rootSequence;
+34 -113
src/parser.test.js
···
const parseTag = (quasis, ...expressions) => parse(quasis, expressions);
-
it('supports parsing expressions', () => {
-
expect(parseTag`${1}`).toEqual({
-
type: 'sequence',
-
sequence: [
-
{
-
type: 'expression',
-
expression: 1,
-
quantifier: null,
-
},
-
],
-
alternation: null,
-
});
-
});
-
it('supports parsing expressions with quantifiers', () => {
let ast;
ast = parseTag`${1}?`;
-
expect(ast).toHaveProperty('sequence.0.type', 'expression');
-
expect(ast).toHaveProperty('sequence.0.quantifier', {
-
type: 'quantifier',
-
required: false,
-
singular: true,
-
});
+
expect(ast).toHaveProperty('0.quantifier', '?');
ast = parseTag`${1}+`;
-
expect(ast).toHaveProperty('sequence.0.type', 'expression');
-
expect(ast).toHaveProperty('sequence.0.quantifier', {
-
type: 'quantifier',
-
required: true,
-
singular: false,
-
});
+
expect(ast).toHaveProperty('0.quantifier', '+');
ast = parseTag`${1}*`;
-
expect(ast).toHaveProperty('sequence.0.type', 'expression');
-
expect(ast).toHaveProperty('sequence.0.quantifier', {
-
type: 'quantifier',
-
required: false,
-
singular: false,
-
});
+
expect(ast).toHaveProperty('0.quantifier', '*');
});
it('supports top-level alternations', () => {
let ast;
ast = parseTag`${1} | ${2}`;
-
expect(ast).toHaveProperty('sequence.length', 1);
-
expect(ast).toHaveProperty('sequence.0.type', 'expression');
-
expect(ast).toHaveProperty('sequence.0.expression', 1);
-
expect(ast).toHaveProperty('alternation.type', 'sequence');
-
expect(ast).toHaveProperty('alternation.sequence.0.expression', 2);
+
expect(ast).toHaveProperty('length', 1);
+
expect(ast).toHaveProperty('0.expression', 1);
+
expect(ast).toHaveProperty('alternation.0.expression', 2);
ast = parseTag`${1}? | ${2}?`;
-
expect(ast).toHaveProperty('sequence.0.quantifier.type', 'quantifier');
-
expect(ast).toHaveProperty(
-
'alternation.sequence.0.quantifier.type',
-
'quantifier'
-
);
+
expect(ast).toHaveProperty('0.quantifier', '?');
});
it('supports groups with quantifiers', () => {
let ast;
ast = parseTag`(${1} ${2})`;
-
expect(ast).toHaveProperty('sequence.length', 1);
-
expect(ast).toHaveProperty('sequence.0.type', 'group');
-
expect(ast).toHaveProperty('sequence.0.sequence.sequence.length', 2);
-
expect(ast).toHaveProperty('sequence.0.sequence.sequence.0.expression', 1);
-
expect(ast).toHaveProperty('sequence.0.sequence.sequence.1.expression', 2);
+
expect(ast).toHaveProperty('length', 1);
+
expect(ast).toHaveProperty('0.sequence.length', 2);
+
expect(ast).toHaveProperty('0.sequence.0.expression', 1);
+
expect(ast).toHaveProperty('0.sequence.1.expression', 2);
ast = parseTag`(${1} ${2}?)?`;
-
expect(ast).toHaveProperty('sequence.length', 1);
-
expect(ast).toHaveProperty('sequence.0.type', 'group');
-
expect(ast).toHaveProperty('sequence.0.quantifier.type', 'quantifier');
-
expect(ast).toHaveProperty('sequence.0.sequence.sequence.0.quantifier', null);
-
expect(ast).toHaveProperty(
-
'sequence.0.sequence.sequence.1.quantifier.type',
-
'quantifier'
-
);
+
expect(ast).toHaveProperty('length', 1);
+
expect(ast).toHaveProperty('0.quantifier', '?');
+
expect(ast).toHaveProperty('0.sequence.0.quantifier', undefined);
});
it('supports non-capturing groups', () => {
const ast = parseTag`(?: ${1})`;
-
expect(ast).toHaveProperty('sequence.length', 1);
-
expect(ast).toHaveProperty('sequence.0.type', 'group');
-
expect(ast).toHaveProperty('sequence.0.capturing', false);
-
expect(ast).toHaveProperty('sequence.0.lookahead', null);
-
expect(ast).toHaveProperty('sequence.0.sequence.sequence.length', 1);
+
expect(ast).toHaveProperty('length', 1);
+
expect(ast).toHaveProperty('0.capture', ':');
+
expect(ast).toHaveProperty('0.sequence.length', 1);
});
it('supports positive lookahead groups', () => {
const ast = parseTag`(?= ${1})`;
-
expect(ast).toHaveProperty('sequence.length', 1);
-
expect(ast).toHaveProperty('sequence.0.type', 'group');
-
expect(ast).toHaveProperty('sequence.0.capturing', false);
-
expect(ast).toHaveProperty('sequence.0.lookahead', 'positive');
-
expect(ast).toHaveProperty('sequence.0.sequence.sequence.length', 1);
+
expect(ast).toHaveProperty('length', 1);
+
expect(ast).toHaveProperty('0.capture', '=');
+
expect(ast).toHaveProperty('0.sequence.length', 1);
});
it('supports negative lookahead groups', () => {
const ast = parseTag`(?! ${1})`;
-
expect(ast).toHaveProperty('sequence.length', 1);
-
expect(ast).toHaveProperty('sequence.0.type', 'group');
-
expect(ast).toHaveProperty('sequence.0.capturing', false);
-
expect(ast).toHaveProperty('sequence.0.lookahead', 'negative');
-
expect(ast).toHaveProperty('sequence.0.sequence.sequence.length', 1);
-
});
-
-
it('throws when a quantifier is combined with a lookahead', () => {
-
expect(() => parseTag`(?! ${1})+`).toThrow();
-
expect(() => parseTag`(?! ${1})?`).toThrow();
-
expect(() => parseTag`(?! ${1})*`).toThrow();
+
expect(ast).toHaveProperty('length', 1);
+
expect(ast).toHaveProperty('0.capture', '!');
+
expect(ast).toHaveProperty('0.sequence.length', 1);
});
it('supports groups with alternates', () => {
expect(parseTag`(${1} | ${2}) ${3}`).toMatchInlineSnapshot(`
-
Object {
-
"alternation": null,
-
"sequence": Array [
-
Object {
-
"capturing": true,
-
"lookahead": null,
-
"quantifier": null,
-
"sequence": Object {
-
"alternation": Object {
-
"alternation": null,
-
"sequence": Array [
-
Object {
-
"expression": 2,
-
"quantifier": null,
-
"type": "expression",
-
},
-
],
-
"type": "sequence",
-
},
-
"sequence": Array [
-
Object {
-
"expression": 1,
-
"quantifier": null,
-
"type": "expression",
-
},
-
],
-
"type": "sequence",
+
Array [
+
Object {
+
"sequence": Array [
+
Object {
+
"expression": 1,
},
-
"type": "group",
-
},
-
Object {
-
"expression": 3,
-
"quantifier": null,
-
"type": "expression",
-
},
-
],
-
"type": "sequence",
-
}
+
],
+
},
+
Object {
+
"expression": 3,
+
},
+
]
`);
});
+178 -22
yarn.lock
···
# yarn lockfile v1
+
"@ampproject/remapping@0.2.0":
+
version "0.2.0"
+
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-0.2.0.tgz#07290a5c0f5eac8a4c33d38aa0d15a3416db432e"
+
integrity sha512-a4EztS9/GOVQjX5Ol+Iz33TFhaXvYBF7aB6D8+Qz0/SCIxOm3UNRhGZiwcCuJ8/Ifc6NCogp3S48kc5hFxRpUw==
+
dependencies:
+
"@jridgewell/resolve-uri" "1.0.0"
+
sourcemap-codec "1.4.8"
+
+
"@ampproject/rollup-plugin-closure-compiler@^0.26.0":
+
version "0.26.0"
+
resolved "https://registry.yarnpkg.com/@ampproject/rollup-plugin-closure-compiler/-/rollup-plugin-closure-compiler-0.26.0.tgz#69f8265e5fdbf3e26905eaaedc60cb5982bd6be0"
+
integrity sha512-wuHzGE6BDhDR0L7nUPlpQDPGiGnMw+b0B+cDPG0S5TatOmFNQva8KSNdBHan3L9RbvNyYXOXicuCrZtSoBfrBg==
+
dependencies:
+
"@ampproject/remapping" "0.2.0"
+
acorn "7.2.0"
+
acorn-walk "7.1.1"
+
estree-walker "2.0.1"
+
google-closure-compiler "20200517.0.0"
+
magic-string "0.25.7"
+
uuid "8.1.0"
+
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
···
"@babel/helper-simple-access" "^7.8.3"
babel-plugin-dynamic-import-node "^2.3.3"
-
"@babel/plugin-transform-object-assign@^7.8.3":
-
version "7.8.3"
-
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.8.3.tgz#dc3b8dd50ef03837868a37b7df791f64f288538e"
-
integrity sha512-i3LuN8tPDqUCRFu3dkzF2r1Nx0jp4scxtm7JxtIqI9he9Vk20YD+/zshdzR9JLsoBMlJlNR82a62vQExNEVx/Q==
-
dependencies:
-
"@babel/helper-plugin-utils" "^7.8.3"
-
"@babel/template@^7.3.3", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
···
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
+
"@jridgewell/resolve-uri@1.0.0":
+
version "1.0.0"
+
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-1.0.0.tgz#3fdf5798f0b49e90155896f6291df186eac06c83"
+
integrity sha512-9oLAnygRMi8Q5QkYEU4XWK04B+nuoXoxjRvRxgjuChkLZFBja0YPSgdZ7dZtwhncLBcQe/I/E+fLuk5qxcYVJA==
+
"@rollup/plugin-buble@^0.21.3":
version "0.21.3"
resolved "https://registry.yarnpkg.com/@rollup/plugin-buble/-/plugin-buble-0.21.3.tgz#1649a915b1d051a4f430d40e7734a7f67a69b33e"
···
estree-walker "^1.0.1"
picomatch "^2.2.2"
+
"@rollup/pluginutils@^4.1.0":
+
version "4.1.0"
+
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.0.tgz#0dcc61c780e39257554feb7f77207dceca13c838"
+
integrity sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ==
+
dependencies:
+
estree-walker "^2.0.1"
+
picomatch "^2.2.2"
+
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
···
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
-
acorn-walk@^7.1.1:
+
acorn-walk@7.1.1, acorn-walk@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e"
integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==
+
acorn@7.2.0, acorn@^7.1.1:
+
version "7.2.0"
+
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
+
integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==
+
acorn@^6.4.1:
version "6.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
-
acorn@^7.1.1:
-
version "7.2.0"
-
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
-
integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==
-
aggregate-error@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0"
···
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
-
chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
+
chalk@2.x, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
···
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
+
clone-buffer@^1.0.0:
+
version "1.0.0"
+
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
+
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
+
+
clone-stats@^1.0.0:
+
version "1.0.0"
+
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
+
integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=
+
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
+
clone@^2.1.1:
+
version "2.1.2"
+
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
+
+
cloneable-readable@^1.0.0:
+
version "1.1.3"
+
resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec"
+
integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==
+
dependencies:
+
inherits "^2.0.1"
+
process-nextick-args "^2.0.0"
+
readable-stream "^2.3.5"
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
···
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
-
core-util-is@1.0.2:
+
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
···
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
estree-walker@2.0.1, estree-walker@^2.0.1:
+
version "2.0.1"
+
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0"
+
integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==
+
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
···
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
google-closure-compiler-java@^20200517.0.0:
+
version "20200517.0.0"
+
resolved "https://registry.yarnpkg.com/google-closure-compiler-java/-/google-closure-compiler-java-20200517.0.0.tgz#778370c22273c9085f4cf959ce063f8f112c02ac"
+
integrity sha512-JVZBiyyXwcYi6Yc3lO6dF2hMLJA4OzPm4/mgsem/tF1vk2HsWTnL3GTaBsPB2ENVZp0hoqsd4KgpPiG9ssNWxw==
+
+
google-closure-compiler-js@^20200517.0.0:
+
version "20200517.0.0"
+
resolved "https://registry.yarnpkg.com/google-closure-compiler-js/-/google-closure-compiler-js-20200517.0.0.tgz#9cb0861f764073d1c4d3b7453b74073ccb1ecfb1"
+
integrity sha512-dz6dOUHx5nhdIqMRXacAYS8aJfLvw4IKxGg28Hq/zeeDPHlX3P3iBK20NgFDfT8zdushThymtMqChSy7C5eyfA==
+
+
google-closure-compiler-linux@^20200517.0.0:
+
version "20200517.0.0"
+
resolved "https://registry.yarnpkg.com/google-closure-compiler-linux/-/google-closure-compiler-linux-20200517.0.0.tgz#2b9ecb634130060174aff5c52329a694ea4be68b"
+
integrity sha512-S5xPh6TtP+ESzZrmQLcDDqtZAsCVTbdI4VS98wQlN6IMZTd94nAnOCg9mrxQNAgop2t4sdsv/KuH0BGPUWEZ+w==
+
+
google-closure-compiler-osx@^20200517.0.0:
+
version "20200517.0.0"
+
resolved "https://registry.yarnpkg.com/google-closure-compiler-osx/-/google-closure-compiler-osx-20200517.0.0.tgz#9394e9a2fd97e3729fc3bd2abcffff6aab2cfcaa"
+
integrity sha512-FWIcsKqLllLjdOBZd7azijVaObydgRd0obVNi63eUfC5MX6T4qxKumGCyor2UCNY6by2ESz+PlGqCFzFhZ6b2g==
+
+
google-closure-compiler-windows@^20200517.0.0:
+
version "20200517.0.0"
+
resolved "https://registry.yarnpkg.com/google-closure-compiler-windows/-/google-closure-compiler-windows-20200517.0.0.tgz#c5cdde438c29458666a83358567b12072924ed6c"
+
integrity sha512-UXhjRGwS8deTkRla/riyVq3psscgMuw78lepEPtq5NgbumgJzY2+IQP9q+4MVOfJW58Rv0JUWKAFOnBBSZWcAQ==
+
+
google-closure-compiler@20200517.0.0:
+
version "20200517.0.0"
+
resolved "https://registry.yarnpkg.com/google-closure-compiler/-/google-closure-compiler-20200517.0.0.tgz#6c47f99fc1be59bd4f9e23c5a8f2e66d64b54143"
+
integrity sha512-80W9zBS9Ajk1T5InWCfsoPohDmo5T1AAyw1rHh5+dgb/jPgwC65KhY+oJozTncf+/7tyQHJXozTARwhSlBUcMg==
+
dependencies:
+
chalk "2.x"
+
google-closure-compiler-java "^20200517.0.0"
+
google-closure-compiler-js "^20200517.0.0"
+
minimist "1.x"
+
vinyl "2.x"
+
vinyl-sourcemaps-apply "^0.2.0"
+
optionalDependencies:
+
google-closure-compiler-linux "^20200517.0.0"
+
google-closure-compiler-osx "^20200517.0.0"
+
google-closure-compiler-windows "^20200517.0.0"
+
graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
···
once "^1.3.0"
wrappy "1"
-
inherits@2:
+
inherits@2, inherits@^2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
···
dependencies:
is-docker "^2.0.0"
-
isarray@1.0.0:
+
isarray@1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
···
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
-
magic-string@^0.25.0, magic-string@^0.25.2, magic-string@^0.25.7:
+
magic-string@0.25.7, magic-string@^0.25.0, magic-string@^0.25.2, magic-string@^0.25.7:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
···
dependencies:
brace-expansion "^1.1.7"
-
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
+
minimist@1.x, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
···
ansi-styles "^4.0.0"
react-is "^16.12.0"
+
process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
+
version "2.0.1"
+
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
prompts@^2.0.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068"
···
parse-json "^5.0.0"
type-fest "^0.6.0"
+
readable-stream@^2.3.5:
+
version "2.3.7"
+
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+
dependencies:
+
core-util-is "~1.0.0"
+
inherits "~2.0.3"
+
isarray "~1.0.0"
+
process-nextick-args "~2.0.0"
+
safe-buffer "~5.1.1"
+
string_decoder "~1.1.1"
+
util-deprecate "~1.0.1"
+
regenerate-unicode-properties@^8.0.2:
version "8.2.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
···
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
+
replace-ext@^1.0.0:
+
version "1.0.1"
+
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a"
+
integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==
+
request-promise-core@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
···
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-
safe-buffer@~5.1.1:
+
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
···
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
-
source-map@^0.5.0, source-map@^0.5.6:
+
source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
···
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
sourcemap-codec@^1.4.4:
+
sourcemap-codec@1.4.8, sourcemap-codec@^1.4.4:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
···
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
+
+
string_decoder@~1.1.1:
+
version "1.1.1"
+
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+
dependencies:
+
safe-buffer "~5.1.0"
stringify-object@^3.3.0:
version "3.3.0"
···
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
+
util-deprecate@~1.0.1:
+
version "1.0.2"
+
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+
uuid@8.1.0:
+
version "8.1.0"
+
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
+
integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
+
uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
···
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"
+
+
vinyl-sourcemaps-apply@^0.2.0:
+
version "0.2.1"
+
resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705"
+
integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=
+
dependencies:
+
source-map "^0.5.1"
+
+
vinyl@2.x:
+
version "2.2.1"
+
resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974"
+
integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==
+
dependencies:
+
clone "^2.1.1"
+
clone-buffer "^1.0.0"
+
clone-stats "^1.0.0"
+
cloneable-readable "^1.0.0"
+
remove-trailing-separator "^1.0.1"
+
replace-ext "^1.0.0"
w3c-hr-time@^1.0.2:
version "1.0.2"