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 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) [![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 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 computer languages and build transformers, interpreters, compilers and other
tools easily. 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 Features
-------- --------
@ -20,49 +21,48 @@ Features
* Based on [parsing expression * Based on [parsing expression
grammar](http://en.wikipedia.org/wiki/Parsing_expression_grammar) formalism grammar](http://en.wikipedia.org/wiki/Parsing_expression_grammar) formalism
— more powerful than traditional LL(*k*) and LR(*k*) parsers — more powerful than traditional LL(*k*) and LR(*k*) parsers
* Usable [from your browser](https://pegjs.org/online), from the command line, * Usable from your browser, from the command line, or via JavaScript API
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 * 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.
parser. Just enter your grammar, try parsing few inputs, and download generated * Bower and stand-alone browser builds have been discontinued. Please use a bundler (see below) instead.
parser code. * 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 Installation
------------ ------------
### Node.js ### Node.js
To use the `pegjs` command, install PEG.js globally: To use the `pegjs` command, install PEG-Redux globally:
```console ```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 ```console
$ npm install pegjs $ npm install peg-redux
``` ```
If you need both the `pegjs` command and the JavaScript API, install PEG.js both If you need both the `pegjs` command and the JavaScript API, install PEG-Redux
ways. both ways.
### Browser ### Browser
[Download](https://pegjs.org/#download) the PEG.js library (regular or minified PEG-Redux works with bundlers such as [Browserify](http://browserify.org/), [Parcel](https://parceljs.org/) and [Webpack](https://webpack.js.org/).
version) or install it using Bower:
```console 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 install pegjs
``` 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 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 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. 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` `peg.generate`
* `--extra-options-file` — file with additional options (in JSON format) to * `--extra-options-file` — file with additional options (in JSON format) to
pass to `peg.generate` 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 * `--optimize` — selects between optimizing the generated parser for parsing
speed (`speed`) or code size (`size`) (default: `speed`) 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) times)
* `--trace` — makes the parser trace its progress * `--trace` — makes the parser trace its progress
### JavaScript API ### JavaScript API
In Node.js, require the PEG.js parser generator module: Require the PEG-Redux parser generator module:
```javascript ```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 To generate a parser, call the `peg.generate` method and pass your grammar as a
parameter: parameter:
@ -142,14 +136,7 @@ object to `peg.generate`. The following options are supported:
`false`) `false`)
* `dependencies` — parser dependencies, the value is an object which maps * `dependencies` — parser dependencies, the value is an object which maps
variables used to access the dependencies in the parser to module IDs used 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 to load them.
`"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"`)
* `optimize`— selects between optimizing the generated parser for parsing * `optimize`— selects between optimizing the generated parser for parsing
speed (`"speed"`) or code size (`"size"`) (default: `"speed"`) speed (`"speed"`) or code size (`"size"`) (default: `"speed"`)
* `output` — if set to `"parser"`, the method will return generated parser * `output` — if set to `"parser"`, the method will return generated parser
@ -503,25 +490,14 @@ environments:
* Safari * Safari
* Opera * Opera
However, please note that it is currently only actively tested in Node.js and Firefox. This will likely change in the future.
Development Development
----------- -----------
* [Project website](https://pegjs.org/) PEG-Redux is maintained by [Sven Slootweg (joepie91)](http://cryto.net/~joepie91).
* [Wiki](https://github.com/pegjs/pegjs/wiki) The original PEG.js was developed by [David Majda](http://majda.cz/) ([@dmajda](http://twitter.com/dmajda)).
* [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)).
You are welcome to contribute code. Unless your contribution is really trivial 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 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. sides. You can send code both as a patch or a pull request.
Note that PEG.js is still very much work in progress. There are no compatibility
guarantees until version 1.0.

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

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

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

@ -21,16 +21,17 @@ app.use(express.static(__dirname));
app.use("/examples", express.static(`${__dirname}/../examples`)); app.use("/examples", express.static(`${__dirname}/../examples`));
app.get("/bundle.js", (req, res) => { app.get("/bundle.js", (req, res) => {
let files = glob.sync(`${__dirname}/**/*.js`, { let files = glob.sync(`${__dirname}/**/*.js`, {
ignore: `${__dirname}/vendor/**/*` ignore: `${__dirname}/vendor/**/*`
}); });
browserify(files) browserify(files)
.transform(babelify, { presets: "es2015", compact: false }) .transform(babelify, { presets: "env", compact: false })
.bundle() .bundle()
.pipe(res); .pipe(res);
}); });
app.listen(8000, () => { 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"; "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 gulp = require("gulp");
let header = require("gulp-header");
let mocha = require("gulp-mocha");
let package_ = require("./package");
let peg = require("./lib/peg"); let peg = require("./lib/peg");
let rename = require("gulp-rename"); 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 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) { function generate(contents) {
return peg.generate(contents.toString(), { return peg.generate(contents.toString(), {
output: "source", output: "source",
format: "commonjs" 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. // Generate the grammar parser.
gulp.task("parser", () => gulp.task("parser", () =>
gulp.src("src/parser.pegjs") gulp.src("src/parser.pegjs")
.pipe(transform(generate)) .pipe(transform("utf8", generate))
.pipe(rename({ extname: ".js" })) .pipe(rename({ extname: ".js" }))
.pipe(gulp.dest("lib")) .pipe(gulp.dest("lib"))
);
// Default task.
gulp.task("default", cb =>
runSequence("lint", "test", cb)
); );

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

@ -11,81 +11,68 @@ let reportUndefinedRules = require("./passes/report-undefined-rules");
let visitor = require("./visitor"); let visitor = require("./visitor");
function processOptions(options, defaults) { function processOptions(options, defaults) {
let processedOptions = {}; return Object.assign({}, defaults, options);
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;
} }
let compiler = { let compiler = {
// AST node visitor builder. Useful mainly for plugins which manipulate the // AST node visitor builder. Useful mainly for plugins which manipulate the
// AST. // AST.
visitor: visitor, 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
}
},
// 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 : {};
options = 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 => { // Compiler passes.
passes[stage].forEach(p => { p(ast, options); }); //
}); // 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
}
},
switch (options.output) { // Generates a parser from a specified grammar AST. Throws |peg.GrammarError|
case "parser": // if the AST contains a semantic error. Note that not all errors are detected
return eval(ast.code); // 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
});
case "source": Object.values(passes).forEach((stagePasses) => {
return ast.code; stagePasses.forEach(pass => { pass(ast, processedOptions); });
});
default: switch (processedOptions.output) {
throw new Error("Invalid output format: " + options.output + "."); case "parser":
} return eval(ast.code);
}
case "source":
return ast.code;
// FIXME: Move to Validatem code at entrypoint
default:
throw new Error("Invalid output format: " + processedOptions.output + ".");
}
}
}; };
module.exports = compiler; module.exports = compiler;

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

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

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

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

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

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

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

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

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

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

File diff suppressed because one or more lines are too long

@ -1,54 +1,51 @@
"use strict"; "use strict";
const mapObj = require("map-obj");
let GrammarError = require("./grammar-error"); let GrammarError = require("./grammar-error");
let compiler = require("./compiler"); let compiler = require("./compiler");
let parser = require("./parser"); let parser = require("./parser");
let peg = { module.exports = {
// PEG.js version (uses semantic versioning). // PEG.js version (uses semantic versioning).
VERSION: "0.10.0", VERSION: require("../package.json").version,
GrammarError: GrammarError, GrammarError: GrammarError,
parser: parser, parser: parser,
compiler: compiler, compiler: compiler,
// Generates a parser from a specified grammar and returns it. // 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 grammar must be a string in the format described by the metagramar in
// the parser.pegjs file. // the parser.pegjs file.
// //
// Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or // Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
// |peg.GrammarError| if it contains a semantic error. Note that not all // |peg.GrammarError| if it contains a semantic error. Note that not all
// errors are detected during the generation and some may protrude to the // errors are detected during the generation and some may protrude to the
// generated parser and cause its malfunction. // generated parser and cause its malfunction.
generate(grammar, options) { generate(grammar, options = {}) {
options = options !== undefined ? options : {}; // FIXME: Validatem
function convertPasses(passes) { let config = {
let converted = {}; parser: parser,
passes: mapObj(compiler.passes, (stage, passesForStage) => {
Object.keys(passes).forEach(stage => { return [ stage, Object.values(passesForStage) ];
converted[stage] = Object.keys(passes[stage]) })
.map(name => passes[stage][name]); };
});
let parseResult = config.parser.parse(grammar);
return converted;
} if (parseResult.imports.length === 0) {
return compiler.compile(
let plugins = "plugins" in options ? options.plugins : []; parseResult,
let config = { config.passes,
parser: peg.parser, options
passes: convertPasses(peg.compiler.passes) );
}; } else {
throw new Error("`import` syntax can only be used with `generateFromFile`");
plugins.forEach(p => { p.use(config, options); }); }
},
return peg.compiler.compile( generateFromFile: function (path, options = {}) {
config.parser.parse(grammar), throw new Error(`Unimplemented`);
config.passes, }
options
);
}
}; };
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", "version": "0.10.0",
"description": "Parser generator for JavaScript", "description": "Parser generator for JavaScript",
"keywords": [ "keywords": [
"parser generator", "parser generator",
"PEG" "PEG"
], ],
"homepage": "https://pegjs.org/",
"bugs": "https://github.com/pegjs/pegjs/issues",
"license": "MIT", "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": [ "files": [
"CHANGELOG.md", "CHANGELOG.md",
"LICENSE", "LICENSE",
@ -41,31 +42,32 @@
"bin": "bin/pegjs", "bin": "bin/pegjs",
"repository": "pegjs/pegjs", "repository": "pegjs/pegjs",
"scripts": { "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": { "devDependencies": {
"babel-preset-es2015": "6.14.0", "@joepie91/eslint-config": "^1.1.0",
"babelify": "7.3.0", "babelify": "^10.0.0",
"browserify": "13.1.0", "browserify": "^17.0.0",
"chai": "3.5.0", "chai": "3.5.0",
"del": "2.2.2", "express": "^4.17.1",
"eslint-config-dmajda": "1.0.0", "glob": "^7.1.6",
"express": "4.14.0", "gulp": "^4.0.2",
"glob": "7.0.6",
"gulp": "3.9.1",
"gulp-eslint": "3.0.1",
"gulp-header": "1.8.8",
"gulp-mocha": "3.0.1",
"gulp-rename": "1.2.2", "gulp-rename": "1.2.2",
"gulp-transform": "1.0.8", "gulp-transform": "^3.0.5",
"gulp-uglify": "2.0.0", "mocha": "^8.2.0",
"morgan": "1.7.0", "morgan": "1.7.0",
"run-sequence": "1.2.2",
"sinon": "1.17.6", "sinon": "1.17.6",
"vinyl-buffer": "1.0.0", "@babel/preset-env": "^7.12.1",
"vinyl-source-stream": "1.1.0" "eslint": "^7.12.0"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=4"
},
"dependencies": {
"map-obj": "^4.1.0"
} }
} }

@ -55,15 +55,45 @@
// ---- Syntactic Grammar ----- // ---- Syntactic Grammar -----
Grammar Grammar
= __ initializer:(Initializer __)? rules:(Rule __)+ { = __ imports:(ImportStatement __)* initializer:(Initializer __)? rules:(Rule __)+ {
return { return {
type: "grammar", type: "grammar",
initializer: extractOptional(initializer, 0), initializer: extractOptional(initializer, 0),
rules: extractList(rules, 0), rules: extractList(rules, 0),
imports: extractList(imports, 0),
location: location() 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 Initializer
= code:CodeBlock EOS { = code:CodeBlock EOS {
return { type: "initializer", code: code, location: location() }; return { type: "initializer", code: code, location: location() };
@ -242,6 +272,7 @@ SingleLineComment
Identifier Identifier
= !ReservedWord name:IdentifierName { return name; } = !ReservedWord name:IdentifierName { return name; }
// TODO: Can performance here be improved by using $?
IdentifierName "identifier" IdentifierName "identifier"
= head:IdentifierStart tail:IdentifierPart* { return head + tail.join(""); } = head:IdentifierStart tail:IdentifierPart* { return head + tail.join(""); }

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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