Compare commits

...

10 Commits

@ -0,0 +1,3 @@
lib/parser.js
test/vendor/**/*
benchmark/vendor/**/*

@ -1,3 +1,3 @@
{
"extends": "dmajda"
"extends": "@joepie91/eslint-config"
}

4
.gitignore vendored

@ -1,3 +1,3 @@
browser/*
browser
examples/*.js
node_modules/*
node_modules

@ -1,9 +1,6 @@
[![Build status](https://img.shields.io/travis/pegjs/pegjs.svg)](https://travis-ci.org/pegjs/pegjs)
[![npm version](https://img.shields.io/npm/v/pegjs.svg)](https://www.npmjs.com/package/pegjs)
[![Bower version](https://img.shields.io/bower/v/pegjs.svg)](https://github.com/pegjs/bower)
[![License](https://img.shields.io/badge/license-mit-blue.svg)](https://opensource.org/licenses/MIT)
PEG.js
PEG-Redux
======
PEG.js is a simple parser generator for JavaScript that produces fast parsers
@ -11,6 +8,10 @@ with excellent error reporting. You can use it to process complex data or
computer languages and build transformers, interpreters, compilers and other
tools easily.
PEG-Redux is a __work-in-progress__ fork of PEG.js, with the aim of continuing
maintenance on the PEG.js project, while adding support for modern features
such as modules.
Features
--------
@ -20,49 +21,48 @@ Features
* Based on [parsing expression
grammar](http://en.wikipedia.org/wiki/Parsing_expression_grammar) formalism
— more powerful than traditional LL(*k*) and LR(*k*) parsers
* Usable [from your browser](https://pegjs.org/online), from the command line,
or via JavaScript API
* Usable from your browser, from the command line, or via JavaScript API
Getting Started
---------------
Differences from the original PEG.js
--------
[Online version](https://pegjs.org/online) is the easiest way to generate a
parser. Just enter your grammar, try parsing few inputs, and download generated
parser code.
* The plugin API has been dropped for now, as it was underspecified and not very commonly used. A new, more robust and extensive plugin API may come to exist in the future, if it turns out that there is a high demand for customizations that wouldn't fit into the PEG-Redux project itself.
* Bower and stand-alone browser builds have been discontinued. Please use a bundler (see below) instead.
* AMD, UMD and globals support have been discontinued. The generated parsers now only support CommonJS.
* Module support. Both for importing other PEGRedux files, and for `require()`ing JS modules.
Installation
------------
### Node.js
To use the `pegjs` command, install PEG.js globally:
To use the `pegjs` command, install PEG-Redux globally:
```console
$ npm install -g pegjs
$ npm install -g peg-redux
```
To use the JavaScript API, install PEG.js locally:
To use the JavaScript API, install PEG-Redux locally:
```console
$ npm install pegjs
$ npm install peg-redux
```
If you need both the `pegjs` command and the JavaScript API, install PEG.js both
ways.
If you need both the `pegjs` command and the JavaScript API, install PEG-Redux
both ways.
### Browser
[Download](https://pegjs.org/#download) the PEG.js library (regular or minified
version) or install it using Bower:
PEG-Redux works with bundlers such as [Browserify](http://browserify.org/), [Parcel](https://parceljs.org/) and [Webpack](https://webpack.js.org/).
```console
$ bower install pegjs
```
Simply `require()` and use the module like you would in Node.js. The one exception is that modules (either PEG-Redux or Javascript modules) are not currently supported in browser environments.
Bower and standalone builds have been discontinued in this fork. Getting started with Browserify will only take a few minutes, and give you a better developer experience.
Generating a Parser
-------------------
PEG.js generates parser from a grammar that describes expected input and can
PEG-Redux generates parser from a grammar that describes expected input and can
specify what the parser returns (using semantic actions on matched parts of the
input). Generated parser itself is a JavaScript object with a simple API.
@ -100,26 +100,20 @@ You can tweak the generated parser with several options:
`peg.generate`
* `--extra-options-file` — file with additional options (in JSON format) to
pass to `peg.generate`
* `--format` — format of the generated parser: `amd`, `commonjs`, `globals`,
`umd` (default: `commonjs`)
* `--optimize` — selects between optimizing the generated parser for parsing
speed (`speed`) or code size (`size`) (default: `speed`)
* `--plugin` — makes PEG.js use a specified plugin (can be specified multiple
* `--plugin` — makes PEG-Redux use a specified plugin (can be specified multiple
times)
* `--trace` — makes the parser trace its progress
### JavaScript API
In Node.js, require the PEG.js parser generator module:
Require the PEG-Redux parser generator module:
```javascript
var peg = require("pegjs");
var peg = require("peg-redux");
```
In browser, include the PEG.js library in your web page or application using the
`<script>` tag. If PEG.js detects an AMD loader, it will define itself as a
module, otherwise the API will be available in the `peg` global object.
To generate a parser, call the `peg.generate` method and pass your grammar as a
parameter:
@ -142,14 +136,7 @@ object to `peg.generate`. The following options are supported:
`false`)
* `dependencies` — parser dependencies, the value is an object which maps
variables used to access the dependencies in the parser to module IDs used
to load them; valid only when `format` is set to `"amd"`, `"commonjs"`, or
`"umd"` (default: `{}`)
* `exportVar` — name of a global variable into which the parser object is
assigned to when no module loader is detected; valid only when `format` is
set to `"globals"` or `"umd"` (default: `null`)
* `format` — format of the genreated parser (`"amd"`, `"bare"`, `"commonjs"`,
`"globals"`, or `"umd"`); valid only when `output` is set to `"source"`
(default: `"bare"`)
to load them.
* `optimize`— selects between optimizing the generated parser for parsing
speed (`"speed"`) or code size (`"size"`) (default: `"speed"`)
* `output` — if set to `"parser"`, the method will return generated parser
@ -503,25 +490,14 @@ environments:
* Safari
* Opera
However, please note that it is currently only actively tested in Node.js and Firefox. This will likely change in the future.
Development
-----------
* [Project website](https://pegjs.org/)
* [Wiki](https://github.com/pegjs/pegjs/wiki)
* [Source code](https://github.com/pegjs/pegjs)
* [Issue tracker](https://github.com/pegjs/pegjs/issues)
* [Google Group](http://groups.google.com/group/pegjs)
* [Twitter](http://twitter.com/peg_js)
PEG.js is developed by [David Majda](https://majda.cz/)
([@dmajda](http://twitter.com/dmajda)). The [Bower
package](https://github.com/pegjs/bower) is maintained by [Michel
Krämer](http://www.michel-kraemer.com/)
([@michelkraemer](https://twitter.com/michelkraemer)).
PEG-Redux is maintained by [Sven Slootweg (joepie91)](http://cryto.net/~joepie91).
The original PEG.js was developed by [David Majda](http://majda.cz/) ([@dmajda](http://twitter.com/dmajda)).
You are welcome to contribute code. Unless your contribution is really trivial
you should get in touch with me first — this can prevent wasted effort on both
sides. You can send code both as a patch or a GitHub pull request.
Note that PEG.js is still very much work in progress. There are no compatibility
guarantees until version 1.0.
sides. You can send code both as a patch or a pull request.

@ -1,42 +1,42 @@
"use strict";
let benchmarks = [
{
id: "json",
title: "JSON",
tests: [
{ file: "example1.json", title: "Example 1" },
{ file: "example2.json", title: "Example 2" },
{ file: "example3.json", title: "Example 3" },
{ file: "example4.json", title: "Example 4" },
{ file: "example5.json", title: "Example 5" }
]
},
{
id: "css",
title: "CSS",
tests: [
{ file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
{ file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
{ file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
{ file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
{ file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
// Contains syntax errors.
// { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
{ file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
{ file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
// Contains syntax errors.
// { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
{ file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
{ file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
{ file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
{ file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
{ file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
{ file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
{ file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
{ file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
]
}
{
id: "json",
title: "JSON",
tests: [
{ file: "example1.json", title: "Example 1" },
{ file: "example2.json", title: "Example 2" },
{ file: "example3.json", title: "Example 3" },
{ file: "example4.json", title: "Example 4" },
{ file: "example5.json", title: "Example 5" }
]
},
{
id: "css",
title: "CSS",
tests: [
{ file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
{ file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
{ file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
{ file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
{ file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
// Contains syntax errors.
// { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
{ file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
{ file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
// Contains syntax errors.
// { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
{ file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
{ file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
{ file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
{ file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
{ file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
{ file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
{ file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
{ file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
]
}
];
module.exports = benchmarks;

@ -6,22 +6,22 @@ let Runner = require("./runner.js");
let benchmarks = require("./benchmarks.js");
$("#run").click(() => {
// Results Table Manipulation
// Results Table Manipulation
let resultsTable = $("#results-table");
let resultsTable = $("#results-table");
function appendHeading(heading) {
resultsTable.append(
"<tr class='heading'><th colspan='4'>" + heading + "</th></tr>"
);
}
function appendHeading(heading) {
resultsTable.append(
"<tr class='heading'><th colspan='4'>" + heading + "</th></tr>"
);
}
function appendResult(klass, title, url, inputSize, parseTime) {
const KB = 1024;
const MS_IN_S = 1000;
function appendResult(klass, title, url, inputSize, parseTime) {
const KB = 1024;
const MS_IN_S = 1000;
resultsTable.append(
"<tr class='" + klass + "'>"
resultsTable.append(
"<tr class='" + klass + "'>"
+ "<td class='title'>"
+ (url !== null ? "<a href='" + url + "'>" : "")
+ title
@ -46,93 +46,94 @@ $("#run").click(() => {
+ "&nbsp;<span class='unit'>kB/s</span>"
+ "</td>"
+ "</tr>"
);
}
// Main
// Each input is parsed multiple times and the results are averaged. We
// do this for two reasons:
//
// 1. To warm up the interpreter (PEG.js-generated parsers will be
// most likely used repeatedly, so it makes sense to measure
// performance after warming up).
//
// 2. To minimize random errors.
let runCount = parseInt($("#run-count").val(), 10);
let options = {
cache: $("#cache").is(":checked"),
optimize: $("#optimize").val()
};
if (isNaN(runCount) || runCount <= 0) {
alert("Number of runs must be a positive integer.");
return;
}
Runner.run(benchmarks, runCount, options, {
readFile(file) {
return $.ajax({
type: "GET",
url: file,
dataType: "text",
async: false
}).responseText;
},
testStart() {
// Nothing to do.
},
testFinish(benchmark, test, inputSize, parseTime) {
appendResult(
"individual",
test.title,
benchmark.id + "/" + test.file,
inputSize,
parseTime
);
},
benchmarkStart(benchmark) {
appendHeading(benchmark.title);
},
benchmarkFinish(benchmark, inputSize, parseTime) {
appendResult(
"benchmark-total",
benchmark.title + " total",
null,
inputSize,
parseTime
);
},
start() {
$("#run-count, #cache, #run").attr("disabled", "disabled");
resultsTable.show();
$("#results-table tr").slice(1).remove();
},
finish(inputSize, parseTime) {
appendResult(
"total",
"Total",
null,
inputSize,
parseTime
);
$.scrollTo("max", { axis: "y", duration: 500 });
$("#run-count, #cache, #run").removeAttr("disabled");
}
});
);
}
// Main
// Each input is parsed multiple times and the results are averaged. We
// do this for two reasons:
//
// 1. To warm up the interpreter (PEG.js-generated parsers will be
// most likely used repeatedly, so it makes sense to measure
// performance after warming up).
//
// 2. To minimize random errors.
let runCount = parseInt($("#run-count").val(), 10);
let options = {
cache: $("#cache").is(":checked"),
optimize: $("#optimize").val()
};
if (isNaN(runCount) || runCount <= 0) {
// eslint-disable-next-line no-alert
alert("Number of runs must be a positive integer.");
return;
}
Runner.run(benchmarks, runCount, options, {
readFile(file) {
return $.ajax({
type: "GET",
url: file,
dataType: "text",
async: false
}).responseText;
},
testStart() {
// Nothing to do.
},
testFinish(benchmark, test, inputSize, parseTime) {
appendResult(
"individual",
test.title,
benchmark.id + "/" + test.file,
inputSize,
parseTime
);
},
benchmarkStart(benchmark) {
appendHeading(benchmark.title);
},
benchmarkFinish(benchmark, inputSize, parseTime) {
appendResult(
"benchmark-total",
benchmark.title + " total",
null,
inputSize,
parseTime
);
},
start() {
$("#run-count, #cache, #run").attr("disabled", "disabled");
resultsTable.show();
$("#results-table tr").slice(1).remove();
},
finish(inputSize, parseTime) {
appendResult(
"total",
"Total",
null,
inputSize,
parseTime
);
$.scrollTo("max", { axis: "y", duration: 500 });
$("#run-count, #cache, #run").removeAttr("disabled");
}
});
});
$(document).ready(() => {
$("#run").focus();
$("#run").focus();
});

@ -1,118 +1,116 @@
"use strict";
/* global setTimeout */
let peg = require("../lib/peg");
let Runner = {
run(benchmarks, runCount, options, callbacks) {
// Queue
let Q = {
functions: [],
add(f) {
this.functions.push(f);
},
run() {
if (this.functions.length > 0) {
this.functions.shift()();
// We can't use |arguments.callee| here because |this| would get
// messed-up in that case.
setTimeout(() => { Q.run(); }, 0);
}
}
};
// The benchmark itself is factored out into several functions (some of them
// generated), which are enqueued and run one by one using |setTimeout|. We
// do this for two reasons:
//
// 1. To avoid bowser mechanism for interrupting long-running scripts to
// kick-in (or at least to not kick-in that often).
//
// 2. To ensure progressive rendering of results in the browser (some
// browsers do not render at all when running JavaScript code).
//
// The enqueued functions share state, which is all stored in the properties
// of the |state| object.
let state = {};
function initialize() {
callbacks.start();
state.totalInputSize = 0;
state.totalParseTime = 0;
}
function benchmarkInitializer(benchmark) {
return function() {
callbacks.benchmarkStart(benchmark);
state.parser = peg.generate(
callbacks.readFile("../examples/" + benchmark.id + ".pegjs"),
options
);
state.benchmarkInputSize = 0;
state.benchmarkParseTime = 0;
};
}
function testRunner(benchmark, test) {
return function() {
callbacks.testStart(benchmark, test);
let input = callbacks.readFile(benchmark.id + "/" + test.file);
let parseTime = 0;
for (let i = 0; i < runCount; i++) {
let t = (new Date()).getTime();
state.parser.parse(input);
parseTime += (new Date()).getTime() - t;
}
let averageParseTime = parseTime / runCount;
callbacks.testFinish(benchmark, test, input.length, averageParseTime);
state.benchmarkInputSize += input.length;
state.benchmarkParseTime += averageParseTime;
};
}
function benchmarkFinalizer(benchmark) {
return function() {
callbacks.benchmarkFinish(
benchmark,
state.benchmarkInputSize,
state.benchmarkParseTime
);
state.totalInputSize += state.benchmarkInputSize;
state.totalParseTime += state.benchmarkParseTime;
};
}
function finalize() {
callbacks.finish(state.totalInputSize, state.totalParseTime);
}
// Main
Q.add(initialize);
benchmarks.forEach(benchmark => {
Q.add(benchmarkInitializer(benchmark));
benchmark.tests.forEach(test => {
Q.add(testRunner(benchmark, test));
});
Q.add(benchmarkFinalizer(benchmark));
});
Q.add(finalize);
Q.run();
}
run(benchmarks, runCount, options, callbacks) {
// Queue
let Q = {
functions: [],
add(f) {
this.functions.push(f);
},
run() {
if (this.functions.length > 0) {
this.functions.shift()();
// We can't use |arguments.callee| here because |this| would get
// messed-up in that case.
setTimeout(() => { Q.run(); }, 0);
}
}
};
// The benchmark itself is factored out into several functions (some of them
// generated), which are enqueued and run one by one using |setTimeout|. We
// do this for two reasons:
//
// 1. To avoid bowser mechanism for interrupting long-running scripts to
// kick-in (or at least to not kick-in that often).
//
// 2. To ensure progressive rendering of results in the browser (some
// browsers do not render at all when running JavaScript code).
//
// The enqueued functions share state, which is all stored in the properties
// of the |state| object.
let state = {};
function initialize() {
callbacks.start();
state.totalInputSize = 0;
state.totalParseTime = 0;
}
function benchmarkInitializer(benchmark) {
return function() {
callbacks.benchmarkStart(benchmark);
state.parser = peg.generate(
callbacks.readFile("../examples/" + benchmark.id + ".pegjs"),
options
);
state.benchmarkInputSize = 0;
state.benchmarkParseTime = 0;
};
}
function testRunner(benchmark, test) {
return function() {
callbacks.testStart(benchmark, test);
let input = callbacks.readFile(benchmark.id + "/" + test.file);
let parseTime = 0;
for (let i = 0; i < runCount; i++) {
let t = (new Date()).getTime();
state.parser.parse(input);
parseTime += (new Date()).getTime() - t;
}
let averageParseTime = parseTime / runCount;
callbacks.testFinish(benchmark, test, input.length, averageParseTime);
state.benchmarkInputSize += input.length;
state.benchmarkParseTime += averageParseTime;
};
}
function benchmarkFinalizer(benchmark) {
return function() {
callbacks.benchmarkFinish(
benchmark,
state.benchmarkInputSize,
state.benchmarkParseTime
);
state.totalInputSize += state.benchmarkInputSize;
state.totalParseTime += state.benchmarkParseTime;
};
}
function finalize() {
callbacks.finish(state.totalInputSize, state.totalParseTime);
}
// Main
Q.add(initialize);
benchmarks.forEach(benchmark => {
Q.add(benchmarkInitializer(benchmark));
benchmark.tests.forEach(test => {
Q.add(testRunner(benchmark, test));
});
Q.add(benchmarkFinalizer(benchmark));
});
Q.add(finalize);
Q.run();
}
};
module.exports = Runner;

@ -21,16 +21,17 @@ app.use(express.static(__dirname));
app.use("/examples", express.static(`${__dirname}/../examples`));
app.get("/bundle.js", (req, res) => {
let files = glob.sync(`${__dirname}/**/*.js`, {
ignore: `${__dirname}/vendor/**/*`
});
browserify(files)
.transform(babelify, { presets: "es2015", compact: false })
.bundle()
.pipe(res);
let files = glob.sync(`${__dirname}/**/*.js`, {
ignore: `${__dirname}/vendor/**/*`
});
browserify(files)
.transform(babelify, { presets: "env", compact: false })
.bundle()
.pipe(res);
});
app.listen(8000, () => {
console.log("Benchmark server running at http://localhost:8000...");
// eslint-disable-next-line no-console
console.log("Benchmark server running at http://localhost:8000...");
});

@ -0,0 +1,14 @@
"use strict";
const fs = require("fs");
const path = require("path");
const util = require("util");
const generate = require("../").generate;
const parser = require("../lib/parser");
let grammar = fs.readFileSync(path.join(__dirname, "test.pegjs"), { encoding: "utf-8" });
// let parseResult = parser.parse(grammar);
let parseResult = generate(grammar);
console.log(util.inspect(parseResult, { depth: null, colors: true }));

@ -0,0 +1,5 @@
import Foo from "./util.pegjs"
import { CommaDelimited as DelimitedNumber } from "./delimited-number"
TopLevelRule
= "hello"

@ -1,108 +1,21 @@
"use strict";
/* eslint-env node */
let babelify = require("babelify");
let browserify = require("browserify");
let buffer = require("vinyl-buffer");
let del = require("del");
let eslint = require("gulp-eslint");
let gulp = require("gulp");
let header = require("gulp-header");
let mocha = require("gulp-mocha");
let package_ = require("./package");
let peg = require("./lib/peg");
let rename = require("gulp-rename");
let runSequence = require("run-sequence");
let source = require("vinyl-source-stream");
let spawn = require("child_process").spawn;
let transform = require("gulp-transform");
let uglify = require("gulp-uglify");
const HEADER = [
"// PEG.js " + package_.version,
"//",
"// https://pegjs.org/",
"//",
"// Copyright (c) 2010-2016 David Majda",
"// Licensed under the MIT License.",
""
].map(line => `${line}\n`).join("");
const JS_FILES = [
"lib/**/*.js",
"!lib/parser.js",
"test/**/*.js",
"test/server",
"!test/vendor/**/*",
"benchmark/**/*.js",
"benchmark/run",
"benchmark/server",
"!benchmark/vendor/**/*",
"bin/pegjs",
"gulpfile.js"
];
const TEST_FILES = [
"test/**/*.js",
"!test/vendor/**/*"
];
function generate(contents) {
return peg.generate(contents.toString(), {
output: "source",
format: "commonjs"
});
return peg.generate(contents.toString(), {
output: "source",
format: "commonjs"
});
}
// Run ESLint on all JavaScript files.
gulp.task("lint", () =>
gulp.src(JS_FILES)
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
);
// Run tests.
gulp.task("test", () =>
gulp.src(TEST_FILES, { read: false })
.pipe(mocha())
);
// Run benchmarks.
gulp.task("benchmark", () =>
spawn("benchmark/run", { stdio: "inherit" })
);
// Create the browser build.
gulp.task("browser:build", () =>
browserify("lib/peg.js", { standalone: "peg" })
.transform(babelify, { presets: "es2015", compact: false })
.bundle()
.pipe(source("peg.js"))
.pipe(header(HEADER))
.pipe(gulp.dest("browser"))
.pipe(rename({ suffix: ".min" }))
.pipe(buffer())
.pipe(uglify())
.pipe(header(HEADER))
.pipe(gulp.dest("browser"))
);
// Delete the browser build.
gulp.task("browser:clean", () =>
del("browser")
);
// Generate the grammar parser.
gulp.task("parser", () =>
gulp.src("src/parser.pegjs")
.pipe(transform(generate))
.pipe(rename({ extname: ".js" }))
.pipe(gulp.dest("lib"))
);
// Default task.
gulp.task("default", cb =>
runSequence("lint", "test", cb)
gulp.src("src/parser.pegjs")
.pipe(transform("utf8", generate))
.pipe(rename({ extname: ".js" }))
.pipe(gulp.dest("lib"))
);

@ -4,73 +4,73 @@ let visitor = require("./visitor");
// AST utilities.
let asts = {
findRule(ast, name) {
for (let i = 0; i < ast.rules.length; i++) {
if (ast.rules[i].name === name) {
return ast.rules[i];
}
}
return undefined;
},
indexOfRule(ast, name) {
for (let i = 0; i < ast.rules.length; i++) {
if (ast.rules[i].name === name) {
return i;
}
}
return -1;
},
alwaysConsumesOnSuccess(ast, node) {
function consumesTrue() { return true; }
function consumesFalse() { return false; }
function consumesExpression(node) {
return consumes(node.expression);
}
let consumes = visitor.build({
rule: consumesExpression,
named: consumesExpression,
choice(node) {
return node.alternatives.every(consumes);
},
action: consumesExpression,
sequence(node) {
return node.elements.some(consumes);
},
labeled: consumesExpression,
text: consumesExpression,
simple_and: consumesFalse,
simple_not: consumesFalse,
optional: consumesFalse,
zero_or_more: consumesFalse,
one_or_more: consumesExpression,
group: consumesExpression,
semantic_and: consumesFalse,
semantic_not: consumesFalse,
rule_ref(node) {
return consumes(asts.findRule(ast, node.name));
},
literal(node) {
return node.value !== "";
},
class: consumesTrue,
any: consumesTrue
});
return consumes(node);
}
findRule(ast, name) {
for (let i = 0; i < ast.rules.length; i++) {
if (ast.rules[i].name === name) {
return ast.rules[i];
}
}
return undefined;
},
indexOfRule(ast, name) {
for (let i = 0; i < ast.rules.length; i++) {
if (ast.rules[i].name === name) {
return i;
}
}
return -1;
},
alwaysConsumesOnSuccess(ast, node) {
function consumesTrue() { return true; }
function consumesFalse() { return false; }
function consumesExpression(node) {
return consumes(node.expression);
}
let consumes = visitor.build({
rule: consumesExpression,
named: consumesExpression,
choice(node) {
return node.alternatives.every(consumes);
},
action: consumesExpression,
sequence(node) {
return node.elements.some(consumes);
},
labeled: consumesExpression,
text: consumesExpression,
simple_and: consumesFalse,
simple_not: consumesFalse,
optional: consumesFalse,
zero_or_more: consumesFalse,
one_or_more: consumesExpression,
group: consumesExpression,
semantic_and: consumesFalse,
semantic_not: consumesFalse,
rule_ref(node) {
return consumes(asts.findRule(ast, node.name));
},
literal(node) {
return node.value !== "";
},
class: consumesTrue,
any: consumesTrue
});
return consumes(node);
}
};
module.exports = asts;

@ -11,81 +11,68 @@ let reportUndefinedRules = require("./passes/report-undefined-rules");
let visitor = require("./visitor");
function processOptions(options, defaults) {
let processedOptions = {};
Object.keys(options).forEach(name => {
processedOptions[name] = options[name];
});
Object.keys(defaults).forEach(name => {
if (!Object.prototype.hasOwnProperty.call(processedOptions, name)) {
processedOptions[name] = defaults[name];
}
});
return processedOptions;
return Object.assign({}, defaults, options);
}
let compiler = {
// AST node visitor builder. Useful mainly for plugins which manipulate the
// AST.
visitor: visitor,
// Compiler passes.
//
// Each pass is a function that is passed the AST. It can perform checks on it
// or modify it as needed. If the pass encounters a semantic error, it throws
// |peg.GrammarError|.
passes: {
check: {
reportUndefinedRules: reportUndefinedRules,
reportDuplicateRules: reportDuplicateRules,
reportDuplicateLabels: reportDuplicateLabels,
reportInfiniteRecursion: reportInfiniteRecursion,
reportInfiniteRepetition: reportInfiniteRepetition
},
transform: {
removeProxyRules: removeProxyRules
},
generate: {
generateBytecode: generateBytecode,
generateJS: generateJS
}
},
// AST node visitor builder. Useful mainly for plugins which manipulate the
// AST.
visitor: visitor,
// Generates a parser from a specified grammar AST. Throws |peg.GrammarError|
// if the AST contains a semantic error. Note that not all errors are detected
// during the generation and some may protrude to the generated parser and
// cause its malfunction.
compile(ast, passes, options) {
options = options !== undefined ? options : {};
// Compiler passes.
//
// Each pass is a function that is passed the AST. It can perform checks on it
// or modify it as needed. If the pass encounters a semantic error, it throws
// |peg.GrammarError|.
passes: {
check: {
reportUndefinedRules: reportUndefinedRules,
reportDuplicateRules: reportDuplicateRules,
reportDuplicateLabels: reportDuplicateLabels,
reportInfiniteRecursion: reportInfiniteRecursion,
reportInfiniteRepetition: reportInfiniteRepetition
},
transform: {
removeProxyRules: removeProxyRules
},
generate: {
generateBytecode: generateBytecode,
generateJS: generateJS
}
},
options = processOptions(options, {
allowedStartRules: [ast.rules[0].name],
cache: false,
dependencies: {},
exportVar: null,
format: "bare",
optimize: "speed",
output: "parser",
trace: false
});
// Generates a parser from a specified grammar AST. Throws |peg.GrammarError|
// if the AST contains a semantic error. Note that not all errors are detected
// during the generation and some may protrude to the generated parser and
// cause its malfunction.
compile(ast, passes, options = {}) {
let processedOptions = processOptions(options, {
allowedStartRules: [ast.rules[0].name],
cache: false,
dependencies: {},
exportVar: null,
format: "bare",
optimize: "speed",
output: "parser",
trace: false
});
Object.keys(passes).forEach(stage => {
passes[stage].forEach(p => { p(ast, options); });
});
Object.values(passes).forEach((stagePasses) => {
stagePasses.forEach(pass => { pass(ast, processedOptions); });
});
switch (options.output) {
case "parser":
return eval(ast.code);
switch (processedOptions.output) {
case "parser":
return eval(ast.code);
case "source":
return ast.code;
case "source":
return ast.code;
default:
throw new Error("Invalid output format: " + options.output + ".");
}
}
// FIXME: Move to Validatem code at entrypoint
default:
throw new Error("Invalid output format: " + processedOptions.output + ".");
}
}
};
module.exports = compiler;

@ -1,54 +1,59 @@
"use strict";
function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
function hex(character) {
return character
.charCodeAt(0)
.toString(16)
.toUpperCase();
}
// JavaScript code generation helpers.
let js = {
stringEscape(s) {
// ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
// literal except for the closing quote character, backslash, carriage
// return, line separator, paragraph separator, and line feed. Any character
// may appear in the form of an escape sequence.
//
// For portability, we also escape all control and non-ASCII characters.
return s
.replace(/\\/g, "\\\\") // backslash
.replace(/"/g, "\\\"") // closing double quote
.replace(/\0/g, "\\0") // null
.replace(/\x08/g, "\\b") // backspace
.replace(/\t/g, "\\t") // horizontal tab
.replace(/\n/g, "\\n") // line feed
.replace(/\v/g, "\\v") // vertical tab
.replace(/\f/g, "\\f") // form feed
.replace(/\r/g, "\\r") // carriage return
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
},
stringEscape(s) {
// ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
// literal except for the closing quote character, backslash, carriage
// return, line separator, paragraph separator, and line feed. Any character
// may appear in the form of an escape sequence.
//
// For portability, we also escape all control and non-ASCII characters.
return s
.replace(/\\/g, "\\\\") // backslash
.replace(/"/g, "\\\"") // closing double quote
.replace(/\0/g, "\\0") // null
.replace(/\x08/g, "\\b") // backspace
.replace(/\t/g, "\\t") // horizontal tab
.replace(/\n/g, "\\n") // line feed
.replace(/\v/g, "\\v") // vertical tab
.replace(/\f/g, "\\f") // form feed
.replace(/\r/g, "\\r") // carriage return
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
},
regexpClassEscape(s) {
// Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
//
// For portability, we also escape all control and non-ASCII characters.
return s
.replace(/\\/g, "\\\\") // backslash
.replace(/\//g, "\\/") // closing slash
.replace(/]/g, "\\]") // closing bracket
.replace(/\^/g, "\\^") // caret
.replace(/-/g, "\\-") // dash
.replace(/\0/g, "\\0") // null
.replace(/\x08/g, "\\b") // backspace
.replace(/\t/g, "\\t") // horizontal tab
.replace(/\n/g, "\\n") // line feed
.replace(/\v/g, "\\v") // vertical tab
.replace(/\f/g, "\\f") // form feed
.replace(/\r/g, "\\r") // carriage return
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
}
regexpClassEscape(s) {
// Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
//
// For portability, we also escape all control and non-ASCII characters.
return s
.replace(/\\/g, "\\\\") // backslash
.replace(/\//g, "\\/") // closing slash
.replace(/]/g, "\\]") // closing bracket
.replace(/\^/g, "\\^") // caret
.replace(/-/g, "\\-") // dash
.replace(/\0/g, "\\0") // null
.replace(/\x08/g, "\\b") // backspace
.replace(/\t/g, "\\t") // horizontal tab
.replace(/\n/g, "\\n") // line feed
.replace(/\v/g, "\\v") // vertical tab
.replace(/\f/g, "\\f") // form feed
.replace(/\r/g, "\\r") // carriage return
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
}
};
module.exports = js;

@ -2,53 +2,53 @@
// Bytecode instruction opcodes.
let opcodes = {
// Stack Manipulation
// Stack Manipulation
PUSH: 0, // PUSH c
PUSH_UNDEFINED: 1, // PUSH_UNDEFINED
PUSH_NULL: 2, // PUSH_NULL
PUSH_FAILED: 3, // PUSH_FAILED
PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY
PUSH_CURR_POS: 5, // PUSH_CURR_POS
POP: 6, // POP
POP_CURR_POS: 7, // POP_CURR_POS
POP_N: 8, // POP_N n
NIP: 9, // NIP
APPEND: 10, // APPEND
WRAP: 11, // WRAP n
TEXT: 12, // TEXT
PUSH: 0, // PUSH c
PUSH_UNDEFINED: 1, // PUSH_UNDEFINED
PUSH_NULL: 2, // PUSH_NULL
PUSH_FAILED: 3, // PUSH_FAILED
PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY
PUSH_CURR_POS: 5, // PUSH_CURR_POS
POP: 6, // POP
POP_CURR_POS: 7, // POP_CURR_POS
POP_N: 8, // POP_N n
NIP: 9, // NIP
APPEND: 10, // APPEND
WRAP: 11, // WRAP n
TEXT: 12, // TEXT
// Conditions and Loops
// Conditions and Loops
IF: 13, // IF t, f
IF_ERROR: 14, // IF_ERROR t, f
IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f
WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b
IF: 13, // IF t, f
IF_ERROR: 14, // IF_ERROR t, f
IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f
WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b
// Matching
// Matching
MATCH_ANY: 17, // MATCH_ANY a, f, ...
MATCH_STRING: 18, // MATCH_STRING s, a, f, ...
MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...
MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...
ACCEPT_N: 21, // ACCEPT_N n
ACCEPT_STRING: 22, // ACCEPT_STRING s
FAIL: 23, // FAIL e
MATCH_ANY: 17, // MATCH_ANY a, f, ...
MATCH_STRING: 18, // MATCH_STRING s, a, f, ...
MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...
MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...
ACCEPT_N: 21, // ACCEPT_N n
ACCEPT_STRING: 22, // ACCEPT_STRING s
FAIL: 23, // FAIL e
// Calls
// Calls
LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p
UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS
CALL: 26, // CALL f, n, pc, p1, p2, ..., pN
LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p
UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS
CALL: 26, // CALL f, n, pc, p1, p2, ..., pN
// Rules
// Rules
RULE: 27, // RULE r
RULE: 27, // RULE r
// Failure Reporting
// Failure Reporting
SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
};
module.exports = opcodes;

@ -188,431 +188,431 @@ let visitor = require("../visitor");
//
// silentFails--;
function generateBytecode(ast) {
let consts = [];
function addConst(value) {
let index = consts.indexOf(value);
return index === -1 ? consts.push(value) - 1 : index;
}
function addFunctionConst(params, code) {
return addConst(
"function(" + params.join(", ") + ") {" + code + "}"
);
}
function cloneEnv(env) {
let clone = {};
Object.keys(env).forEach(name => {
clone[name] = env[name];
});
return clone;
}
function buildSequence() {
return Array.prototype.concat.apply([], arguments);
}
function buildCondition(condCode, thenCode, elseCode) {
return condCode.concat(
[thenCode.length, elseCode.length],
thenCode,
elseCode
);
}
function buildLoop(condCode, bodyCode) {
return condCode.concat([bodyCode.length], bodyCode);
}
function buildCall(functionIndex, delta, env, sp) {
let params = Object.keys(env).map(name => sp - env[name]);
return [op.CALL, functionIndex, delta, params.length].concat(params);
}
function buildSimplePredicate(expression, negative, context) {
return buildSequence(
[op.PUSH_CURR_POS],
[op.SILENT_FAILS_ON],
generate(expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
}),
[op.SILENT_FAILS_OFF],
buildCondition(
[negative ? op.IF_ERROR : op.IF_NOT_ERROR],
buildSequence(
[op.POP],
[negative ? op.POP : op.POP_CURR_POS],
[op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
[negative ? op.POP_CURR_POS : op.POP],
[op.PUSH_FAILED]
)
)
);
}
function buildSemanticPredicate(code, negative, context) {
let functionIndex = addFunctionConst(Object.keys(context.env), code);
return buildSequence(
[op.UPDATE_SAVED_POS],
buildCall(functionIndex, 0, context.env, context.sp),
buildCondition(
[op.IF],
buildSequence(
[op.POP],
negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]
)
)
);
}
function buildAppendLoop(expressionCode) {
return buildLoop(
[op.WHILE_NOT_ERROR],
buildSequence([op.APPEND], expressionCode)
);
}
let generate = visitor.build({
grammar(node) {
node.rules.forEach(generate);
node.consts = consts;
},
rule(node) {
node.bytecode = generate(node.expression, {
sp: -1, // stack pointer
env: { }, // mapping of label names to stack positions
action: null // action nodes pass themselves to children here
});
},
named(node, context) {
let nameIndex = addConst(
"peg$otherExpectation(\"" + js.stringEscape(node.name) + "\")"
);
// The code generated below is slightly suboptimal because |FAIL| pushes
// to the stack, so we need to stick a |POP| in front of it. We lack a
// dedicated instruction that would just report the failure and not touch
// the stack.
return buildSequence(
[op.SILENT_FAILS_ON],
generate(node.expression, context),
[op.SILENT_FAILS_OFF],
buildCondition([op.IF_ERROR], [op.FAIL, nameIndex], [])
);
},
choice(node, context) {
function buildAlternativesCode(alternatives, context) {
return buildSequence(
generate(alternatives[0], {
sp: context.sp,
env: cloneEnv(context.env),
action: null
}),
alternatives.length > 1
? buildCondition(
[op.IF_ERROR],
buildSequence(
[op.POP],
buildAlternativesCode(alternatives.slice(1), context)
),
[]
)
: []
);
}
return buildAlternativesCode(node.alternatives, context);
},
action(node, context) {
let env = cloneEnv(context.env);
let emitCall = node.expression.type !== "sequence"
let consts = [];
function addConst(value) {
let index = consts.indexOf(value);
return index === -1 ? consts.push(value) - 1 : index;
}
function addFunctionConst(params, code) {
return addConst(
"function(" + params.join(", ") + ") {" + code + "}"
);
}
function cloneEnv(env) {
let clone = {};
Object.keys(env).forEach(name => {
clone[name] = env[name];
});
return clone;
}
function buildSequence() {
return Array.prototype.concat.apply([], arguments);
}
function buildCondition(condCode, thenCode, elseCode) {
return condCode.concat(
[thenCode.length, elseCode.length],
thenCode,
elseCode
);
}
function buildLoop(condCode, bodyCode) {
return condCode.concat([bodyCode.length], bodyCode);
}
function buildCall(functionIndex, delta, env, sp) {
let params = Object.keys(env).map(name => sp - env[name]);
return [op.CALL, functionIndex, delta, params.length].concat(params);
}
function buildSimplePredicate(expression, negative, context) {
return buildSequence(
[op.PUSH_CURR_POS],
[op.SILENT_FAILS_ON],
generate(expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
}),
[op.SILENT_FAILS_OFF],
buildCondition(
[negative ? op.IF_ERROR : op.IF_NOT_ERROR],
buildSequence(
[op.POP],
[negative ? op.POP : op.POP_CURR_POS],
[op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
[negative ? op.POP_CURR_POS : op.POP],
[op.PUSH_FAILED]
)
)
);
}
function buildSemanticPredicate(code, negative, context) {
let functionIndex = addFunctionConst(Object.keys(context.env), code);
return buildSequence(
[op.UPDATE_SAVED_POS],
buildCall(functionIndex, 0, context.env, context.sp),
buildCondition(
[op.IF],
buildSequence(
[op.POP],
negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]
)
)
);
}
function buildAppendLoop(expressionCode) {
return buildLoop(
[op.WHILE_NOT_ERROR],
buildSequence([op.APPEND], expressionCode)
);
}
let generate = visitor.build({
grammar(node) {
node.rules.forEach(generate);
node.consts = consts;
},
rule(node) {
node.bytecode = generate(node.expression, {
sp: -1, // stack pointer
env: { }, // mapping of label names to stack positions
action: null // action nodes pass themselves to children here
});
},
named(node, context) {
let nameIndex = addConst(
"peg$otherExpectation(\"" + js.stringEscape(node.name) + "\")"
);
// The code generated below is slightly suboptimal because |FAIL| pushes
// to the stack, so we need to stick a |POP| in front of it. We lack a
// dedicated instruction that would just report the failure and not touch
// the stack.
return buildSequence(
[op.SILENT_FAILS_ON],
generate(node.expression, context),
[op.SILENT_FAILS_OFF],
buildCondition([op.IF_ERROR], [op.FAIL, nameIndex], [])
);
},
choice(node, context) {
function buildAlternativesCode(alternatives, context) {
return buildSequence(
generate(alternatives[0], {
sp: context.sp,
env: cloneEnv(context.env),
action: null
}),
alternatives.length > 1
? buildCondition(
[op.IF_ERROR],
buildSequence(
[op.POP],
buildAlternativesCode(alternatives.slice(1), context)
),
[]
)
: []
);
}
return buildAlternativesCode(node.alternatives, context);
},
action(node, context) {
let env = cloneEnv(context.env);
let emitCall = node.expression.type !== "sequence"
|| node.expression.elements.length === 0;
let expressionCode = generate(node.expression, {
sp: context.sp + (emitCall ? 1 : 0),
env: env,
action: node
});
let functionIndex = addFunctionConst(Object.keys(env), node.code);
return emitCall
? buildSequence(
[op.PUSH_CURR_POS],
expressionCode,
buildCondition(
[op.IF_NOT_ERROR],
buildSequence(
[op.LOAD_SAVED_POS, 1],
buildCall(functionIndex, 1, env, context.sp + 2)
),
[]
),
[op.NIP]
)
: expressionCode;
},
sequence(node, context) {
function buildElementsCode(elements, context) {
if (elements.length > 0) {
let processedCount = node.elements.length - elements.slice(1).length;
return buildSequence(
generate(elements[0], {
sp: context.sp,
env: context.env,
action: null
}),
buildCondition(
[op.IF_NOT_ERROR],
buildElementsCode(elements.slice(1), {
sp: context.sp + 1,
env: context.env,
action: context.action
}),
buildSequence(
processedCount > 1 ? [op.POP_N, processedCount] : [op.POP],
[op.POP_CURR_POS],
[op.PUSH_FAILED]
)
)
);
} else {
if (context.action) {
let functionIndex = addFunctionConst(
Object.keys(context.env),
context.action.code
);
return buildSequence(
[op.LOAD_SAVED_POS, node.elements.length],
buildCall(
functionIndex,
node.elements.length,
context.env,
context.sp
),
[op.NIP]
);
} else {
return buildSequence([op.WRAP, node.elements.length], [op.NIP]);
}
}
}
return buildSequence(
[op.PUSH_CURR_POS],
buildElementsCode(node.elements, {
sp: context.sp + 1,
env: context.env,
action: context.action
})
);
},
labeled(node, context) {
let env = cloneEnv(context.env);
context.env[node.label] = context.sp + 1;
return generate(node.expression, {
sp: context.sp,
env: env,
action: null
});
},
text(node, context) {
return buildSequence(
[op.PUSH_CURR_POS],
generate(node.expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
}),
buildCondition(
[op.IF_NOT_ERROR],
buildSequence([op.POP], [op.TEXT]),
[op.NIP]
)
);
},
simple_and(node, context) {
return buildSimplePredicate(node.expression, false, context);
},
simple_not(node, context) {
return buildSimplePredicate(node.expression, true, context);
},
optional(node, context) {
return buildSequence(
generate(node.expression, {
sp: context.sp,
env: cloneEnv(context.env),
action: null
}),
buildCondition(
[op.IF_ERROR],
buildSequence([op.POP], [op.PUSH_NULL]),
[]
)
);
},
zero_or_more(node, context) {
let expressionCode = generate(node.expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
});
return buildSequence(
[op.PUSH_EMPTY_ARRAY],
expressionCode,
buildAppendLoop(expressionCode),
[op.POP]
);
},
one_or_more(node, context) {
let expressionCode = generate(node.expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
});
return buildSequence(
[op.PUSH_EMPTY_ARRAY],
expressionCode,
buildCondition(
[op.IF_NOT_ERROR],
buildSequence(buildAppendLoop(expressionCode), [op.POP]),
buildSequence([op.POP], [op.POP], [op.PUSH_FAILED])
)
);
},
group(node, context) {
return generate(node.expression, {
sp: context.sp,
env: cloneEnv(context.env),
action: null
});
},
semantic_and(node, context) {
return buildSemanticPredicate(node.code, false, context);
},
semantic_not(node, context) {
return buildSemanticPredicate(node.code, true, context);
},
rule_ref(node) {
return [op.RULE, asts.indexOfRule(ast, node.name)];
},
literal(node) {
if (node.value.length > 0) {
let stringIndex = addConst("\""
let expressionCode = generate(node.expression, {
sp: context.sp + (emitCall ? 1 : 0),
env: env,
action: node
});
let functionIndex = addFunctionConst(Object.keys(env), node.code);
return emitCall
? buildSequence(
[op.PUSH_CURR_POS],
expressionCode,
buildCondition(
[op.IF_NOT_ERROR],
buildSequence(
[op.LOAD_SAVED_POS, 1],
buildCall(functionIndex, 1, env, context.sp + 2)
),
[]
),
[op.NIP]
)
: expressionCode;
},
sequence(node, context) {
function buildElementsCode(elements, context) {
if (elements.length > 0) {
let processedCount = node.elements.length - elements.slice(1).length;
return buildSequence(
generate(elements[0], {
sp: context.sp,
env: context.env,
action: null
}),
buildCondition(
[op.IF_NOT_ERROR],
buildElementsCode(elements.slice(1), {
sp: context.sp + 1,
env: context.env,
action: context.action
}),
buildSequence(
processedCount > 1 ? [op.POP_N, processedCount] : [op.POP],
[op.POP_CURR_POS],
[op.PUSH_FAILED]
)
)
);
} else {
if (context.action) {
let functionIndex = addFunctionConst(
Object.keys(context.env),
context.action.code
);
return buildSequence(
[op.LOAD_SAVED_POS, node.elements.length],
buildCall(
functionIndex,
node.elements.length,
context.env,
context.sp
),
[op.NIP]
);
} else {
return buildSequence([op.WRAP, node.elements.length], [op.NIP]);
}
}
}
return buildSequence(
[op.PUSH_CURR_POS],
buildElementsCode(node.elements, {
sp: context.sp + 1,
env: context.env,
action: context.action
})
);
},
labeled(node, context) {
let env = cloneEnv(context.env);
context.env[node.label] = context.sp + 1;
return generate(node.expression, {
sp: context.sp,
env: env,
action: null
});
},
text(node, context) {
return buildSequence(
[op.PUSH_CURR_POS],
generate(node.expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
}),
buildCondition(
[op.IF_NOT_ERROR],
buildSequence([op.POP], [op.TEXT]),
[op.NIP]
)
);
},
simple_and(node, context) {
return buildSimplePredicate(node.expression, false, context);
},
simple_not(node, context) {
return buildSimplePredicate(node.expression, true, context);
},
optional(node, context) {
return buildSequence(
generate(node.expression, {
sp: context.sp,
env: cloneEnv(context.env),
action: null
}),
buildCondition(
[op.IF_ERROR],
buildSequence([op.POP], [op.PUSH_NULL]),
[]
)
);
},
zero_or_more(node, context) {
let expressionCode = generate(node.expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
});
return buildSequence(
[op.PUSH_EMPTY_ARRAY],
expressionCode,
buildAppendLoop(expressionCode),
[op.POP]
);
},
one_or_more(node, context) {
let expressionCode = generate(node.expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
});
return buildSequence(
[op.PUSH_EMPTY_ARRAY],
expressionCode,
buildCondition(
[op.IF_NOT_ERROR],
buildSequence(buildAppendLoop(expressionCode), [op.POP]),
buildSequence([op.POP], [op.POP], [op.PUSH_FAILED])
)
);
},
group(node, context) {
return generate(node.expression, {
sp: context.sp,
env: cloneEnv(context.env),
action: null
});
},
semantic_and(node, context) {
return buildSemanticPredicate(node.code, false, context);
},
semantic_not(node, context) {
return buildSemanticPredicate(node.code, true, context);
},
rule_ref(node) {
return [op.RULE, asts.indexOfRule(ast, node.name)];
},
literal(node) {
if (node.value.length > 0) {
let stringIndex = addConst("\""
+ js.stringEscape(
node.ignoreCase ? node.value.toLowerCase() : node.value
)
node.ignoreCase ? node.value.toLowerCase() : node.value
)
+ "\""
);
let expectedIndex = addConst(
"peg$literalExpectation("
);
let expectedIndex = addConst(
"peg$literalExpectation("
+ "\"" + js.stringEscape(node.value) + "\", "
+ node.ignoreCase
+ ")"
);
// For case-sensitive strings the value must match the beginning of the
// remaining input exactly. As a result, we can use |ACCEPT_STRING| and
// save one |substr| call that would be needed if we used |ACCEPT_N|.
return buildCondition(
node.ignoreCase
? [op.MATCH_STRING_IC, stringIndex]
: [op.MATCH_STRING, stringIndex],
node.ignoreCase
? [op.ACCEPT_N, node.value.length]
: [op.ACCEPT_STRING, stringIndex],
[op.FAIL, expectedIndex]
);
} else {
let stringIndex = addConst("\"\"");
return [op.PUSH, stringIndex];
}
},
class(node) {
let regexp = "/^["
);
// For case-sensitive strings the value must match the beginning of the
// remaining input exactly. As a result, we can use |ACCEPT_STRING| and
// save one |substr| call that would be needed if we used |ACCEPT_N|.
return buildCondition(
node.ignoreCase
? [op.MATCH_STRING_IC, stringIndex]
: [op.MATCH_STRING, stringIndex],
node.ignoreCase
? [op.ACCEPT_N, node.value.length]
: [op.ACCEPT_STRING, stringIndex],
[op.FAIL, expectedIndex]
);
} else {
let stringIndex = addConst("\"\"");
return [op.PUSH, stringIndex];
}
},
class(node) {
let regexp = "/^["
+ (node.inverted ? "^" : "")
+ node.parts.map(part =>
Array.isArray(part)
? js.regexpClassEscape(part[0])
Array.isArray(part)
? js.regexpClassEscape(part[0])
+ "-"
+ js.regexpClassEscape(part[1])
: js.regexpClassEscape(part)
).join("")
: js.regexpClassEscape(part)
).join("")
+ "]/" + (node.ignoreCase ? "i" : "");
let parts = "["
let parts = "["
+ node.parts.map(part =>
Array.isArray(part)
? "[\"" + js.stringEscape(part[0]) + "\", \"" + js.stringEscape(part[1]) + "\"]"
: "\"" + js.stringEscape(part) + "\""
).join(", ")
Array.isArray(part)
? "[\"" + js.stringEscape(part[0]) + "\", \"" + js.stringEscape(part[1]) + "\"]"
: "\"" + js.stringEscape(part) + "\""
).join(", ")
+ "]";
let regexpIndex = addConst(regexp);
let expectedIndex = addConst(
"peg$classExpectation("
let regexpIndex = addConst(regexp);
let expectedIndex = addConst(
"peg$classExpectation("
+ parts + ", "
+ node.inverted + ", "
+ node.ignoreCase
+ ")"
);
return buildCondition(
[op.MATCH_REGEXP, regexpIndex],
[op.ACCEPT_N, 1],
[op.FAIL, expectedIndex]
);
},
any() {
let expectedIndex = addConst("peg$anyExpectation()");
return buildCondition(
[op.MATCH_ANY],
[op.ACCEPT_N, 1],
[op.FAIL, expectedIndex]
);
}
});
generate(ast);
);
return buildCondition(
[op.MATCH_REGEXP, regexpIndex],
[op.ACCEPT_N, 1],
[op.FAIL, expectedIndex]
);
},
any() {
let expectedIndex = addConst("peg$anyExpectation()");
return buildCondition(
[op.MATCH_ANY],
[op.ACCEPT_N, 1],
[op.FAIL, expectedIndex]
);
}
});
generate(ast);
}
module.exports = generateBytecode;

File diff suppressed because it is too large Load Diff

@ -4,36 +4,36 @@ let visitor = require("../visitor");
// Removes proxy rules -- that is, rules that only delegate to other rule.
function removeProxyRules(ast, options) {
function isProxyRule(node) {
return node.type === "rule" && node.expression.type === "rule_ref";
}
function replaceRuleRefs(ast, from, to) {
let replace = visitor.build({
rule_ref(node) {
if (node.name === from) {
node.name = to;
}
}
});
replace(ast);
}
let indices = [];
ast.rules.forEach((rule, i) => {
if (isProxyRule(rule)) {
replaceRuleRefs(ast, rule.name, rule.expression.name);
if (options.allowedStartRules.indexOf(rule.name) === -1) {
indices.push(i);
}
}
});
indices.reverse();
indices.forEach(i => { ast.rules.splice(i, 1); });
function isProxyRule(node) {
return node.type === "rule" && node.expression.type === "rule_ref";
}
function replaceRuleRefs(ast, from, to) {
let replace = visitor.build({
rule_ref(node) {
if (node.name === from) {
node.name = to;
}
}
});
replace(ast);
}
let indices = [];
ast.rules.forEach((rule, i) => {
if (isProxyRule(rule)) {
replaceRuleRefs(ast, rule.name, rule.expression.name);
if (options.allowedStartRules.indexOf(rule.name) === -1) {
indices.push(i);
}
}
});
indices.reverse();
indices.forEach(i => { ast.rules.splice(i, 1); });
}
module.exports = removeProxyRules;

@ -5,58 +5,58 @@ let visitor = require("../visitor");
// Checks that each label is defined only once within each scope.
function reportDuplicateLabels(ast) {
function cloneEnv(env) {
let clone = {};
function cloneEnv(env) {
let clone = {};
Object.keys(env).forEach(name => {
clone[name] = env[name];
});
Object.keys(env).forEach(name => {
clone[name] = env[name];
});
return clone;
}
return clone;
}
function checkExpressionWithClonedEnv(node, env) {
check(node.expression, cloneEnv(env));
}
function checkExpressionWithClonedEnv(node, env) {
check(node.expression, cloneEnv(env));
}
let check = visitor.build({
rule(node) {
check(node.expression, { });
},
let check = visitor.build({
rule(node) {
check(node.expression, { });
},
choice(node, env) {
node.alternatives.forEach(alternative => {
check(alternative, cloneEnv(env));
});
},
choice(node, env) {
node.alternatives.forEach(alternative => {
check(alternative, cloneEnv(env));
});
},
action: checkExpressionWithClonedEnv,
action: checkExpressionWithClonedEnv,
labeled(node, env) {
if (Object.prototype.hasOwnProperty.call(env, node.label)) {
throw new GrammarError(
"Label \"" + node.label + "\" is already defined "
labeled(node, env) {
if (Object.prototype.hasOwnProperty.call(env, node.label)) {
throw new GrammarError(
"Label \"" + node.label + "\" is already defined "
+ "at line " + env[node.label].start.line + ", "
+ "column " + env[node.label].start.column + ".",
node.location
);
}
node.location
);
}
check(node.expression, env);
check(node.expression, env);
env[node.label] = node.location;
},
env[node.label] = node.location;
},
text: checkExpressionWithClonedEnv,
simple_and: checkExpressionWithClonedEnv,
simple_not: checkExpressionWithClonedEnv,
optional: checkExpressionWithClonedEnv,
zero_or_more: checkExpressionWithClonedEnv,
one_or_more: checkExpressionWithClonedEnv,
group: checkExpressionWithClonedEnv
});
text: checkExpressionWithClonedEnv,
simple_and: checkExpressionWithClonedEnv,
simple_not: checkExpressionWithClonedEnv,
optional: checkExpressionWithClonedEnv,
zero_or_more: checkExpressionWithClonedEnv,
one_or_more: checkExpressionWithClonedEnv,
group: checkExpressionWithClonedEnv
});
check(ast);
check(ast);
}
module.exports = reportDuplicateLabels;

@ -5,24 +5,24 @@ let visitor = require("../visitor");
// Checks that each rule is defined only once.
function reportDuplicateRules(ast) {
let rules = {};
let rules = {};
let check = visitor.build({
rule(node) {
if (Object.prototype.hasOwnProperty.call(rules, node.name)) {
throw new GrammarError(
"Rule \"" + node.name + "\" is already defined "
let check = visitor.build({
rule(node) {
if (Object.prototype.hasOwnProperty.call(rules, node.name)) {
throw new GrammarError(
"Rule \"" + node.name + "\" is already defined "
+ "at line " + rules[node.name].start.line + ", "
+ "column " + rules[node.name].start.column + ".",
node.location
);
}
node.location
);
}
rules[node.name] = node.location;
}
});
rules[node.name] = node.location;
}
});
check(ast);
check(ast);
}
module.exports = reportDuplicateRules;

@ -15,40 +15,40 @@ let visitor = require("../visitor");
// In general, if a rule reference can be reached without consuming any input,
// it can lead to left recursion.
function reportInfiniteRecursion(ast) {
let visitedRules = [];
let visitedRules = [];
let check = visitor.build({
rule(node) {
visitedRules.push(node.name);
check(node.expression);
visitedRules.pop(node.name);
},
let check = visitor.build({
rule(node) {
visitedRules.push(node.name);
check(node.expression);
visitedRules.pop(node.name);
},
sequence(node) {
node.elements.every(element => {
check(element);
sequence(node) {
node.elements.every(element => {
check(element);
return !asts.alwaysConsumesOnSuccess(ast, element);
});
},
return !asts.alwaysConsumesOnSuccess(ast, element);
});
},
rule_ref(node) {
if (visitedRules.indexOf(node.name) !== -1) {
visitedRules.push(node.name);
rule_ref(node) {
if (visitedRules.indexOf(node.name) !== -1) {
visitedRules.push(node.name);
throw new GrammarError(
"Possible infinite loop when parsing (left recursion: "
throw new GrammarError(
"Possible infinite loop when parsing (left recursion: "
+ visitedRules.join(" -> ")
+ ").",
node.location
);
}
node.location
);
}
check(asts.findRule(ast, node.name));
}
});
check(asts.findRule(ast, node.name));
}
});
check(ast);
check(ast);
}
module.exports = reportInfiniteRecursion;

@ -7,27 +7,27 @@ let visitor = require("../visitor");
// Reports expressions that don't consume any input inside |*| or |+| in the
// grammar, which prevents infinite loops in the generated parser.
function reportInfiniteRepetition(ast) {
let check = visitor.build({
zero_or_more(node) {
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
throw new GrammarError(
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
node.location
);
}
},
let check = visitor.build({
zero_or_more(node) {
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
throw new GrammarError(
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
node.location
);
}
},
one_or_more(node) {
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
throw new GrammarError(
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
node.location
);
}
}
});
one_or_more(node) {
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
throw new GrammarError(
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
node.location
);
}
}
});
check(ast);
check(ast);
}
module.exports = reportInfiniteRepetition;

@ -6,18 +6,18 @@ let visitor = require("../visitor");
// Checks that all referenced rules exist.
function reportUndefinedRules(ast) {
let check = visitor.build({
rule_ref(node) {
if (!asts.findRule(ast, node.name)) {
throw new GrammarError(
"Rule \"" + node.name + "\" is not defined.",
node.location
);
}
}
});
let check = visitor.build({
rule_ref(node) {
if (!asts.findRule(ast, node.name)) {
throw new GrammarError(
"Rule \"" + node.name + "\" is not defined.",
node.location
);
}
}
});
check(ast);
check(ast);
}
module.exports = reportUndefinedRules;

@ -2,74 +2,74 @@
// Simple AST node visitor builder.
let visitor = {
build(functions) {
function visit(node) {
return functions[node.type].apply(null, arguments);
}
build(functions) {
function visit(node) {
return functions[node.type].apply(null, arguments);
}
function visitNop() {
// Do nothing.
}
function visitNop() {
// Do nothing.
}
function visitExpression(node) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
function visitExpression(node) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
visit.apply(null, [node.expression].concat(extraArgs));
}
visit.apply(null, [node.expression].concat(extraArgs));
}
function visitChildren(property) {
return function(node) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
function visitChildren(property) {
return function(node) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
node[property].forEach(child => {
visit.apply(null, [child].concat(extraArgs));
});
};
}
node[property].forEach(child => {
visit.apply(null, [child].concat(extraArgs));
});
};
}
const DEFAULT_FUNCTIONS = {
grammar(node) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
const DEFAULT_FUNCTIONS = {
grammar(node) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
if (node.initializer) {
visit.apply(null, [node.initializer].concat(extraArgs));
}
if (node.initializer) {
visit.apply(null, [node.initializer].concat(extraArgs));
}
node.rules.forEach(rule => {
visit.apply(null, [rule].concat(extraArgs));
});
},
node.rules.forEach(rule => {
visit.apply(null, [rule].concat(extraArgs));
});
},
initializer: visitNop,
rule: visitExpression,
named: visitExpression,
choice: visitChildren("alternatives"),
action: visitExpression,
sequence: visitChildren("elements"),
labeled: visitExpression,
text: visitExpression,
simple_and: visitExpression,
simple_not: visitExpression,
optional: visitExpression,
zero_or_more: visitExpression,
one_or_more: visitExpression,
group: visitExpression,
semantic_and: visitNop,
semantic_not: visitNop,
rule_ref: visitNop,
literal: visitNop,
class: visitNop,
any: visitNop
};
initializer: visitNop,
rule: visitExpression,
named: visitExpression,
choice: visitChildren("alternatives"),
action: visitExpression,
sequence: visitChildren("elements"),
labeled: visitExpression,
text: visitExpression,
simple_and: visitExpression,
simple_not: visitExpression,
optional: visitExpression,
zero_or_more: visitExpression,
one_or_more: visitExpression,
group: visitExpression,
semantic_and: visitNop,
semantic_not: visitNop,
rule_ref: visitNop,
literal: visitNop,
class: visitNop,
any: visitNop
};
Object.keys(DEFAULT_FUNCTIONS).forEach(type => {
if (!Object.prototype.hasOwnProperty.call(functions, type)) {
functions[type] = DEFAULT_FUNCTIONS[type];
}
});
Object.keys(DEFAULT_FUNCTIONS).forEach(type => {
if (!Object.prototype.hasOwnProperty.call(functions, type)) {
functions[type] = DEFAULT_FUNCTIONS[type];
}
});
return visit;
}
return visit;
}
};
module.exports = visitor;

@ -2,15 +2,15 @@
// Thrown when the grammar contains an error.
class GrammarError {
constructor(message, location) {
this.name = "GrammarError";
this.message = message;
this.location = location;
constructor(message, location) {
this.name = "GrammarError";
this.message = message;
this.location = location;
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(this, GrammarError);
}
}
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(this, GrammarError);
}
}
}
module.exports = GrammarError;

File diff suppressed because one or more lines are too long

@ -1,54 +1,51 @@
"use strict";
const mapObj = require("map-obj");
let GrammarError = require("./grammar-error");
let compiler = require("./compiler");
let parser = require("./parser");
let peg = {
// PEG.js version (uses semantic versioning).
VERSION: "0.10.0",
GrammarError: GrammarError,
parser: parser,
compiler: compiler,
// Generates a parser from a specified grammar and returns it.
//
// The grammar must be a string in the format described by the metagramar in
// the parser.pegjs file.
//
// Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
// |peg.GrammarError| if it contains a semantic error. Note that not all
// errors are detected during the generation and some may protrude to the
// generated parser and cause its malfunction.
generate(grammar, options) {
options = options !== undefined ? options : {};
function convertPasses(passes) {
let converted = {};
Object.keys(passes).forEach(stage => {
converted[stage] = Object.keys(passes[stage])
.map(name => passes[stage][name]);
});
return converted;
}
let plugins = "plugins" in options ? options.plugins : [];
let config = {
parser: peg.parser,
passes: convertPasses(peg.compiler.passes)
};
plugins.forEach(p => { p.use(config, options); });
return peg.compiler.compile(
config.parser.parse(grammar),
config.passes,
options
);
}
module.exports = {
// PEG.js version (uses semantic versioning).
VERSION: require("../package.json").version,
GrammarError: GrammarError,
parser: parser,
compiler: compiler,
// Generates a parser from a specified grammar and returns it.
//
// The grammar must be a string in the format described by the metagramar in
// the parser.pegjs file.
//
// Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
// |peg.GrammarError| if it contains a semantic error. Note that not all
// errors are detected during the generation and some may protrude to the
// generated parser and cause its malfunction.
generate(grammar, options = {}) {
// FIXME: Validatem
let config = {
parser: parser,
passes: mapObj(compiler.passes, (stage, passesForStage) => {
return [ stage, Object.values(passesForStage) ];
})
};
let parseResult = config.parser.parse(grammar);
if (parseResult.imports.length === 0) {
return compiler.compile(
parseResult,
config.passes,
options
);
} else {
throw new Error("`import` syntax can only be used with `generateFromFile`");
}
},
generateFromFile: function (path, options = {}) {
throw new Error(`Unimplemented`);
}
};
module.exports = peg;

@ -0,0 +1,52 @@
Import syntax
import Foo from packagename
import { Foo, Bar } from ./util
import { Foo as Bar, Baz, Qux } from ../quz
need to mangle identifiers declared in the initializer!
===========
Benchmark before internals changes (best of 3):
$ yarn benchmark
yarn run v1.21.1
$ node benchmark/run
┌─────────────────────────────────────┬───────────┬────────────┬──────────────┐
│ Test │ Inp. size │ Avg. time │ Avg. speed │
├─────────────────────────────────────┴───────────┴────────────┴──────────────┤
│ JSON │
├─────────────────────────────────────┬───────────┬────────────┬──────────────┤
│ Example 1 │ 0.69 kB │ 0.40 ms │ 1723.63 kB/s │
│ Example 2 │ 0.24 kB │ 0.10 ms │ 2363.28 kB/s │
│ Example 3 │ 0.59 kB │ 0.40 ms │ 1474.61 kB/s │
│ Example 4 │ 3.39 kB │ 0.90 ms │ 3763.02 kB/s │
│ Example 5 │ 0.85 kB │ 0.20 ms │ 4262.70 kB/s │
├─────────────────────────────────────┼───────────┼────────────┼──────────────┤
│ JSON total │ 5.75 kB │ 2.00 ms │ 2877.44 kB/s │
├─────────────────────────────────────┴───────────┴────────────┴──────────────┤
│ CSS │
├─────────────────────────────────────┬───────────┬────────────┬──────────────┤
│ Blueprint - reset.css (source) │ 1.20 kB │ 3.00 ms │ 401.04 kB/s │
│ Blueprint - typography.css (source) │ 3.11 kB │ 5.00 ms │ 621.48 kB/s │
│ Blueprint - forms.css (source) │ 1.79 kB │ 2.00 ms │ 896.00 kB/s │
│ Blueprint - grid.css (source) │ 9.54 kB │ 6.50 ms │ 1467.25 kB/s │
│ Blueprint - print.css (source) │ 1.78 kB │ 1.10 ms │ 1621.09 kB/s │
│ Blueprint - screen.css (minified) │ 11.83 kB │ 8.00 ms │ 1478.64 kB/s │
│ Blueprint - print.css (minified) │ 1.25 kB │ 0.60 ms │ 2089.84 kB/s │
│ 960.gs - reset.css (source) │ 0.99 kB │ 0.50 ms │ 1980.47 kB/s │
│ 960.gs - text.css (source) │ 0.97 kB │ 0.40 ms │ 2426.76 kB/s │
│ 960.gs - 960.css (source) │ 8.94 kB │ 3.00 ms │ 2979.49 kB/s │
│ 960.gs - 960_24_col.css (source) │ 7.48 kB │ 2.60 ms │ 2877.85 kB/s │
│ 960.gs - reset.css (minified) │ 0.63 kB │ 0.50 ms │ 1265.63 kB/s │
│ 960.gs - text.css (minified) │ 0.41 kB │ 0.40 ms │ 1020.51 kB/s │
│ 960.gs - 960.css (minified) │ 5.21 kB │ 2.60 ms │ 2004.21 kB/s │
│ 960.gs - 960_24_col.css (minified) │ 4.94 kB │ 2.40 ms │ 2057.70 kB/s │
├─────────────────────────────────────┼───────────┼────────────┼──────────────┤
│ CSS total │ 60.08 kB │ 38.60 ms │ 1556.43 kB/s │
├─────────────────────────────────────┼───────────┼────────────┼──────────────┤
│ Total │ 65.83 kB │ 40.60 ms │ 1621.50 kB/s │
└─────────────────────────────────────┴───────────┴────────────┴──────────────┘
Done in 0.60s.

@ -1,15 +1,16 @@
{
"name": "pegjs",
"name": "peg-redux",
"version": "0.10.0",
"description": "Parser generator for JavaScript",
"keywords": [
"parser generator",
"PEG"
],
"homepage": "https://pegjs.org/",
"bugs": "https://github.com/pegjs/pegjs/issues",
"license": "MIT",
"author": "David Majda <david@majda.cz> (https://majda.cz/)",
"contributors": [
"David Majda <david@majda.cz> (http://majda.cz/)",
"Sven Slootweg <admin@cryto.net>"
],
"files": [
"CHANGELOG.md",
"LICENSE",
@ -41,31 +42,32 @@
"bin": "bin/pegjs",
"repository": "pegjs/pegjs",
"scripts": {
"test": "gulp"
"test": "mocha 'test/**/*.js' '!test.vendor.**/*'",
"benchmark": "node benchmark/run",
"lint": "eslint .",
"build": "gulp parser",
"validate": "yarn build && yarn lint && yarn test"
},
"devDependencies": {
"babel-preset-es2015": "6.14.0",
"babelify": "7.3.0",
"browserify": "13.1.0",
"@joepie91/eslint-config": "^1.1.0",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"chai": "3.5.0",
"del": "2.2.2",
"eslint-config-dmajda": "1.0.0",
"express": "4.14.0",
"glob": "7.0.6",
"gulp": "3.9.1",
"gulp-eslint": "3.0.1",
"gulp-header": "1.8.8",
"gulp-mocha": "3.0.1",
"express": "^4.17.1",
"glob": "^7.1.6",
"gulp": "^4.0.2",
"gulp-rename": "1.2.2",
"gulp-transform": "1.0.8",
"gulp-uglify": "2.0.0",
"gulp-transform": "^3.0.5",
"mocha": "^8.2.0",
"morgan": "1.7.0",
"run-sequence": "1.2.2",
"sinon": "1.17.6",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0"
"@babel/preset-env": "^7.12.1",
"eslint": "^7.12.0"
},
"engines": {
"node": ">=4"
},
"dependencies": {
"map-obj": "^4.1.0"
}
}

@ -55,15 +55,45 @@
// ---- Syntactic Grammar -----
Grammar
= __ initializer:(Initializer __)? rules:(Rule __)+ {
= __ imports:(ImportStatement __)* initializer:(Initializer __)? rules:(Rule __)+ {
return {
type: "grammar",
initializer: extractOptional(initializer, 0),
rules: extractList(rules, 0),
imports: extractList(imports, 0),
location: location()
};
}
ImportStatement
= "import" _ bindings:ImportBindings _ "from" _ path:StringLiteral EOS {
return {
type: "import",
path: path,
bindings: bindings,
location: location()
};
}
ImportBindings
= TopLevelImportBinding
/ NamedImportBindings
TopLevelImportBinding
= binding:ImportBinding {
return { type: "topLevelBinding", binding: binding, location: location() };
}
NamedImportBindings
= "{" __ head:(ImportBinding __) tail:("," __ ImportBinding __)* "}" {
return { type: "namedBindings", bindings: buildList(head[0], tail, 2), location: location() };
}
ImportBinding
= name:IdentifierName alias:(_ "as" _ IdentifierName)? {
return { type: "binding", name: name, alias: extractOptional(alias, 3), location: location() };
}
Initializer
= code:CodeBlock EOS {
return { type: "initializer", code: code, location: location() };
@ -242,6 +272,7 @@ SingleLineComment
Identifier
= !ReservedWord name:IdentifierName { return name; }
// TODO: Can performance here be improved by using $?
IdentifierName "identifier"
= head:IdentifierStart tail:IdentifierPart* { return head + tail.join(""); }

@ -1,7 +1,6 @@
/* eslint-disable no-console */
"use strict";
/* global console */
let chai = require("chai");
let peg = require("../../lib/peg");
let sinon = require("sinon");
@ -9,160 +8,160 @@ let sinon = require("sinon");
let expect = chai.expect;
describe("generated parser API", function() {
describe("parse", function() {
it("parses input", function() {
let parser = peg.generate("start = 'a'");
expect(parser.parse("a")).to.equal("a");
});
it("throws an exception on syntax error", function() {
let parser = peg.generate("start = 'a'");
expect(() => { parser.parse("b"); }).to.throw();
});
describe("start rule", function() {
let parser = peg.generate([
"a = 'x' { return 'a'; }",
"b = 'x' { return 'b'; }",
"c = 'x' { return 'c'; }"
].join("\n"), { allowedStartRules: ["b", "c"] });
describe("when |startRule| is not set", function() {
it("starts parsing from the first allowed rule", function() {
expect(parser.parse("x")).to.equal("b");
});
});
describe("when |startRule| is set to an allowed rule", function() {
it("starts parsing from specified rule", function() {
expect(parser.parse("x", { startRule: "b" })).to.equal("b");
expect(parser.parse("x", { startRule: "c" })).to.equal("c");
});
});
describe("when |startRule| is set to a disallowed start rule", function() {
it("throws an exception", function() {
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
});
});
});
describe("tracing", function() {
let parser = peg.generate([
"start = a / b",
"a = 'a'",
"b = 'b'"
].join("\n"), { trace: true });
describe("default tracer", function() {
it("traces using console.log (if console is defined)", function() {
let messages = [
"1:1-1:1 rule.enter start",
"1:1-1:1 rule.enter a",
"1:1-1:1 rule.fail a",
"1:1-1:1 rule.enter b",
"1:1-1:2 rule.match b",
"1:1-1:2 rule.match start"
];
if (typeof console === "object") {
sinon.stub(console, "log");
}
try {
parser.parse("b");
if (typeof console === "object") {
expect(console.log.callCount).to.equal(messages.length);
messages.forEach((message, index) => {
let call = console.log.getCall(index);
expect(call.calledWithExactly(message)).to.equal(true);
});
}
} finally {
if (typeof console === "object") {
console.log.restore();
}
}
});
});
describe("custom tracers", function() {
describe("trace", function() {
it("receives tracing events", function() {
let events = [
{
type: "rule.enter",
rule: "start",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.enter",
rule: "a",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.fail",
rule: "a",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.enter",
rule: "b",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.match",
rule: "b",
result: "b",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
},
{
type: "rule.match",
rule: "start",
result: "b",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
}
];
let tracer = { trace: sinon.spy() };
parser.parse("b", { tracer: tracer });
expect(tracer.trace.callCount).to.equal(events.length);
events.forEach((event, index) => {
let call = tracer.trace.getCall(index);
expect(call.calledWithExactly(event)).to.equal(true);
});
});
});
});
});
it("accepts custom options", function() {
let parser = peg.generate("start = 'a'");
parser.parse("a", { foo: 42 });
});
});
describe("parse", function() {
it("parses input", function() {
let parser = peg.generate("start = 'a'");
expect(parser.parse("a")).to.equal("a");
});
it("throws an exception on syntax error", function() {
let parser = peg.generate("start = 'a'");
expect(() => { parser.parse("b"); }).to.throw();
});
describe("start rule", function() {
let parser = peg.generate([
"a = 'x' { return 'a'; }",
"b = 'x' { return 'b'; }",
"c = 'x' { return 'c'; }"
].join("\n"), { allowedStartRules: ["b", "c"] });
describe("when |startRule| is not set", function() {
it("starts parsing from the first allowed rule", function() {
expect(parser.parse("x")).to.equal("b");
});
});
describe("when |startRule| is set to an allowed rule", function() {
it("starts parsing from specified rule", function() {
expect(parser.parse("x", { startRule: "b" })).to.equal("b");
expect(parser.parse("x", { startRule: "c" })).to.equal("c");
});
});
describe("when |startRule| is set to a disallowed start rule", function() {
it("throws an exception", function() {
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
});
});
});
describe("tracing", function() {
let parser = peg.generate([
"start = a / b",
"a = 'a'",
"b = 'b'"
].join("\n"), { trace: true });
describe("default tracer", function() {
it("traces using console.log (if console is defined)", function() {
let messages = [
"1:1-1:1 rule.enter start",
"1:1-1:1 rule.enter a",
"1:1-1:1 rule.fail a",
"1:1-1:1 rule.enter b",
"1:1-1:2 rule.match b",
"1:1-1:2 rule.match start"
];
if (typeof console === "object") {
sinon.stub(console, "log");
}
try {
parser.parse("b");
if (typeof console === "object") {
expect(console.log.callCount).to.equal(messages.length);
messages.forEach((message, index) => {
let call = console.log.getCall(index);
expect(call.calledWithExactly(message)).to.equal(true);
});
}
} finally {
if (typeof console === "object") {
console.log.restore();
}
}
});
});
describe("custom tracers", function() {
describe("trace", function() {
it("receives tracing events", function() {
let events = [
{
type: "rule.enter",
rule: "start",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.enter",
rule: "a",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.fail",
rule: "a",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.enter",
rule: "b",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 0, line: 1, column: 1 }
}
},
{
type: "rule.match",
rule: "b",
result: "b",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
},
{
type: "rule.match",
rule: "start",
result: "b",
location: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 1, line: 1, column: 2 }
}
}
];
let tracer = { trace: sinon.spy() };
parser.parse("b", { tracer: tracer });
expect(tracer.trace.callCount).to.equal(events.length);
events.forEach((event, index) => {
let call = tracer.trace.getCall(index);
expect(call.calledWithExactly(event)).to.equal(true);
});
});
});
});
});
it("accepts custom options", function() {
let parser = peg.generate("start = 'a'");
parser.parse("a", { foo: 42 });
});
});
});

@ -7,194 +7,194 @@ let sinon = require("sinon");
let expect = chai.expect;
describe("PEG.js API", function() {
describe("generate", function() {
it("generates a parser", function() {
let parser = peg.generate("start = 'a'");
expect(parser).to.be.an("object");
expect(parser.parse("a")).to.equal("a");
});
it("throws an exception on syntax error", function() {
expect(() => { peg.generate("start = @"); }).to.throw();
});
it("throws an exception on semantic error", function() {
expect(() => { peg.generate("start = undefined"); }).to.throw();
});
describe("allowed start rules", function() {
let grammar = [
"a = 'x'",
"b = 'x'",
"c = 'x'"
].join("\n");
// The |allowedStartRules| option is implemented separately for each
// optimization mode, so we need to test it in both.
describe("when optimizing for parsing speed", function() {
describe("when |allowedStartRules| is not set", function() {
it("generated parser can start only from the first rule", function() {
let parser = peg.generate(grammar, { optimize: "speed" });
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
});
});
describe("when |allowedStartRules| is set", function() {
it("generated parser can start only from specified rules", function() {
let parser = peg.generate(grammar, {
optimize: "speed",
allowedStartRules: ["b", "c"]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});
describe("when optimizing for code size", function() {
describe("when |allowedStartRules| is not set", function() {
it("generated parser can start only from the first rule", function() {
let parser = peg.generate(grammar, { optimize: "size" });
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
});
});
describe("when |allowedStartRules| is set", function() {
it("generated parser can start only from specified rules", function() {
let parser = peg.generate(grammar, {
optimize: "size",
allowedStartRules: ["b", "c"]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});
});
describe("intermediate results caching", function() {
let grammar = [
"{ var n = 0; }",
"start = (a 'b') / (a 'c') { return n; }",
"a = 'a' { n++; }"
].join("\n");
describe("when |cache| is not set", function() {
it("generated parser doesn't cache intermediate parse results", function() {
let parser = peg.generate(grammar);
expect(parser.parse("ac")).to.equal(2);
});
});
describe("when |cache| is set to |false|", function() {
it("generated parser doesn't cache intermediate parse results", function() {
let parser = peg.generate(grammar, { cache: false });
expect(parser.parse("ac")).to.equal(2);
});
});
describe("when |cache| is set to |true|", function() {
it("generated parser caches intermediate parse results", function() {
let parser = peg.generate(grammar, { cache: true });
expect(parser.parse("ac")).to.equal(1);
});
});
});
describe("tracing", function() {
let grammar = "start = 'a'";
describe("when |trace| is not set", function() {
it("generated parser doesn't trace", function() {
let parser = peg.generate(grammar);
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace.called).to.equal(false);
});
});
describe("when |trace| is set to |false|", function() {
it("generated parser doesn't trace", function() {
let parser = peg.generate(grammar, { trace: false });
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace.called).to.equal(false);
});
});
describe("when |trace| is set to |true|", function() {
it("generated parser traces", function() {
let parser = peg.generate(grammar, { trace: true });
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace.called).to.equal(true);
});
});
});
// The |optimize| option isn't tested because there is no meaningful way to
// write the tests without turning this into a performance test.
describe("output", function() {
let grammar = "start = 'a'";
describe("when |output| is not set", function() {
it("returns generated parser object", function() {
let parser = peg.generate(grammar);
expect(parser).to.be.an("object");
expect(parser.parse("a")).to.equal("a");
});
});
describe("when |output| is set to |\"parser\"|", function() {
it("returns generated parser object", function() {
let parser = peg.generate(grammar, { output: "parser" });
expect(parser).to.be.an("object");
expect(parser.parse("a")).to.equal("a");
});
});
describe("when |output| is set to |\"source\"|", function() {
it("returns generated parser source code", function() {
let source = peg.generate(grammar, { output: "source" });
expect(source).to.be.a("string");
expect(eval(source).parse("a")).to.equal("a");
});
});
});
describe("generate", function() {
it("generates a parser", function() {
let parser = peg.generate("start = 'a'");
expect(parser).to.be.an("object");
expect(parser.parse("a")).to.equal("a");
});
it("throws an exception on syntax error", function() {
expect(() => { peg.generate("start = @"); }).to.throw();
});
it("throws an exception on semantic error", function() {
expect(() => { peg.generate("start = undefined"); }).to.throw();
});
describe("allowed start rules", function() {
let grammar = [
"a = 'x'",
"b = 'x'",
"c = 'x'"
].join("\n");
// The |allowedStartRules| option is implemented separately for each
// optimization mode, so we need to test it in both.
describe("when optimizing for parsing speed", function() {
describe("when |allowedStartRules| is not set", function() {
it("generated parser can start only from the first rule", function() {
let parser = peg.generate(grammar, { optimize: "speed" });
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
});
});
describe("when |allowedStartRules| is set", function() {
it("generated parser can start only from specified rules", function() {
let parser = peg.generate(grammar, {
optimize: "speed",
allowedStartRules: ["b", "c"]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});
describe("when optimizing for code size", function() {
describe("when |allowedStartRules| is not set", function() {
it("generated parser can start only from the first rule", function() {
let parser = peg.generate(grammar, { optimize: "size" });
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
});
});
describe("when |allowedStartRules| is set", function() {
it("generated parser can start only from specified rules", function() {
let parser = peg.generate(grammar, {
optimize: "size",
allowedStartRules: ["b", "c"]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});
});
describe("intermediate results caching", function() {
let grammar = [
"{ var n = 0; }",
"start = (a 'b') / (a 'c') { return n; }",
"a = 'a' { n++; }"
].join("\n");
describe("when |cache| is not set", function() {
it("generated parser doesn't cache intermediate parse results", function() {
let parser = peg.generate(grammar);
expect(parser.parse("ac")).to.equal(2);
});
});
describe("when |cache| is set to |false|", function() {
it("generated parser doesn't cache intermediate parse results", function() {
let parser = peg.generate(grammar, { cache: false });
expect(parser.parse("ac")).to.equal(2);
});
});
describe("when |cache| is set to |true|", function() {
it("generated parser caches intermediate parse results", function() {
let parser = peg.generate(grammar, { cache: true });
expect(parser.parse("ac")).to.equal(1);
});
});
});
describe("tracing", function() {
let grammar = "start = 'a'";
describe("when |trace| is not set", function() {
it("generated parser doesn't trace", function() {
let parser = peg.generate(grammar);
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace.called).to.equal(false);
});
});
describe("when |trace| is set to |false|", function() {
it("generated parser doesn't trace", function() {
let parser = peg.generate(grammar, { trace: false });
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace.called).to.equal(false);
});
});
describe("when |trace| is set to |true|", function() {
it("generated parser traces", function() {
let parser = peg.generate(grammar, { trace: true });
let tracer = { trace: sinon.spy() };
parser.parse("a", { tracer: tracer });
expect(tracer.trace.called).to.equal(true);
});
});
});
// The |optimize| option isn't tested because there is no meaningful way to
// write the tests without turning this into a performance test.
describe("output", function() {
let grammar = "start = 'a'";
describe("when |output| is not set", function() {
it("returns generated parser object", function() {
let parser = peg.generate(grammar);
expect(parser).to.be.an("object");
expect(parser.parse("a")).to.equal("a");
});
});
describe("when |output| is set to |\"parser\"|", function() {
it("returns generated parser object", function() {
let parser = peg.generate(grammar, { output: "parser" });
expect(parser).to.be.an("object");
expect(parser.parse("a")).to.equal("a");
});
});
describe("when |output| is set to |\"source\"|", function() {
it("returns generated parser source code", function() {
let source = peg.generate(grammar, { output: "source" });
expect(source).to.be.a("string");
expect(eval(source).parse("a")).to.equal("a");
});
});
});
// The |format|, |exportVars|, and |dependencies| options are not tested
// becasue there is no meaningful way to thest their effects without turning
// this into an integration test.
// The |format|, |exportVars|, and |dependencies| options are not tested
// becasue there is no meaningful way to thest their effects without turning
// this into an integration test.
// The |plugins| option is tested in plugin API tests.
// The |plugins| option is tested in plugin API tests.
it("accepts custom options", function() {
peg.generate("start = 'a'", { foo: 42 });
});
});
it("accepts custom options", function() {
peg.generate("start = 'a'", { foo: 42 });
});
});
});

@ -1,128 +0,0 @@
"use strict";
let chai = require("chai");
let peg = require("../../lib/peg");
let expect = chai.expect;
describe("plugin API", function() {
describe("use", function() {
let grammar = "start = 'a'";
it("is called for each plugin", function() {
let pluginsUsed = [false, false, false];
let plugins = [
{ use() { pluginsUsed[0] = true; } },
{ use() { pluginsUsed[1] = true; } },
{ use() { pluginsUsed[2] = true; } }
];
peg.generate(grammar, { plugins: plugins });
expect(pluginsUsed).to.deep.equal([true, true, true]);
});
it("receives configuration", function() {
let plugin = {
use(config) {
expect(config).to.be.an("object");
expect(config.parser).to.be.an("object");
expect(config.parser.parse("start = 'a'")).to.be.an("object");
expect(config.passes).to.be.an("object");
expect(config.passes.check).to.be.an("array");
config.passes.check.forEach(pass => {
expect(pass).to.be.a("function");
});
expect(config.passes.transform).to.be.an("array");
config.passes.transform.forEach(pass => {
expect(pass).to.be.a("function");
});
expect(config.passes.generate).to.be.an("array");
config.passes.generate.forEach(pass => {
expect(pass).to.be.a("function");
});
}
};
peg.generate(grammar, { plugins: [plugin] });
});
it("receives options", function() {
let plugin = {
use(config, options) {
expect(options).to.equal(generateOptions);
}
};
let generateOptions = { plugins: [plugin], foo: 42 };
peg.generate(grammar, generateOptions);
});
it("can replace parser", function() {
let plugin = {
use(config) {
let parser = peg.generate([
"start = .* {",
" return {",
" type: 'grammar',",
" rules: [",
" {",
" type: 'rule',",
" name: 'start',",
" expression: { type: 'literal', value: text(), ignoreCase: false }",
" }",
" ]",
" };",
"}"
].join("\n"));
config.parser = parser;
}
};
let parser = peg.generate("a", { plugins: [plugin] });
expect(parser.parse("a")).to.equal("a");
});
it("can change compiler passes", function() {
let plugin = {
use(config) {
function pass(ast) {
ast.code = "({ parse: function() { return 42; } })";
}
config.passes.generate = [pass];
}
};
let parser = peg.generate(grammar, { plugins: [plugin] });
expect(parser.parse("a")).to.equal(42);
});
it("can change options", function() {
let grammar = [
"a = 'x'",
"b = 'x'",
"c = 'x'"
].join("\n");
let plugin = {
use(config, options) {
options.allowedStartRules = ["b", "c"];
}
};
let parser = peg.generate(grammar, {
allowedStartRules: ["a"],
plugins: [plugin]
});
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
});
});
});

File diff suppressed because it is too large Load Diff

@ -25,7 +25,7 @@ app.get("/bundle.js", (req, res) => {
});
browserify(files)
.transform(babelify, { presets: "es2015", compact: false })
.transform(babelify, { presets: "env", compact: false })
.bundle()
.pipe(res);
});

File diff suppressed because it is too large Load Diff

@ -3,86 +3,86 @@
let parser = require("../../../../lib/parser");
module.exports = function(chai, utils) {
let Assertion = chai.Assertion;
Assertion.addMethod("changeAST", function(grammar, props, options) {
options = options !== undefined ? options : {};
function matchProps(value, props) {
function isArray(value) {
return Object.prototype.toString.apply(value) === "[object Array]";
}
function isObject(value) {
return value !== null && typeof value === "object";
}
if (isArray(props)) {
if (!isArray(value)) { return false; }
if (value.length !== props.length) { return false; }
for (let i = 0; i < props.length; i++) {
if (!matchProps(value[i], props[i])) { return false; }
}
return true;
} else if (isObject(props)) {
if (!isObject(value)) { return false; }
let keys = Object.keys(props);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (!(key in value)) { return false; }
if (!matchProps(value[key], props[key])) { return false; }
}
return true;
} else {
return value === props;
}
}
let ast = parser.parse(grammar);
utils.flag(this, "object")(ast, options);
this.assert(
matchProps(ast, props),
"expected #{this} to change the AST to match #{exp}",
"expected #{this} to not change the AST to match #{exp}",
props,
ast
);
});
Assertion.addMethod("reportError", function(grammar, props) {
let ast = parser.parse(grammar);
let passed, result;
try {
utils.flag(this, "object")(ast);
passed = true;
} catch (e) {
result = e;
passed = false;
}
this.assert(
!passed,
"expected #{this} to report an error but it didn't",
"expected #{this} to not report an error but #{act} was reported",
null,
result
);
if (!passed && props !== undefined) {
Object.keys(props).forEach(key => {
new Assertion(result).to.have.property(key)
.that.is.deep.equal(props[key]);
});
}
});
let Assertion = chai.Assertion;
Assertion.addMethod("changeAST", function(grammar, props, options) {
options = options !== undefined ? options : {};
function matchProps(value, props) {
function isArray(value) {
return Object.prototype.toString.apply(value) === "[object Array]";
}
function isObject(value) {
return value !== null && typeof value === "object";
}
if (isArray(props)) {
if (!isArray(value)) { return false; }
if (value.length !== props.length) { return false; }
for (let i = 0; i < props.length; i++) {
if (!matchProps(value[i], props[i])) { return false; }
}
return true;
} else if (isObject(props)) {
if (!isObject(value)) { return false; }
let keys = Object.keys(props);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (!(key in value)) { return false; }
if (!matchProps(value[key], props[key])) { return false; }
}
return true;
} else {
return value === props;
}
}
let ast = parser.parse(grammar);
utils.flag(this, "object")(ast, options);
this.assert(
matchProps(ast, props),
"expected #{this} to change the AST to match #{exp}",
"expected #{this} to not change the AST to match #{exp}",
props,
ast
);
});
Assertion.addMethod("reportError", function(grammar, props) {
let ast = parser.parse(grammar);
let passed, result;
try {
utils.flag(this, "object")(ast);
passed = true;
} catch (e) {
result = e;
passed = false;
}
this.assert(
!passed,
"expected #{this} to report an error but it didn't",
"expected #{this} to not report an error but #{act} was reported",
null,
result
);
if (!passed && props !== undefined) {
Object.keys(props).forEach(key => {
new Assertion(result).to.have.property(key)
.that.is.deep.equal(props[key]);
});
}
});
};

@ -9,51 +9,51 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |removeProxyRules|", function() {
describe("when a proxy rule isn't listed in |allowedStartRules|", function() {
it("updates references and removes it", function() {
expect(pass).to.changeAST(
[
"start = proxy",
"proxy = proxied",
"proxied = 'a'"
].join("\n"),
{
rules: [
{
name: "start",
expression: { type: "rule_ref", name: "proxied" }
},
{ name: "proxied" }
]
},
{ allowedStartRules: ["start"] }
);
});
});
describe("when a proxy rule isn't listed in |allowedStartRules|", function() {
it("updates references and removes it", function() {
expect(pass).to.changeAST(
[
"start = proxy",
"proxy = proxied",
"proxied = 'a'"
].join("\n"),
{
rules: [
{
name: "start",
expression: { type: "rule_ref", name: "proxied" }
},
{ name: "proxied" }
]
},
{ allowedStartRules: ["start"] }
);
});
});
describe("when a proxy rule is listed in |allowedStartRules|", function() {
it("updates references but doesn't remove it", function() {
expect(pass).to.changeAST(
[
"start = proxy",
"proxy = proxied",
"proxied = 'a'"
].join("\n"),
{
rules: [
{
name: "start",
expression: { type: "rule_ref", name: "proxied" }
},
{
name: "proxy",
expression: { type: "rule_ref", name: "proxied" }
},
{ name: "proxied" }
]
},
{ allowedStartRules: ["start", "proxy"] }
);
});
});
describe("when a proxy rule is listed in |allowedStartRules|", function() {
it("updates references but doesn't remove it", function() {
expect(pass).to.changeAST(
[
"start = proxy",
"proxy = proxied",
"proxied = 'a'"
].join("\n"),
{
rules: [
{
name: "start",
expression: { type: "rule_ref", name: "proxied" }
},
{
name: "proxy",
expression: { type: "rule_ref", name: "proxied" }
},
{ name: "proxied" }
]
},
{ allowedStartRules: ["start", "proxy"] }
);
});
});
});

@ -9,55 +9,55 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportDuplicateLabels|", function() {
describe("in a sequence", function() {
it("reports labels duplicate with labels of preceding elements", function() {
expect(pass).to.reportError("start = a:'a' a:'a'", {
message: "Label \"a\" is already defined at line 1, column 9.",
location: {
start: { offset: 14, line: 1, column: 15 },
end: { offset: 19, line: 1, column: 20 }
}
});
});
it("doesn't report labels duplicate with labels in subexpressions", function() {
expect(pass).to.not.reportError("start = ('a' / a:'a' / 'a') a:'a'");
expect(pass).to.not.reportError("start = (a:'a' { }) a:'a'");
expect(pass).to.not.reportError("start = ('a' a:'a' 'a') a:'a'");
expect(pass).to.not.reportError("start = b:(a:'a') a:'a'");
expect(pass).to.not.reportError("start = $(a:'a') a:'a'");
expect(pass).to.not.reportError("start = &(a:'a') a:'a'");
expect(pass).to.not.reportError("start = !(a:'a') a:'a'");
expect(pass).to.not.reportError("start = (a:'a')? a:'a'");
expect(pass).to.not.reportError("start = (a:'a')* a:'a'");
expect(pass).to.not.reportError("start = (a:'a')+ a:'a'");
expect(pass).to.not.reportError("start = (a:'a') a:'a'");
});
});
describe("in a choice", function() {
it("doesn't report labels duplicate with labels of preceding alternatives", function() {
expect(pass).to.not.reportError("start = a:'a' / a:'a'");
});
});
describe("in outer sequence", function() {
it("reports labels duplicate with labels of preceding elements", function() {
expect(pass).to.reportError("start = a:'a' (a:'a')", {
message: "Label \"a\" is already defined at line 1, column 9.",
location: {
start: { offset: 15, line: 1, column: 16 },
end: { offset: 20, line: 1, column: 21 }
}
});
});
it("doesn't report labels duplicate with the label of the current element", function() {
expect(pass).to.not.reportError("start = a:(a:'a')");
});
it("doesn't report labels duplicate with labels of following elements", function() {
expect(pass).to.not.reportError("start = (a:'a') a:'a'");
});
});
describe("in a sequence", function() {
it("reports labels duplicate with labels of preceding elements", function() {
expect(pass).to.reportError("start = a:'a' a:'a'", {
message: "Label \"a\" is already defined at line 1, column 9.",
location: {
start: { offset: 14, line: 1, column: 15 },
end: { offset: 19, line: 1, column: 20 }
}
});
});
it("doesn't report labels duplicate with labels in subexpressions", function() {
expect(pass).to.not.reportError("start = ('a' / a:'a' / 'a') a:'a'");
expect(pass).to.not.reportError("start = (a:'a' { }) a:'a'");
expect(pass).to.not.reportError("start = ('a' a:'a' 'a') a:'a'");
expect(pass).to.not.reportError("start = b:(a:'a') a:'a'");
expect(pass).to.not.reportError("start = $(a:'a') a:'a'");
expect(pass).to.not.reportError("start = &(a:'a') a:'a'");
expect(pass).to.not.reportError("start = !(a:'a') a:'a'");
expect(pass).to.not.reportError("start = (a:'a')? a:'a'");
expect(pass).to.not.reportError("start = (a:'a')* a:'a'");
expect(pass).to.not.reportError("start = (a:'a')+ a:'a'");
expect(pass).to.not.reportError("start = (a:'a') a:'a'");
});
});
describe("in a choice", function() {
it("doesn't report labels duplicate with labels of preceding alternatives", function() {
expect(pass).to.not.reportError("start = a:'a' / a:'a'");
});
});
describe("in outer sequence", function() {
it("reports labels duplicate with labels of preceding elements", function() {
expect(pass).to.reportError("start = a:'a' (a:'a')", {
message: "Label \"a\" is already defined at line 1, column 9.",
location: {
start: { offset: 15, line: 1, column: 16 },
end: { offset: 20, line: 1, column: 21 }
}
});
});
it("doesn't report labels duplicate with the label of the current element", function() {
expect(pass).to.not.reportError("start = a:(a:'a')");
});
it("doesn't report labels duplicate with labels of following elements", function() {
expect(pass).to.not.reportError("start = (a:'a') a:'a'");
});
});
});

@ -9,16 +9,16 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportDuplicateRules|", function() {
it("reports duplicate rules", function() {
expect(pass).to.reportError([
"start = 'a'",
"start = 'b'"
].join("\n"), {
message: "Rule \"start\" is already defined at line 1, column 1.",
location: {
start: { offset: 12, line: 2, column: 1 },
end: { offset: 23, line: 2, column: 12 }
}
});
});
it("reports duplicate rules", function() {
expect(pass).to.reportError([
"start = 'a'",
"start = 'b'"
].join("\n"), {
message: "Rule \"start\" is already defined at line 1, column 1.",
location: {
start: { offset: 12, line: 2, column: 1 },
end: { offset: 23, line: 2, column: 12 }
}
});
});
});

@ -9,111 +9,111 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportInfiniteRecursion|", function() {
it("reports direct left recursion", function() {
expect(pass).to.reportError("start = start", {
message: "Possible infinite loop when parsing (left recursion: start -> start).",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 13, line: 1, column: 14 }
}
});
});
it("reports indirect left recursion", function() {
expect(pass).to.reportError([
"start = stop",
"stop = start"
].join("\n"), {
message: "Possible infinite loop when parsing (left recursion: start -> stop -> start).",
location: {
start: { offset: 20, line: 2, column: 8 },
end: { offset: 25, line: 2, column: 13 }
}
});
});
describe("in sequences", function() {
it("reports left recursion if all preceding elements match empty string", function() {
expect(pass).to.reportError("start = '' '' '' start");
});
it("doesn't report left recursion if some preceding element doesn't match empty string", function() {
expect(pass).to.not.reportError("start = 'a' '' '' start");
expect(pass).to.not.reportError("start = '' 'a' '' start");
expect(pass).to.not.reportError("start = '' '' 'a' start");
});
// Regression test for #359.
it("reports left recursion when rule reference is wrapped in an expression", function() {
expect(pass).to.reportError("start = '' start?");
});
it("computes expressions that always consume input on success correctly", function() {
expect(pass).to.reportError([
"start = a start",
"a 'a' = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a start",
"a 'a' = 'a'"
].join("\n"));
expect(pass).to.reportError("start = ('' / 'a' / 'b') start");
expect(pass).to.reportError("start = ('a' / '' / 'b') start");
expect(pass).to.reportError("start = ('a' / 'b' / '') start");
expect(pass).to.not.reportError("start = ('a' / 'b' / 'c') start");
expect(pass).to.reportError("start = ('' { }) start");
expect(pass).to.not.reportError("start = ('a' { }) start");
expect(pass).to.reportError("start = ('' '' '') start");
expect(pass).to.not.reportError("start = ('a' '' '') start");
expect(pass).to.not.reportError("start = ('' 'a' '') start");
expect(pass).to.not.reportError("start = ('' '' 'a') start");
expect(pass).to.reportError("start = a:'' start");
expect(pass).to.not.reportError("start = a:'a' start");
expect(pass).to.reportError("start = $'' start");
expect(pass).to.not.reportError("start = $'a' start");
expect(pass).to.reportError("start = &'' start");
expect(pass).to.reportError("start = &'a' start");
expect(pass).to.reportError("start = !'' start");
expect(pass).to.reportError("start = !'a' start");
expect(pass).to.reportError("start = ''? start");
expect(pass).to.reportError("start = 'a'? start");
expect(pass).to.reportError("start = ''* start");
expect(pass).to.reportError("start = 'a'* start");
expect(pass).to.reportError("start = ''+ start");
expect(pass).to.not.reportError("start = 'a'+ start");
expect(pass).to.reportError("start = ('') start");
expect(pass).to.not.reportError("start = ('a') start");
expect(pass).to.reportError("start = &{ } start");
expect(pass).to.reportError("start = !{ } start");
expect(pass).to.reportError([
"start = a start",
"a = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a start",
"a = 'a'"
].join("\n"));
expect(pass).to.reportError("start = '' start");
expect(pass).to.not.reportError("start = 'a' start");
expect(pass).to.not.reportError("start = [a-d] start");
expect(pass).to.not.reportError("start = . start");
});
});
it("reports direct left recursion", function() {
expect(pass).to.reportError("start = start", {
message: "Possible infinite loop when parsing (left recursion: start -> start).",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 13, line: 1, column: 14 }
}
});
});
it("reports indirect left recursion", function() {
expect(pass).to.reportError([
"start = stop",
"stop = start"
].join("\n"), {
message: "Possible infinite loop when parsing (left recursion: start -> stop -> start).",
location: {
start: { offset: 20, line: 2, column: 8 },
end: { offset: 25, line: 2, column: 13 }
}
});
});
describe("in sequences", function() {
it("reports left recursion if all preceding elements match empty string", function() {
expect(pass).to.reportError("start = '' '' '' start");
});
it("doesn't report left recursion if some preceding element doesn't match empty string", function() {
expect(pass).to.not.reportError("start = 'a' '' '' start");
expect(pass).to.not.reportError("start = '' 'a' '' start");
expect(pass).to.not.reportError("start = '' '' 'a' start");
});
// Regression test for #359.
it("reports left recursion when rule reference is wrapped in an expression", function() {
expect(pass).to.reportError("start = '' start?");
});
it("computes expressions that always consume input on success correctly", function() {
expect(pass).to.reportError([
"start = a start",
"a 'a' = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a start",
"a 'a' = 'a'"
].join("\n"));
expect(pass).to.reportError("start = ('' / 'a' / 'b') start");
expect(pass).to.reportError("start = ('a' / '' / 'b') start");
expect(pass).to.reportError("start = ('a' / 'b' / '') start");
expect(pass).to.not.reportError("start = ('a' / 'b' / 'c') start");
expect(pass).to.reportError("start = ('' { }) start");
expect(pass).to.not.reportError("start = ('a' { }) start");
expect(pass).to.reportError("start = ('' '' '') start");
expect(pass).to.not.reportError("start = ('a' '' '') start");
expect(pass).to.not.reportError("start = ('' 'a' '') start");
expect(pass).to.not.reportError("start = ('' '' 'a') start");
expect(pass).to.reportError("start = a:'' start");
expect(pass).to.not.reportError("start = a:'a' start");
expect(pass).to.reportError("start = $'' start");
expect(pass).to.not.reportError("start = $'a' start");
expect(pass).to.reportError("start = &'' start");
expect(pass).to.reportError("start = &'a' start");
expect(pass).to.reportError("start = !'' start");
expect(pass).to.reportError("start = !'a' start");
expect(pass).to.reportError("start = ''? start");
expect(pass).to.reportError("start = 'a'? start");
expect(pass).to.reportError("start = ''* start");
expect(pass).to.reportError("start = 'a'* start");
expect(pass).to.reportError("start = ''+ start");
expect(pass).to.not.reportError("start = 'a'+ start");
expect(pass).to.reportError("start = ('') start");
expect(pass).to.not.reportError("start = ('a') start");
expect(pass).to.reportError("start = &{ } start");
expect(pass).to.reportError("start = !{ } start");
expect(pass).to.reportError([
"start = a start",
"a = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a start",
"a = 'a'"
].join("\n"));
expect(pass).to.reportError("start = '' start");
expect(pass).to.not.reportError("start = 'a' start");
expect(pass).to.not.reportError("start = [a-d] start");
expect(pass).to.not.reportError("start = . start");
});
});
});

@ -9,91 +9,91 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportInfiniteRepetition|", function() {
it("reports infinite loops for zero_or_more", function() {
expect(pass).to.reportError("start = ('')*", {
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 13, line: 1, column: 14 }
}
});
});
it("reports infinite loops for one_or_more", function() {
expect(pass).to.reportError("start = ('')+", {
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 13, line: 1, column: 14 }
}
});
});
it("computes expressions that always consume input on success correctly", function() {
expect(pass).to.reportError([
"start = a*",
"a 'a' = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a*",
"a 'a' = 'a'"
].join("\n"));
expect(pass).to.reportError("start = ('' / 'a' / 'b')*");
expect(pass).to.reportError("start = ('a' / '' / 'b')*");
expect(pass).to.reportError("start = ('a' / 'b' / '')*");
expect(pass).to.not.reportError("start = ('a' / 'b' / 'c')*");
expect(pass).to.reportError("start = ('' { })*");
expect(pass).to.not.reportError("start = ('a' { })*");
expect(pass).to.reportError("start = ('' '' '')*");
expect(pass).to.not.reportError("start = ('a' '' '')*");
expect(pass).to.not.reportError("start = ('' 'a' '')*");
expect(pass).to.not.reportError("start = ('' '' 'a')*");
expect(pass).to.reportError("start = (a:'')*");
expect(pass).to.not.reportError("start = (a:'a')*");
expect(pass).to.reportError("start = ($'')*");
expect(pass).to.not.reportError("start = ($'a')*");
expect(pass).to.reportError("start = (&'')*");
expect(pass).to.reportError("start = (&'a')*");
expect(pass).to.reportError("start = (!'')*");
expect(pass).to.reportError("start = (!'a')*");
expect(pass).to.reportError("start = (''?)*");
expect(pass).to.reportError("start = ('a'?)*");
expect(pass).to.reportError("start = (''*)*");
expect(pass).to.reportError("start = ('a'*)*");
expect(pass).to.reportError("start = (''+)*");
expect(pass).to.not.reportError("start = ('a'+)*");
expect(pass).to.reportError("start = ('')*");
expect(pass).to.not.reportError("start = ('a')*");
expect(pass).to.reportError("start = (&{ })*");
expect(pass).to.reportError("start = (!{ })*");
expect(pass).to.reportError([
"start = a*",
"a = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a*",
"a = 'a'"
].join("\n"));
expect(pass).to.reportError("start = ''*");
expect(pass).to.not.reportError("start = 'a'*");
expect(pass).to.not.reportError("start = [a-d]*");
expect(pass).to.not.reportError("start = .*");
});
it("reports infinite loops for zero_or_more", function() {
expect(pass).to.reportError("start = ('')*", {
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 13, line: 1, column: 14 }
}
});
});
it("reports infinite loops for one_or_more", function() {
expect(pass).to.reportError("start = ('')+", {
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 13, line: 1, column: 14 }
}
});
});
it("computes expressions that always consume input on success correctly", function() {
expect(pass).to.reportError([
"start = a*",
"a 'a' = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a*",
"a 'a' = 'a'"
].join("\n"));
expect(pass).to.reportError("start = ('' / 'a' / 'b')*");
expect(pass).to.reportError("start = ('a' / '' / 'b')*");
expect(pass).to.reportError("start = ('a' / 'b' / '')*");
expect(pass).to.not.reportError("start = ('a' / 'b' / 'c')*");
expect(pass).to.reportError("start = ('' { })*");
expect(pass).to.not.reportError("start = ('a' { })*");
expect(pass).to.reportError("start = ('' '' '')*");
expect(pass).to.not.reportError("start = ('a' '' '')*");
expect(pass).to.not.reportError("start = ('' 'a' '')*");
expect(pass).to.not.reportError("start = ('' '' 'a')*");
expect(pass).to.reportError("start = (a:'')*");
expect(pass).to.not.reportError("start = (a:'a')*");
expect(pass).to.reportError("start = ($'')*");
expect(pass).to.not.reportError("start = ($'a')*");
expect(pass).to.reportError("start = (&'')*");
expect(pass).to.reportError("start = (&'a')*");
expect(pass).to.reportError("start = (!'')*");
expect(pass).to.reportError("start = (!'a')*");
expect(pass).to.reportError("start = (''?)*");
expect(pass).to.reportError("start = ('a'?)*");
expect(pass).to.reportError("start = (''*)*");
expect(pass).to.reportError("start = ('a'*)*");
expect(pass).to.reportError("start = (''+)*");
expect(pass).to.not.reportError("start = ('a'+)*");
expect(pass).to.reportError("start = ('')*");
expect(pass).to.not.reportError("start = ('a')*");
expect(pass).to.reportError("start = (&{ })*");
expect(pass).to.reportError("start = (!{ })*");
expect(pass).to.reportError([
"start = a*",
"a = ''"
].join("\n"));
expect(pass).to.not.reportError([
"start = a*",
"a = 'a'"
].join("\n"));
expect(pass).to.reportError("start = ''*");
expect(pass).to.not.reportError("start = 'a'*");
expect(pass).to.not.reportError("start = [a-d]*");
expect(pass).to.not.reportError("start = .*");
});
});

@ -9,13 +9,13 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportUndefinedRules|", function() {
it("reports undefined rules", function() {
expect(pass).to.reportError("start = undefined", {
message: "Rule \"undefined\" is not defined.",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 17, line: 1, column: 18 }
}
});
});
it("reports undefined rules", function() {
expect(pass).to.reportError("start = undefined", {
message: "Rule \"undefined\" is not defined.",
location: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 17, line: 1, column: 18 }
}
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save