diff --git a/benchmark/benchmarks.js b/benchmark/benchmarks.js
index 227888b..39c8406 100644
--- a/benchmark/benchmarks.js
+++ b/benchmark/benchmarks.js
@@ -1,42 +1,42 @@
"use strict";
let benchmarks = [
- {
- id: "json",
- title: "JSON",
- tests: [
- { file: "example1.json", title: "Example 1" },
- { file: "example2.json", title: "Example 2" },
- { file: "example3.json", title: "Example 3" },
- { file: "example4.json", title: "Example 4" },
- { file: "example5.json", title: "Example 5" }
- ]
- },
- {
- id: "css",
- title: "CSS",
- tests: [
- { file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
- { file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
- { file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
- { file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
- { file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
- // Contains syntax errors.
- // { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
- { file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
- { file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
- // Contains syntax errors.
- // { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
- { file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
- { file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
- { file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
- { file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
- { file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
- { file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
- { file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
- { file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
- ]
- }
+ {
+ id: "json",
+ title: "JSON",
+ tests: [
+ { file: "example1.json", title: "Example 1" },
+ { file: "example2.json", title: "Example 2" },
+ { file: "example3.json", title: "Example 3" },
+ { file: "example4.json", title: "Example 4" },
+ { file: "example5.json", title: "Example 5" }
+ ]
+ },
+ {
+ id: "css",
+ title: "CSS",
+ tests: [
+ { file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
+ { file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
+ { file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
+ { file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
+ { file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
+ // Contains syntax errors.
+ // { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
+ { file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
+ { file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
+ // Contains syntax errors.
+ // { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
+ { file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
+ { file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
+ { file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
+ { file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
+ { file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
+ { file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
+ { file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
+ { file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
+ ]
+ }
];
module.exports = benchmarks;
diff --git a/benchmark/index.js b/benchmark/index.js
index 190d95a..527e5ea 100644
--- a/benchmark/index.js
+++ b/benchmark/index.js
@@ -6,22 +6,22 @@ let Runner = require("./runner.js");
let benchmarks = require("./benchmarks.js");
$("#run").click(() => {
- // Results Table Manipulation
+ // Results Table Manipulation
- let resultsTable = $("#results-table");
+ let resultsTable = $("#results-table");
- function appendHeading(heading) {
- resultsTable.append(
- "
" + heading + " |
"
- );
- }
+ function appendHeading(heading) {
+ resultsTable.append(
+ "" + heading + " |
"
+ );
+ }
- function appendResult(klass, title, url, inputSize, parseTime) {
- const KB = 1024;
- const MS_IN_S = 1000;
+ function appendResult(klass, title, url, inputSize, parseTime) {
+ const KB = 1024;
+ const MS_IN_S = 1000;
- resultsTable.append(
- ""
+ resultsTable.append(
+ "
"
+ ""
+ (url !== null ? "" : "")
+ title
@@ -46,93 +46,93 @@ $("#run").click(() => {
+ " kB/s"
+ " | "
+ "
"
- );
- }
-
- // Main
-
- // Each input is parsed multiple times and the results are averaged. We
- // do this for two reasons:
- //
- // 1. To warm up the interpreter (PEG.js-generated parsers will be
- // most likely used repeatedly, so it makes sense to measure
- // performance after warming up).
- //
- // 2. To minimize random errors.
-
- let runCount = parseInt($("#run-count").val(), 10);
- let options = {
- cache: $("#cache").is(":checked"),
- optimize: $("#optimize").val()
- };
-
- if (isNaN(runCount) || runCount <= 0) {
- alert("Number of runs must be a positive integer.");
-
- return;
- }
-
- Runner.run(benchmarks, runCount, options, {
- readFile(file) {
- return $.ajax({
- type: "GET",
- url: file,
- dataType: "text",
- async: false
- }).responseText;
- },
-
- testStart() {
- // Nothing to do.
- },
-
- testFinish(benchmark, test, inputSize, parseTime) {
- appendResult(
- "individual",
- test.title,
- benchmark.id + "/" + test.file,
- inputSize,
- parseTime
- );
- },
-
- benchmarkStart(benchmark) {
- appendHeading(benchmark.title);
- },
-
- benchmarkFinish(benchmark, inputSize, parseTime) {
- appendResult(
- "benchmark-total",
- benchmark.title + " total",
- null,
- inputSize,
- parseTime
- );
- },
-
- start() {
- $("#run-count, #cache, #run").attr("disabled", "disabled");
-
- resultsTable.show();
- $("#results-table tr").slice(1).remove();
- },
-
- finish(inputSize, parseTime) {
- appendResult(
- "total",
- "Total",
- null,
- inputSize,
- parseTime
- );
-
- $.scrollTo("max", { axis: "y", duration: 500 });
-
- $("#run-count, #cache, #run").removeAttr("disabled");
- }
- });
+ );
+ }
+
+ // Main
+
+ // Each input is parsed multiple times and the results are averaged. We
+ // do this for two reasons:
+ //
+ // 1. To warm up the interpreter (PEG.js-generated parsers will be
+ // most likely used repeatedly, so it makes sense to measure
+ // performance after warming up).
+ //
+ // 2. To minimize random errors.
+
+ let runCount = parseInt($("#run-count").val(), 10);
+ let options = {
+ cache: $("#cache").is(":checked"),
+ optimize: $("#optimize").val()
+ };
+
+ if (isNaN(runCount) || runCount <= 0) {
+ alert("Number of runs must be a positive integer.");
+
+ return;
+ }
+
+ Runner.run(benchmarks, runCount, options, {
+ readFile(file) {
+ return $.ajax({
+ type: "GET",
+ url: file,
+ dataType: "text",
+ async: false
+ }).responseText;
+ },
+
+ testStart() {
+ // Nothing to do.
+ },
+
+ testFinish(benchmark, test, inputSize, parseTime) {
+ appendResult(
+ "individual",
+ test.title,
+ benchmark.id + "/" + test.file,
+ inputSize,
+ parseTime
+ );
+ },
+
+ benchmarkStart(benchmark) {
+ appendHeading(benchmark.title);
+ },
+
+ benchmarkFinish(benchmark, inputSize, parseTime) {
+ appendResult(
+ "benchmark-total",
+ benchmark.title + " total",
+ null,
+ inputSize,
+ parseTime
+ );
+ },
+
+ start() {
+ $("#run-count, #cache, #run").attr("disabled", "disabled");
+
+ resultsTable.show();
+ $("#results-table tr").slice(1).remove();
+ },
+
+ finish(inputSize, parseTime) {
+ appendResult(
+ "total",
+ "Total",
+ null,
+ inputSize,
+ parseTime
+ );
+
+ $.scrollTo("max", { axis: "y", duration: 500 });
+
+ $("#run-count, #cache, #run").removeAttr("disabled");
+ }
+ });
});
$(document).ready(() => {
- $("#run").focus();
+ $("#run").focus();
});
diff --git a/benchmark/runner.js b/benchmark/runner.js
index 861bf7a..dbd2100 100644
--- a/benchmark/runner.js
+++ b/benchmark/runner.js
@@ -5,114 +5,114 @@
let peg = require("../lib/peg");
let Runner = {
- run(benchmarks, runCount, options, callbacks) {
- // Queue
-
- let Q = {
- functions: [],
-
- add(f) {
- this.functions.push(f);
- },
-
- run() {
- if (this.functions.length > 0) {
- this.functions.shift()();
-
- // We can't use |arguments.callee| here because |this| would get
- // messed-up in that case.
- setTimeout(() => { Q.run(); }, 0);
- }
- }
- };
-
- // The benchmark itself is factored out into several functions (some of them
- // generated), which are enqueued and run one by one using |setTimeout|. We
- // do this for two reasons:
- //
- // 1. To avoid bowser mechanism for interrupting long-running scripts to
- // kick-in (or at least to not kick-in that often).
- //
- // 2. To ensure progressive rendering of results in the browser (some
- // browsers do not render at all when running JavaScript code).
- //
- // The enqueued functions share state, which is all stored in the properties
- // of the |state| object.
-
- let state = {};
-
- function initialize() {
- callbacks.start();
-
- state.totalInputSize = 0;
- state.totalParseTime = 0;
- }
-
- function benchmarkInitializer(benchmark) {
- return function() {
- callbacks.benchmarkStart(benchmark);
-
- state.parser = peg.generate(
- callbacks.readFile("../examples/" + benchmark.id + ".pegjs"),
- options
- );
- state.benchmarkInputSize = 0;
- state.benchmarkParseTime = 0;
- };
- }
-
- function testRunner(benchmark, test) {
- return function() {
- callbacks.testStart(benchmark, test);
-
- let input = callbacks.readFile(benchmark.id + "/" + test.file);
-
- let parseTime = 0;
- for (let i = 0; i < runCount; i++) {
- let t = (new Date()).getTime();
- state.parser.parse(input);
- parseTime += (new Date()).getTime() - t;
- }
- let averageParseTime = parseTime / runCount;
-
- callbacks.testFinish(benchmark, test, input.length, averageParseTime);
-
- state.benchmarkInputSize += input.length;
- state.benchmarkParseTime += averageParseTime;
- };
- }
-
- function benchmarkFinalizer(benchmark) {
- return function() {
- callbacks.benchmarkFinish(
- benchmark,
- state.benchmarkInputSize,
- state.benchmarkParseTime
- );
-
- state.totalInputSize += state.benchmarkInputSize;
- state.totalParseTime += state.benchmarkParseTime;
- };
- }
-
- function finalize() {
- callbacks.finish(state.totalInputSize, state.totalParseTime);
- }
-
- // Main
-
- Q.add(initialize);
- benchmarks.forEach(benchmark => {
- Q.add(benchmarkInitializer(benchmark));
- benchmark.tests.forEach(test => {
- Q.add(testRunner(benchmark, test));
- });
- Q.add(benchmarkFinalizer(benchmark));
- });
- Q.add(finalize);
-
- Q.run();
- }
+ run(benchmarks, runCount, options, callbacks) {
+ // Queue
+
+ let Q = {
+ functions: [],
+
+ add(f) {
+ this.functions.push(f);
+ },
+
+ run() {
+ if (this.functions.length > 0) {
+ this.functions.shift()();
+
+ // We can't use |arguments.callee| here because |this| would get
+ // messed-up in that case.
+ setTimeout(() => { Q.run(); }, 0);
+ }
+ }
+ };
+
+ // The benchmark itself is factored out into several functions (some of them
+ // generated), which are enqueued and run one by one using |setTimeout|. We
+ // do this for two reasons:
+ //
+ // 1. To avoid bowser mechanism for interrupting long-running scripts to
+ // kick-in (or at least to not kick-in that often).
+ //
+ // 2. To ensure progressive rendering of results in the browser (some
+ // browsers do not render at all when running JavaScript code).
+ //
+ // The enqueued functions share state, which is all stored in the properties
+ // of the |state| object.
+
+ let state = {};
+
+ function initialize() {
+ callbacks.start();
+
+ state.totalInputSize = 0;
+ state.totalParseTime = 0;
+ }
+
+ function benchmarkInitializer(benchmark) {
+ return function() {
+ callbacks.benchmarkStart(benchmark);
+
+ state.parser = peg.generate(
+ callbacks.readFile("../examples/" + benchmark.id + ".pegjs"),
+ options
+ );
+ state.benchmarkInputSize = 0;
+ state.benchmarkParseTime = 0;
+ };
+ }
+
+ function testRunner(benchmark, test) {
+ return function() {
+ callbacks.testStart(benchmark, test);
+
+ let input = callbacks.readFile(benchmark.id + "/" + test.file);
+
+ let parseTime = 0;
+ for (let i = 0; i < runCount; i++) {
+ let t = (new Date()).getTime();
+ state.parser.parse(input);
+ parseTime += (new Date()).getTime() - t;
+ }
+ let averageParseTime = parseTime / runCount;
+
+ callbacks.testFinish(benchmark, test, input.length, averageParseTime);
+
+ state.benchmarkInputSize += input.length;
+ state.benchmarkParseTime += averageParseTime;
+ };
+ }
+
+ function benchmarkFinalizer(benchmark) {
+ return function() {
+ callbacks.benchmarkFinish(
+ benchmark,
+ state.benchmarkInputSize,
+ state.benchmarkParseTime
+ );
+
+ state.totalInputSize += state.benchmarkInputSize;
+ state.totalParseTime += state.benchmarkParseTime;
+ };
+ }
+
+ function finalize() {
+ callbacks.finish(state.totalInputSize, state.totalParseTime);
+ }
+
+ // Main
+
+ Q.add(initialize);
+ benchmarks.forEach(benchmark => {
+ Q.add(benchmarkInitializer(benchmark));
+ benchmark.tests.forEach(test => {
+ Q.add(testRunner(benchmark, test));
+ });
+ Q.add(benchmarkFinalizer(benchmark));
+ });
+ Q.add(finalize);
+
+ Q.run();
+ }
};
module.exports = Runner;
diff --git a/gulpfile.js b/gulpfile.js
index 4c0ef1b..e55c7f1 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -6,16 +6,16 @@ let rename = require("gulp-rename");
let transform = require("gulp-transform");
function generate(contents) {
- return peg.generate(contents.toString(), {
- output: "source",
- format: "commonjs"
- });
+ return peg.generate(contents.toString(), {
+ output: "source",
+ format: "commonjs"
+ });
}
// Generate the grammar parser.
gulp.task("parser", () =>
- gulp.src("src/parser.pegjs")
- .pipe(transform("utf8", generate))
- .pipe(rename({ extname: ".js" }))
- .pipe(gulp.dest("lib"))
+ gulp.src("src/parser.pegjs")
+ .pipe(transform("utf8", generate))
+ .pipe(rename({ extname: ".js" }))
+ .pipe(gulp.dest("lib"))
);
diff --git a/lib/compiler/asts.js b/lib/compiler/asts.js
index e7f383a..49f06ed 100644
--- a/lib/compiler/asts.js
+++ b/lib/compiler/asts.js
@@ -4,73 +4,73 @@ let visitor = require("./visitor");
// AST utilities.
let asts = {
- findRule(ast, name) {
- for (let i = 0; i < ast.rules.length; i++) {
- if (ast.rules[i].name === name) {
- return ast.rules[i];
- }
- }
-
- return undefined;
- },
-
- indexOfRule(ast, name) {
- for (let i = 0; i < ast.rules.length; i++) {
- if (ast.rules[i].name === name) {
- return i;
- }
- }
-
- return -1;
- },
-
- alwaysConsumesOnSuccess(ast, node) {
- function consumesTrue() { return true; }
- function consumesFalse() { return false; }
-
- function consumesExpression(node) {
- return consumes(node.expression);
- }
-
- let consumes = visitor.build({
- rule: consumesExpression,
- named: consumesExpression,
-
- choice(node) {
- return node.alternatives.every(consumes);
- },
-
- action: consumesExpression,
-
- sequence(node) {
- return node.elements.some(consumes);
- },
-
- labeled: consumesExpression,
- text: consumesExpression,
- simple_and: consumesFalse,
- simple_not: consumesFalse,
- optional: consumesFalse,
- zero_or_more: consumesFalse,
- one_or_more: consumesExpression,
- group: consumesExpression,
- semantic_and: consumesFalse,
- semantic_not: consumesFalse,
-
- rule_ref(node) {
- return consumes(asts.findRule(ast, node.name));
- },
-
- literal(node) {
- return node.value !== "";
- },
-
- class: consumesTrue,
- any: consumesTrue
- });
-
- return consumes(node);
- }
+ findRule(ast, name) {
+ for (let i = 0; i < ast.rules.length; i++) {
+ if (ast.rules[i].name === name) {
+ return ast.rules[i];
+ }
+ }
+
+ return undefined;
+ },
+
+ indexOfRule(ast, name) {
+ for (let i = 0; i < ast.rules.length; i++) {
+ if (ast.rules[i].name === name) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ alwaysConsumesOnSuccess(ast, node) {
+ function consumesTrue() { return true; }
+ function consumesFalse() { return false; }
+
+ function consumesExpression(node) {
+ return consumes(node.expression);
+ }
+
+ let consumes = visitor.build({
+ rule: consumesExpression,
+ named: consumesExpression,
+
+ choice(node) {
+ return node.alternatives.every(consumes);
+ },
+
+ action: consumesExpression,
+
+ sequence(node) {
+ return node.elements.some(consumes);
+ },
+
+ labeled: consumesExpression,
+ text: consumesExpression,
+ simple_and: consumesFalse,
+ simple_not: consumesFalse,
+ optional: consumesFalse,
+ zero_or_more: consumesFalse,
+ one_or_more: consumesExpression,
+ group: consumesExpression,
+ semantic_and: consumesFalse,
+ semantic_not: consumesFalse,
+
+ rule_ref(node) {
+ return consumes(asts.findRule(ast, node.name));
+ },
+
+ literal(node) {
+ return node.value !== "";
+ },
+
+ class: consumesTrue,
+ any: consumesTrue
+ });
+
+ return consumes(node);
+ }
};
module.exports = asts;
diff --git a/lib/compiler/index.js b/lib/compiler/index.js
index 7722ca1..44ebd29 100644
--- a/lib/compiler/index.js
+++ b/lib/compiler/index.js
@@ -11,81 +11,81 @@ let reportUndefinedRules = require("./passes/report-undefined-rules");
let visitor = require("./visitor");
function processOptions(options, defaults) {
- let processedOptions = {};
+ let processedOptions = {};
- Object.keys(options).forEach(name => {
- processedOptions[name] = options[name];
- });
+ 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];
- }
- });
+ Object.keys(defaults).forEach(name => {
+ if (!Object.prototype.hasOwnProperty.call(processedOptions, name)) {
+ processedOptions[name] = defaults[name];
+ }
+ });
- return processedOptions;
+ return processedOptions;
}
let compiler = {
- // AST node visitor builder. Useful mainly for plugins which manipulate the
- // AST.
- visitor: visitor,
+ // AST node visitor builder. Useful mainly for plugins which manipulate the
+ // AST.
+ visitor: visitor,
- // Compiler passes.
- //
- // Each pass is a function that is passed the AST. It can perform checks on it
- // or modify it as needed. If the pass encounters a semantic error, it throws
- // |peg.GrammarError|.
- passes: {
- check: {
- reportUndefinedRules: reportUndefinedRules,
- reportDuplicateRules: reportDuplicateRules,
- reportDuplicateLabels: reportDuplicateLabels,
- reportInfiniteRecursion: reportInfiniteRecursion,
- reportInfiniteRepetition: reportInfiniteRepetition
- },
- transform: {
- removeProxyRules: removeProxyRules
- },
- generate: {
- generateBytecode: generateBytecode,
- generateJS: generateJS
- }
- },
+ // 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 : {};
+ // 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
- });
+ 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 => {
- passes[stage].forEach(p => { p(ast, options); });
- });
+ Object.keys(passes).forEach(stage => {
+ passes[stage].forEach(p => { p(ast, options); });
+ });
- switch (options.output) {
- case "parser":
- return eval(ast.code);
+ switch (options.output) {
+ case "parser":
+ return eval(ast.code);
- case "source":
- return ast.code;
+ case "source":
+ return ast.code;
- default:
- throw new Error("Invalid output format: " + options.output + ".");
- }
- }
+ default:
+ throw new Error("Invalid output format: " + options.output + ".");
+ }
+ }
};
module.exports = compiler;
diff --git a/lib/compiler/js.js b/lib/compiler/js.js
index 09cc713..8c70c71 100644
--- a/lib/compiler/js.js
+++ b/lib/compiler/js.js
@@ -4,51 +4,51 @@ function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
// JavaScript code generation helpers.
let js = {
- stringEscape(s) {
- // ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
- // literal except for the closing quote character, backslash, carriage
- // return, line separator, paragraph separator, and line feed. Any character
- // may appear in the form of an escape sequence.
- //
- // For portability, we also escape all control and non-ASCII characters.
- return s
- .replace(/\\/g, "\\\\") // backslash
- .replace(/"/g, "\\\"") // closing double quote
- .replace(/\0/g, "\\0") // null
- .replace(/\x08/g, "\\b") // backspace
- .replace(/\t/g, "\\t") // horizontal tab
- .replace(/\n/g, "\\n") // line feed
- .replace(/\v/g, "\\v") // vertical tab
- .replace(/\f/g, "\\f") // form feed
- .replace(/\r/g, "\\r") // carriage return
- .replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
- .replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
- .replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
- .replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
- },
+ stringEscape(s) {
+ // ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
+ // literal except for the closing quote character, backslash, carriage
+ // return, line separator, paragraph separator, and line feed. Any character
+ // may appear in the form of an escape sequence.
+ //
+ // For portability, we also escape all control and non-ASCII characters.
+ return s
+ .replace(/\\/g, "\\\\") // backslash
+ .replace(/"/g, "\\\"") // closing double quote
+ .replace(/\0/g, "\\0") // null
+ .replace(/\x08/g, "\\b") // backspace
+ .replace(/\t/g, "\\t") // horizontal tab
+ .replace(/\n/g, "\\n") // line feed
+ .replace(/\v/g, "\\v") // vertical tab
+ .replace(/\f/g, "\\f") // form feed
+ .replace(/\r/g, "\\r") // carriage return
+ .replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
+ .replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
+ .replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
+ .replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
+ },
- regexpClassEscape(s) {
- // Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
- //
- // For portability, we also escape all control and non-ASCII characters.
- return s
- .replace(/\\/g, "\\\\") // backslash
- .replace(/\//g, "\\/") // closing slash
- .replace(/]/g, "\\]") // closing bracket
- .replace(/\^/g, "\\^") // caret
- .replace(/-/g, "\\-") // dash
- .replace(/\0/g, "\\0") // null
- .replace(/\x08/g, "\\b") // backspace
- .replace(/\t/g, "\\t") // horizontal tab
- .replace(/\n/g, "\\n") // line feed
- .replace(/\v/g, "\\v") // vertical tab
- .replace(/\f/g, "\\f") // form feed
- .replace(/\r/g, "\\r") // carriage return
- .replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
- .replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
- .replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
- .replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
- }
+ regexpClassEscape(s) {
+ // Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
+ //
+ // For portability, we also escape all control and non-ASCII characters.
+ return s
+ .replace(/\\/g, "\\\\") // backslash
+ .replace(/\//g, "\\/") // closing slash
+ .replace(/]/g, "\\]") // closing bracket
+ .replace(/\^/g, "\\^") // caret
+ .replace(/-/g, "\\-") // dash
+ .replace(/\0/g, "\\0") // null
+ .replace(/\x08/g, "\\b") // backspace
+ .replace(/\t/g, "\\t") // horizontal tab
+ .replace(/\n/g, "\\n") // line feed
+ .replace(/\v/g, "\\v") // vertical tab
+ .replace(/\f/g, "\\f") // form feed
+ .replace(/\r/g, "\\r") // carriage return
+ .replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
+ .replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
+ .replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
+ .replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
+ }
};
module.exports = js;
diff --git a/lib/compiler/opcodes.js b/lib/compiler/opcodes.js
index 58e09b5..3620e7c 100644
--- a/lib/compiler/opcodes.js
+++ b/lib/compiler/opcodes.js
@@ -2,53 +2,53 @@
// Bytecode instruction opcodes.
let opcodes = {
- // Stack Manipulation
+ // Stack Manipulation
- PUSH: 0, // PUSH c
- PUSH_UNDEFINED: 1, // PUSH_UNDEFINED
- PUSH_NULL: 2, // PUSH_NULL
- PUSH_FAILED: 3, // PUSH_FAILED
- PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY
- PUSH_CURR_POS: 5, // PUSH_CURR_POS
- POP: 6, // POP
- POP_CURR_POS: 7, // POP_CURR_POS
- POP_N: 8, // POP_N n
- NIP: 9, // NIP
- APPEND: 10, // APPEND
- WRAP: 11, // WRAP n
- TEXT: 12, // TEXT
+ PUSH: 0, // PUSH c
+ PUSH_UNDEFINED: 1, // PUSH_UNDEFINED
+ PUSH_NULL: 2, // PUSH_NULL
+ PUSH_FAILED: 3, // PUSH_FAILED
+ PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY
+ PUSH_CURR_POS: 5, // PUSH_CURR_POS
+ POP: 6, // POP
+ POP_CURR_POS: 7, // POP_CURR_POS
+ POP_N: 8, // POP_N n
+ NIP: 9, // NIP
+ APPEND: 10, // APPEND
+ WRAP: 11, // WRAP n
+ TEXT: 12, // TEXT
- // Conditions and Loops
+ // Conditions and Loops
- IF: 13, // IF t, f
- IF_ERROR: 14, // IF_ERROR t, f
- IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f
- WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b
+ IF: 13, // IF t, f
+ IF_ERROR: 14, // IF_ERROR t, f
+ IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f
+ WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b
- // Matching
+ // Matching
- MATCH_ANY: 17, // MATCH_ANY a, f, ...
- MATCH_STRING: 18, // MATCH_STRING s, a, f, ...
- MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...
- MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...
- ACCEPT_N: 21, // ACCEPT_N n
- ACCEPT_STRING: 22, // ACCEPT_STRING s
- FAIL: 23, // FAIL e
+ MATCH_ANY: 17, // MATCH_ANY a, f, ...
+ MATCH_STRING: 18, // MATCH_STRING s, a, f, ...
+ MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...
+ MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...
+ ACCEPT_N: 21, // ACCEPT_N n
+ ACCEPT_STRING: 22, // ACCEPT_STRING s
+ FAIL: 23, // FAIL e
- // Calls
+ // Calls
- LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p
- UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS
- CALL: 26, // CALL f, n, pc, p1, p2, ..., pN
+ LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p
+ UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS
+ CALL: 26, // CALL f, n, pc, p1, p2, ..., pN
- // Rules
+ // Rules
- RULE: 27, // RULE r
+ RULE: 27, // RULE r
- // Failure Reporting
+ // Failure Reporting
- SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
- SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
+ SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
+ SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
};
module.exports = opcodes;
diff --git a/lib/compiler/passes/generate-bytecode.js b/lib/compiler/passes/generate-bytecode.js
index d944f28..61664f6 100644
--- a/lib/compiler/passes/generate-bytecode.js
+++ b/lib/compiler/passes/generate-bytecode.js
@@ -188,431 +188,431 @@ let visitor = require("../visitor");
//
// silentFails--;
function generateBytecode(ast) {
- let consts = [];
-
- function addConst(value) {
- let index = consts.indexOf(value);
-
- return index === -1 ? consts.push(value) - 1 : index;
- }
-
- function addFunctionConst(params, code) {
- return addConst(
- "function(" + params.join(", ") + ") {" + code + "}"
- );
- }
-
- function cloneEnv(env) {
- let clone = {};
-
- Object.keys(env).forEach(name => {
- clone[name] = env[name];
- });
-
- return clone;
- }
-
- function buildSequence() {
- return Array.prototype.concat.apply([], arguments);
- }
-
- function buildCondition(condCode, thenCode, elseCode) {
- return condCode.concat(
- [thenCode.length, elseCode.length],
- thenCode,
- elseCode
- );
- }
-
- function buildLoop(condCode, bodyCode) {
- return condCode.concat([bodyCode.length], bodyCode);
- }
-
- function buildCall(functionIndex, delta, env, sp) {
- let params = Object.keys(env).map(name => sp - env[name]);
-
- return [op.CALL, functionIndex, delta, params.length].concat(params);
- }
-
- function buildSimplePredicate(expression, negative, context) {
- return buildSequence(
- [op.PUSH_CURR_POS],
- [op.SILENT_FAILS_ON],
- generate(expression, {
- sp: context.sp + 1,
- env: cloneEnv(context.env),
- action: null
- }),
- [op.SILENT_FAILS_OFF],
- buildCondition(
- [negative ? op.IF_ERROR : op.IF_NOT_ERROR],
- buildSequence(
- [op.POP],
- [negative ? op.POP : op.POP_CURR_POS],
- [op.PUSH_UNDEFINED]
- ),
- buildSequence(
- [op.POP],
- [negative ? op.POP_CURR_POS : op.POP],
- [op.PUSH_FAILED]
- )
- )
- );
- }
-
- function buildSemanticPredicate(code, negative, context) {
- let functionIndex = addFunctionConst(Object.keys(context.env), code);
-
- return buildSequence(
- [op.UPDATE_SAVED_POS],
- buildCall(functionIndex, 0, context.env, context.sp),
- buildCondition(
- [op.IF],
- buildSequence(
- [op.POP],
- negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]
- ),
- buildSequence(
- [op.POP],
- negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]
- )
- )
- );
- }
-
- function buildAppendLoop(expressionCode) {
- return buildLoop(
- [op.WHILE_NOT_ERROR],
- buildSequence([op.APPEND], expressionCode)
- );
- }
-
- let generate = visitor.build({
- grammar(node) {
- node.rules.forEach(generate);
-
- node.consts = consts;
- },
-
- rule(node) {
- node.bytecode = generate(node.expression, {
- sp: -1, // stack pointer
- env: { }, // mapping of label names to stack positions
- action: null // action nodes pass themselves to children here
- });
- },
-
- named(node, context) {
- let nameIndex = addConst(
- "peg$otherExpectation(\"" + js.stringEscape(node.name) + "\")"
- );
-
- // The code generated below is slightly suboptimal because |FAIL| pushes
- // to the stack, so we need to stick a |POP| in front of it. We lack a
- // dedicated instruction that would just report the failure and not touch
- // the stack.
- return buildSequence(
- [op.SILENT_FAILS_ON],
- generate(node.expression, context),
- [op.SILENT_FAILS_OFF],
- buildCondition([op.IF_ERROR], [op.FAIL, nameIndex], [])
- );
- },
-
- choice(node, context) {
- function buildAlternativesCode(alternatives, context) {
- return buildSequence(
- generate(alternatives[0], {
- sp: context.sp,
- env: cloneEnv(context.env),
- action: null
- }),
- alternatives.length > 1
- ? buildCondition(
- [op.IF_ERROR],
- buildSequence(
- [op.POP],
- buildAlternativesCode(alternatives.slice(1), context)
- ),
- []
- )
- : []
- );
- }
-
- return buildAlternativesCode(node.alternatives, context);
- },
-
- action(node, context) {
- let env = cloneEnv(context.env);
- let emitCall = node.expression.type !== "sequence"
+ let consts = [];
+
+ function addConst(value) {
+ let index = consts.indexOf(value);
+
+ return index === -1 ? consts.push(value) - 1 : index;
+ }
+
+ function addFunctionConst(params, code) {
+ return addConst(
+ "function(" + params.join(", ") + ") {" + code + "}"
+ );
+ }
+
+ function cloneEnv(env) {
+ let clone = {};
+
+ Object.keys(env).forEach(name => {
+ clone[name] = env[name];
+ });
+
+ return clone;
+ }
+
+ function buildSequence() {
+ return Array.prototype.concat.apply([], arguments);
+ }
+
+ function buildCondition(condCode, thenCode, elseCode) {
+ return condCode.concat(
+ [thenCode.length, elseCode.length],
+ thenCode,
+ elseCode
+ );
+ }
+
+ function buildLoop(condCode, bodyCode) {
+ return condCode.concat([bodyCode.length], bodyCode);
+ }
+
+ function buildCall(functionIndex, delta, env, sp) {
+ let params = Object.keys(env).map(name => sp - env[name]);
+
+ return [op.CALL, functionIndex, delta, params.length].concat(params);
+ }
+
+ function buildSimplePredicate(expression, negative, context) {
+ return buildSequence(
+ [op.PUSH_CURR_POS],
+ [op.SILENT_FAILS_ON],
+ generate(expression, {
+ sp: context.sp + 1,
+ env: cloneEnv(context.env),
+ action: null
+ }),
+ [op.SILENT_FAILS_OFF],
+ buildCondition(
+ [negative ? op.IF_ERROR : op.IF_NOT_ERROR],
+ buildSequence(
+ [op.POP],
+ [negative ? op.POP : op.POP_CURR_POS],
+ [op.PUSH_UNDEFINED]
+ ),
+ buildSequence(
+ [op.POP],
+ [negative ? op.POP_CURR_POS : op.POP],
+ [op.PUSH_FAILED]
+ )
+ )
+ );
+ }
+
+ function buildSemanticPredicate(code, negative, context) {
+ let functionIndex = addFunctionConst(Object.keys(context.env), code);
+
+ return buildSequence(
+ [op.UPDATE_SAVED_POS],
+ buildCall(functionIndex, 0, context.env, context.sp),
+ buildCondition(
+ [op.IF],
+ buildSequence(
+ [op.POP],
+ negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]
+ ),
+ buildSequence(
+ [op.POP],
+ negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]
+ )
+ )
+ );
+ }
+
+ function buildAppendLoop(expressionCode) {
+ return buildLoop(
+ [op.WHILE_NOT_ERROR],
+ buildSequence([op.APPEND], expressionCode)
+ );
+ }
+
+ let generate = visitor.build({
+ grammar(node) {
+ node.rules.forEach(generate);
+
+ node.consts = consts;
+ },
+
+ rule(node) {
+ node.bytecode = generate(node.expression, {
+ sp: -1, // stack pointer
+ env: { }, // mapping of label names to stack positions
+ action: null // action nodes pass themselves to children here
+ });
+ },
+
+ named(node, context) {
+ let nameIndex = addConst(
+ "peg$otherExpectation(\"" + js.stringEscape(node.name) + "\")"
+ );
+
+ // The code generated below is slightly suboptimal because |FAIL| pushes
+ // to the stack, so we need to stick a |POP| in front of it. We lack a
+ // dedicated instruction that would just report the failure and not touch
+ // the stack.
+ return buildSequence(
+ [op.SILENT_FAILS_ON],
+ generate(node.expression, context),
+ [op.SILENT_FAILS_OFF],
+ buildCondition([op.IF_ERROR], [op.FAIL, nameIndex], [])
+ );
+ },
+
+ choice(node, context) {
+ function buildAlternativesCode(alternatives, context) {
+ return buildSequence(
+ generate(alternatives[0], {
+ sp: context.sp,
+ env: cloneEnv(context.env),
+ action: null
+ }),
+ alternatives.length > 1
+ ? buildCondition(
+ [op.IF_ERROR],
+ buildSequence(
+ [op.POP],
+ buildAlternativesCode(alternatives.slice(1), context)
+ ),
+ []
+ )
+ : []
+ );
+ }
+
+ return buildAlternativesCode(node.alternatives, context);
+ },
+
+ action(node, context) {
+ let env = cloneEnv(context.env);
+ let emitCall = node.expression.type !== "sequence"
|| node.expression.elements.length === 0;
- let expressionCode = generate(node.expression, {
- sp: context.sp + (emitCall ? 1 : 0),
- env: env,
- action: node
- });
- let functionIndex = addFunctionConst(Object.keys(env), node.code);
-
- return emitCall
- ? buildSequence(
- [op.PUSH_CURR_POS],
- expressionCode,
- buildCondition(
- [op.IF_NOT_ERROR],
- buildSequence(
- [op.LOAD_SAVED_POS, 1],
- buildCall(functionIndex, 1, env, context.sp + 2)
- ),
- []
- ),
- [op.NIP]
- )
- : expressionCode;
- },
-
- sequence(node, context) {
- function buildElementsCode(elements, context) {
- if (elements.length > 0) {
- let processedCount = node.elements.length - elements.slice(1).length;
-
- return buildSequence(
- generate(elements[0], {
- sp: context.sp,
- env: context.env,
- action: null
- }),
- buildCondition(
- [op.IF_NOT_ERROR],
- buildElementsCode(elements.slice(1), {
- sp: context.sp + 1,
- env: context.env,
- action: context.action
- }),
- buildSequence(
- processedCount > 1 ? [op.POP_N, processedCount] : [op.POP],
- [op.POP_CURR_POS],
- [op.PUSH_FAILED]
- )
- )
- );
- } else {
- if (context.action) {
- let functionIndex = addFunctionConst(
- Object.keys(context.env),
- context.action.code
- );
-
- return buildSequence(
- [op.LOAD_SAVED_POS, node.elements.length],
- buildCall(
- functionIndex,
- node.elements.length,
- context.env,
- context.sp
- ),
- [op.NIP]
- );
- } else {
- return buildSequence([op.WRAP, node.elements.length], [op.NIP]);
- }
- }
- }
-
- return buildSequence(
- [op.PUSH_CURR_POS],
- buildElementsCode(node.elements, {
- sp: context.sp + 1,
- env: context.env,
- action: context.action
- })
- );
- },
-
- labeled(node, context) {
- let env = cloneEnv(context.env);
-
- context.env[node.label] = context.sp + 1;
-
- return generate(node.expression, {
- sp: context.sp,
- env: env,
- action: null
- });
- },
-
- text(node, context) {
- return buildSequence(
- [op.PUSH_CURR_POS],
- generate(node.expression, {
- sp: context.sp + 1,
- env: cloneEnv(context.env),
- action: null
- }),
- buildCondition(
- [op.IF_NOT_ERROR],
- buildSequence([op.POP], [op.TEXT]),
- [op.NIP]
- )
- );
- },
-
- simple_and(node, context) {
- return buildSimplePredicate(node.expression, false, context);
- },
-
- simple_not(node, context) {
- return buildSimplePredicate(node.expression, true, context);
- },
-
- optional(node, context) {
- return buildSequence(
- generate(node.expression, {
- sp: context.sp,
- env: cloneEnv(context.env),
- action: null
- }),
- buildCondition(
- [op.IF_ERROR],
- buildSequence([op.POP], [op.PUSH_NULL]),
- []
- )
- );
- },
-
- zero_or_more(node, context) {
- let expressionCode = generate(node.expression, {
- sp: context.sp + 1,
- env: cloneEnv(context.env),
- action: null
- });
-
- return buildSequence(
- [op.PUSH_EMPTY_ARRAY],
- expressionCode,
- buildAppendLoop(expressionCode),
- [op.POP]
- );
- },
-
- one_or_more(node, context) {
- let expressionCode = generate(node.expression, {
- sp: context.sp + 1,
- env: cloneEnv(context.env),
- action: null
- });
-
- return buildSequence(
- [op.PUSH_EMPTY_ARRAY],
- expressionCode,
- buildCondition(
- [op.IF_NOT_ERROR],
- buildSequence(buildAppendLoop(expressionCode), [op.POP]),
- buildSequence([op.POP], [op.POP], [op.PUSH_FAILED])
- )
- );
- },
-
- group(node, context) {
- return generate(node.expression, {
- sp: context.sp,
- env: cloneEnv(context.env),
- action: null
- });
- },
-
- semantic_and(node, context) {
- return buildSemanticPredicate(node.code, false, context);
- },
-
- semantic_not(node, context) {
- return buildSemanticPredicate(node.code, true, context);
- },
-
- rule_ref(node) {
- return [op.RULE, asts.indexOfRule(ast, node.name)];
- },
-
- literal(node) {
- if (node.value.length > 0) {
- let stringIndex = addConst("\""
+ let expressionCode = generate(node.expression, {
+ sp: context.sp + (emitCall ? 1 : 0),
+ env: env,
+ action: node
+ });
+ let functionIndex = addFunctionConst(Object.keys(env), node.code);
+
+ return emitCall
+ ? buildSequence(
+ [op.PUSH_CURR_POS],
+ expressionCode,
+ buildCondition(
+ [op.IF_NOT_ERROR],
+ buildSequence(
+ [op.LOAD_SAVED_POS, 1],
+ buildCall(functionIndex, 1, env, context.sp + 2)
+ ),
+ []
+ ),
+ [op.NIP]
+ )
+ : expressionCode;
+ },
+
+ sequence(node, context) {
+ function buildElementsCode(elements, context) {
+ if (elements.length > 0) {
+ let processedCount = node.elements.length - elements.slice(1).length;
+
+ return buildSequence(
+ generate(elements[0], {
+ sp: context.sp,
+ env: context.env,
+ action: null
+ }),
+ buildCondition(
+ [op.IF_NOT_ERROR],
+ buildElementsCode(elements.slice(1), {
+ sp: context.sp + 1,
+ env: context.env,
+ action: context.action
+ }),
+ buildSequence(
+ processedCount > 1 ? [op.POP_N, processedCount] : [op.POP],
+ [op.POP_CURR_POS],
+ [op.PUSH_FAILED]
+ )
+ )
+ );
+ } else {
+ if (context.action) {
+ let functionIndex = addFunctionConst(
+ Object.keys(context.env),
+ context.action.code
+ );
+
+ return buildSequence(
+ [op.LOAD_SAVED_POS, node.elements.length],
+ buildCall(
+ functionIndex,
+ node.elements.length,
+ context.env,
+ context.sp
+ ),
+ [op.NIP]
+ );
+ } else {
+ return buildSequence([op.WRAP, node.elements.length], [op.NIP]);
+ }
+ }
+ }
+
+ return buildSequence(
+ [op.PUSH_CURR_POS],
+ buildElementsCode(node.elements, {
+ sp: context.sp + 1,
+ env: context.env,
+ action: context.action
+ })
+ );
+ },
+
+ labeled(node, context) {
+ let env = cloneEnv(context.env);
+
+ context.env[node.label] = context.sp + 1;
+
+ return generate(node.expression, {
+ sp: context.sp,
+ env: env,
+ action: null
+ });
+ },
+
+ text(node, context) {
+ return buildSequence(
+ [op.PUSH_CURR_POS],
+ generate(node.expression, {
+ sp: context.sp + 1,
+ env: cloneEnv(context.env),
+ action: null
+ }),
+ buildCondition(
+ [op.IF_NOT_ERROR],
+ buildSequence([op.POP], [op.TEXT]),
+ [op.NIP]
+ )
+ );
+ },
+
+ simple_and(node, context) {
+ return buildSimplePredicate(node.expression, false, context);
+ },
+
+ simple_not(node, context) {
+ return buildSimplePredicate(node.expression, true, context);
+ },
+
+ optional(node, context) {
+ return buildSequence(
+ generate(node.expression, {
+ sp: context.sp,
+ env: cloneEnv(context.env),
+ action: null
+ }),
+ buildCondition(
+ [op.IF_ERROR],
+ buildSequence([op.POP], [op.PUSH_NULL]),
+ []
+ )
+ );
+ },
+
+ zero_or_more(node, context) {
+ let expressionCode = generate(node.expression, {
+ sp: context.sp + 1,
+ env: cloneEnv(context.env),
+ action: null
+ });
+
+ return buildSequence(
+ [op.PUSH_EMPTY_ARRAY],
+ expressionCode,
+ buildAppendLoop(expressionCode),
+ [op.POP]
+ );
+ },
+
+ one_or_more(node, context) {
+ let expressionCode = generate(node.expression, {
+ sp: context.sp + 1,
+ env: cloneEnv(context.env),
+ action: null
+ });
+
+ return buildSequence(
+ [op.PUSH_EMPTY_ARRAY],
+ expressionCode,
+ buildCondition(
+ [op.IF_NOT_ERROR],
+ buildSequence(buildAppendLoop(expressionCode), [op.POP]),
+ buildSequence([op.POP], [op.POP], [op.PUSH_FAILED])
+ )
+ );
+ },
+
+ group(node, context) {
+ return generate(node.expression, {
+ sp: context.sp,
+ env: cloneEnv(context.env),
+ action: null
+ });
+ },
+
+ semantic_and(node, context) {
+ return buildSemanticPredicate(node.code, false, context);
+ },
+
+ semantic_not(node, context) {
+ return buildSemanticPredicate(node.code, true, context);
+ },
+
+ rule_ref(node) {
+ return [op.RULE, asts.indexOfRule(ast, node.name)];
+ },
+
+ literal(node) {
+ if (node.value.length > 0) {
+ let stringIndex = addConst("\""
+ js.stringEscape(
- node.ignoreCase ? node.value.toLowerCase() : node.value
- )
+ node.ignoreCase ? node.value.toLowerCase() : node.value
+ )
+ "\""
- );
- let expectedIndex = addConst(
- "peg$literalExpectation("
+ );
+ let expectedIndex = addConst(
+ "peg$literalExpectation("
+ "\"" + js.stringEscape(node.value) + "\", "
+ node.ignoreCase
+ ")"
- );
-
- // For case-sensitive strings the value must match the beginning of the
- // remaining input exactly. As a result, we can use |ACCEPT_STRING| and
- // save one |substr| call that would be needed if we used |ACCEPT_N|.
- return buildCondition(
- node.ignoreCase
- ? [op.MATCH_STRING_IC, stringIndex]
- : [op.MATCH_STRING, stringIndex],
- node.ignoreCase
- ? [op.ACCEPT_N, node.value.length]
- : [op.ACCEPT_STRING, stringIndex],
- [op.FAIL, expectedIndex]
- );
- } else {
- let stringIndex = addConst("\"\"");
-
- return [op.PUSH, stringIndex];
- }
- },
-
- class(node) {
- let regexp = "/^["
+ );
+
+ // For case-sensitive strings the value must match the beginning of the
+ // remaining input exactly. As a result, we can use |ACCEPT_STRING| and
+ // save one |substr| call that would be needed if we used |ACCEPT_N|.
+ return buildCondition(
+ node.ignoreCase
+ ? [op.MATCH_STRING_IC, stringIndex]
+ : [op.MATCH_STRING, stringIndex],
+ node.ignoreCase
+ ? [op.ACCEPT_N, node.value.length]
+ : [op.ACCEPT_STRING, stringIndex],
+ [op.FAIL, expectedIndex]
+ );
+ } else {
+ let stringIndex = addConst("\"\"");
+
+ return [op.PUSH, stringIndex];
+ }
+ },
+
+ class(node) {
+ let regexp = "/^["
+ (node.inverted ? "^" : "")
+ node.parts.map(part =>
- Array.isArray(part)
- ? js.regexpClassEscape(part[0])
+ Array.isArray(part)
+ ? js.regexpClassEscape(part[0])
+ "-"
+ js.regexpClassEscape(part[1])
- : js.regexpClassEscape(part)
- ).join("")
+ : js.regexpClassEscape(part)
+ ).join("")
+ "]/" + (node.ignoreCase ? "i" : "");
- let parts = "["
+ let parts = "["
+ node.parts.map(part =>
- Array.isArray(part)
- ? "[\"" + js.stringEscape(part[0]) + "\", \"" + js.stringEscape(part[1]) + "\"]"
- : "\"" + js.stringEscape(part) + "\""
- ).join(", ")
+ Array.isArray(part)
+ ? "[\"" + js.stringEscape(part[0]) + "\", \"" + js.stringEscape(part[1]) + "\"]"
+ : "\"" + js.stringEscape(part) + "\""
+ ).join(", ")
+ "]";
- let regexpIndex = addConst(regexp);
- let expectedIndex = addConst(
- "peg$classExpectation("
+ let regexpIndex = addConst(regexp);
+ let expectedIndex = addConst(
+ "peg$classExpectation("
+ parts + ", "
+ node.inverted + ", "
+ node.ignoreCase
+ ")"
- );
-
- return buildCondition(
- [op.MATCH_REGEXP, regexpIndex],
- [op.ACCEPT_N, 1],
- [op.FAIL, expectedIndex]
- );
- },
-
- any() {
- let expectedIndex = addConst("peg$anyExpectation()");
-
- return buildCondition(
- [op.MATCH_ANY],
- [op.ACCEPT_N, 1],
- [op.FAIL, expectedIndex]
- );
- }
- });
-
- generate(ast);
+ );
+
+ return buildCondition(
+ [op.MATCH_REGEXP, regexpIndex],
+ [op.ACCEPT_N, 1],
+ [op.FAIL, expectedIndex]
+ );
+ },
+
+ any() {
+ let expectedIndex = addConst("peg$anyExpectation()");
+
+ return buildCondition(
+ [op.MATCH_ANY],
+ [op.ACCEPT_N, 1],
+ [op.FAIL, expectedIndex]
+ );
+ }
+ });
+
+ generate(ast);
}
module.exports = generateBytecode;
diff --git a/lib/compiler/passes/generate-js.js b/lib/compiler/passes/generate-js.js
index 4dd0295..12c1da8 100644
--- a/lib/compiler/passes/generate-js.js
+++ b/lib/compiler/passes/generate-js.js
@@ -6,1360 +6,1360 @@ let op = require("../opcodes");
// Generates parser JavaScript code.
function generateJS(ast, options) {
- // These only indent non-empty lines to avoid trailing whitespace.
- function indent2(code) { return code.replace(/^(.+)$/gm, " $1"); }
- function indent10(code) { return code.replace(/^(.+)$/gm, " $1"); }
-
- function generateTables() {
- if (options.optimize === "size") {
- return [
- "var peg$consts = [",
- indent2(ast.consts.join(",\n")),
- "];",
- "",
- "var peg$bytecode = [",
- indent2(ast.rules.map(rule =>
- "peg$decode(\""
+ // These only indent non-empty lines to avoid trailing whitespace.
+ function indent2(code) { return code.replace(/^(.+)$/gm, " $1"); }
+ function indent10(code) { return code.replace(/^(.+)$/gm, " $1"); }
+
+ function generateTables() {
+ if (options.optimize === "size") {
+ return [
+ "var peg$consts = [",
+ indent2(ast.consts.join(",\n")),
+ "];",
+ "",
+ "var peg$bytecode = [",
+ indent2(ast.rules.map(rule =>
+ "peg$decode(\""
+ js.stringEscape(rule.bytecode.map(
- b => String.fromCharCode(b + 32)
- ).join(""))
+ b => String.fromCharCode(b + 32)
+ ).join(""))
+ "\")"
- ).join(",\n")),
- "];"
- ].join("\n");
- } else {
- return ast.consts.map((c, i) => "var peg$c" + i + " = " + c + ";").join("\n");
- }
- }
-
- function generateRuleHeader(ruleNameCode, ruleIndexCode) {
- let parts = [];
-
- parts.push("");
-
- if (options.trace) {
- parts.push([
- "peg$tracer.trace({",
- " type: \"rule.enter\",",
- " rule: " + ruleNameCode + ",",
- " location: peg$computeLocation(startPos, startPos)",
- "});",
- ""
- ].join("\n"));
- }
-
- if (options.cache) {
- parts.push([
- "var key = peg$currPos * " + ast.rules.length + " + " + ruleIndexCode + ";",
- "var cached = peg$resultsCache[key];",
- "",
- "if (cached) {",
- " peg$currPos = cached.nextPos;",
- ""
- ].join("\n"));
-
- if (options.trace) {
- parts.push([
- "if (cached.result !== peg$FAILED) {",
- " peg$tracer.trace({",
- " type: \"rule.match\",",
- " rule: " + ruleNameCode + ",",
- " result: cached.result,",
- " location: peg$computeLocation(startPos, peg$currPos)",
- " });",
- "} else {",
- " peg$tracer.trace({",
- " type: \"rule.fail\",",
- " rule: " + ruleNameCode + ",",
- " location: peg$computeLocation(startPos, startPos)",
- " });",
- "}",
- ""
- ].join("\n"));
- }
-
- parts.push([
- " return cached.result;",
- "}",
- ""
- ].join("\n"));
- }
-
- return parts.join("\n");
- }
-
- function generateRuleFooter(ruleNameCode, resultCode) {
- let parts = [];
-
- if (options.cache) {
- parts.push([
- "",
- "peg$resultsCache[key] = { nextPos: peg$currPos, result: " + resultCode + " };"
- ].join("\n"));
- }
-
- if (options.trace) {
- parts.push([
- "",
- "if (" + resultCode + " !== peg$FAILED) {",
- " peg$tracer.trace({",
- " type: \"rule.match\",",
- " rule: " + ruleNameCode + ",",
- " result: " + resultCode + ",",
- " location: peg$computeLocation(startPos, peg$currPos)",
- " });",
- "} else {",
- " peg$tracer.trace({",
- " type: \"rule.fail\",",
- " rule: " + ruleNameCode + ",",
- " location: peg$computeLocation(startPos, startPos)",
- " });",
- "}"
- ].join("\n"));
- }
-
- parts.push([
- "",
- "return " + resultCode + ";"
- ].join("\n"));
-
- return parts.join("\n");
- }
-
- function generateInterpreter() {
- let parts = [];
-
- function generateCondition(cond, argsLength) {
- let baseLength = argsLength + 3;
- let thenLengthCode = "bc[ip + " + (baseLength - 2) + "]";
- let elseLengthCode = "bc[ip + " + (baseLength - 1) + "]";
-
- return [
- "ends.push(end);",
- "ips.push(ip + " + baseLength + " + " + thenLengthCode + " + " + elseLengthCode + ");",
- "",
- "if (" + cond + ") {",
- " end = ip + " + baseLength + " + " + thenLengthCode + ";",
- " ip += " + baseLength + ";",
- "} else {",
- " end = ip + " + baseLength + " + " + thenLengthCode + " + " + elseLengthCode + ";",
- " ip += " + baseLength + " + " + thenLengthCode + ";",
- "}",
- "",
- "break;"
- ].join("\n");
- }
-
- function generateLoop(cond) {
- let baseLength = 2;
- let bodyLengthCode = "bc[ip + " + (baseLength - 1) + "]";
-
- return [
- "if (" + cond + ") {",
- " ends.push(end);",
- " ips.push(ip);",
- "",
- " end = ip + " + baseLength + " + " + bodyLengthCode + ";",
- " ip += " + baseLength + ";",
- "} else {",
- " ip += " + baseLength + " + " + bodyLengthCode + ";",
- "}",
- "",
- "break;"
- ].join("\n");
- }
-
- function generateCall() {
- let baseLength = 4;
- let paramsLengthCode = "bc[ip + " + (baseLength - 1) + "]";
-
- return [
- "params = bc.slice(ip + " + baseLength + ", ip + " + baseLength + " + " + paramsLengthCode + ")",
- " .map(function(p) { return stack[stack.length - 1 - p]; });",
- "",
- "stack.splice(",
- " stack.length - bc[ip + 2],",
- " bc[ip + 2],",
- " peg$consts[bc[ip + 1]].apply(null, params)",
- ");",
- "",
- "ip += " + baseLength + " + " + paramsLengthCode + ";",
- "break;"
- ].join("\n");
- }
-
- parts.push([
- "function peg$decode(s) {",
- " return s.split(\"\").map(function(ch) { return ch.charCodeAt(0) - 32; });",
- "}",
- "",
- "function peg$parseRule(index) {"
- ].join("\n"));
-
- if (options.trace) {
- parts.push([
- " var bc = peg$bytecode[index];",
- " var ip = 0;",
- " var ips = [];",
- " var end = bc.length;",
- " var ends = [];",
- " var stack = [];",
- " var startPos = peg$currPos;",
- " var params;"
- ].join("\n"));
- } else {
- parts.push([
- " var bc = peg$bytecode[index];",
- " var ip = 0;",
- " var ips = [];",
- " var end = bc.length;",
- " var ends = [];",
- " var stack = [];",
- " var params;"
- ].join("\n"));
- }
-
- parts.push(indent2(generateRuleHeader("peg$ruleNames[index]", "index")));
-
- parts.push([
- // The point of the outer loop and the |ips| & |ends| stacks is to avoid
- // recursive calls for interpreting parts of bytecode. In other words, we
- // implement the |interpret| operation of the abstract machine without
- // function calls. Such calls would likely slow the parser down and more
- // importantly cause stack overflows for complex grammars.
- " while (true) {",
- " while (ip < end) {",
- " switch (bc[ip]) {",
- " case " + op.PUSH + ":", // PUSH c
- " stack.push(peg$consts[bc[ip + 1]]);",
- " ip += 2;",
- " break;",
- "",
- " case " + op.PUSH_UNDEFINED + ":", // PUSH_UNDEFINED
- " stack.push(undefined);",
- " ip++;",
- " break;",
- "",
- " case " + op.PUSH_NULL + ":", // PUSH_NULL
- " stack.push(null);",
- " ip++;",
- " break;",
- "",
- " case " + op.PUSH_FAILED + ":", // PUSH_FAILED
- " stack.push(peg$FAILED);",
- " ip++;",
- " break;",
- "",
- " case " + op.PUSH_EMPTY_ARRAY + ":", // PUSH_EMPTY_ARRAY
- " stack.push([]);",
- " ip++;",
- " break;",
- "",
- " case " + op.PUSH_CURR_POS + ":", // PUSH_CURR_POS
- " stack.push(peg$currPos);",
- " ip++;",
- " break;",
- "",
- " case " + op.POP + ":", // POP
- " stack.pop();",
- " ip++;",
- " break;",
- "",
- " case " + op.POP_CURR_POS + ":", // POP_CURR_POS
- " peg$currPos = stack.pop();",
- " ip++;",
- " break;",
- "",
- " case " + op.POP_N + ":", // POP_N n
- " stack.length -= bc[ip + 1];",
- " ip += 2;",
- " break;",
- "",
- " case " + op.NIP + ":", // NIP
- " stack.splice(-2, 1);",
- " ip++;",
- " break;",
- "",
- " case " + op.APPEND + ":", // APPEND
- " stack[stack.length - 2].push(stack.pop());",
- " ip++;",
- " break;",
- "",
- " case " + op.WRAP + ":", // WRAP n
- " stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));",
- " ip += 2;",
- " break;",
- "",
- " case " + op.TEXT + ":", // TEXT
- " stack.push(input.substring(stack.pop(), peg$currPos));",
- " ip++;",
- " break;",
- "",
- " case " + op.IF + ":", // IF t, f
- indent10(generateCondition("stack[stack.length - 1]", 0)),
- "",
- " case " + op.IF_ERROR + ":", // IF_ERROR t, f
- indent10(generateCondition(
- "stack[stack.length - 1] === peg$FAILED",
- 0
- )),
- "",
- " case " + op.IF_NOT_ERROR + ":", // IF_NOT_ERROR t, f
- indent10(
- generateCondition("stack[stack.length - 1] !== peg$FAILED",
- 0
- )),
- "",
- " case " + op.WHILE_NOT_ERROR + ":", // WHILE_NOT_ERROR b
- indent10(generateLoop("stack[stack.length - 1] !== peg$FAILED")),
- "",
- " case " + op.MATCH_ANY + ":", // MATCH_ANY a, f, ...
- indent10(generateCondition("input.length > peg$currPos", 0)),
- "",
- " case " + op.MATCH_STRING + ":", // MATCH_STRING s, a, f, ...
- indent10(generateCondition(
- "input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]",
- 1
- )),
- "",
- " case " + op.MATCH_STRING_IC + ":", // MATCH_STRING_IC s, a, f, ...
- indent10(generateCondition(
- "input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]",
- 1
- )),
- "",
- " case " + op.MATCH_REGEXP + ":", // MATCH_REGEXP r, a, f, ...
- indent10(generateCondition(
- "peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))",
- 1
- )),
- "",
- " case " + op.ACCEPT_N + ":", // ACCEPT_N n
- " stack.push(input.substr(peg$currPos, bc[ip + 1]));",
- " peg$currPos += bc[ip + 1];",
- " ip += 2;",
- " break;",
- "",
- " case " + op.ACCEPT_STRING + ":", // ACCEPT_STRING s
- " stack.push(peg$consts[bc[ip + 1]]);",
- " peg$currPos += peg$consts[bc[ip + 1]].length;",
- " ip += 2;",
- " break;",
- "",
- " case " + op.FAIL + ":", // FAIL e
- " stack.push(peg$FAILED);",
- " if (peg$silentFails === 0) {",
- " peg$fail(peg$consts[bc[ip + 1]]);",
- " }",
- " ip += 2;",
- " break;",
- "",
- " case " + op.LOAD_SAVED_POS + ":", // LOAD_SAVED_POS p
- " peg$savedPos = stack[stack.length - 1 - bc[ip + 1]];",
- " ip += 2;",
- " break;",
- "",
- " case " + op.UPDATE_SAVED_POS + ":", // UPDATE_SAVED_POS
- " peg$savedPos = peg$currPos;",
- " ip++;",
- " break;",
- "",
- " case " + op.CALL + ":", // CALL f, n, pc, p1, p2, ..., pN
- indent10(generateCall()),
- "",
- " case " + op.RULE + ":", // RULE r
- " stack.push(peg$parseRule(bc[ip + 1]));",
- " ip += 2;",
- " break;",
- "",
- " case " + op.SILENT_FAILS_ON + ":", // SILENT_FAILS_ON
- " peg$silentFails++;",
- " ip++;",
- " break;",
- "",
- " case " + op.SILENT_FAILS_OFF + ":", // SILENT_FAILS_OFF
- " peg$silentFails--;",
- " ip++;",
- " break;",
- "",
- " default:",
- " throw new Error(\"Invalid opcode: \" + bc[ip] + \".\");",
- " }",
- " }",
- "",
- " if (ends.length > 0) {",
- " end = ends.pop();",
- " ip = ips.pop();",
- " } else {",
- " break;",
- " }",
- " }"
- ].join("\n"));
-
- parts.push(indent2(generateRuleFooter("peg$ruleNames[index]", "stack[0]")));
- parts.push("}");
-
- return parts.join("\n");
- }
-
- function generateRuleFunction(rule) {
- let parts = [];
- let stackVars = [];
- let code;
-
- function c(i) { return "peg$c" + i; } // |consts[i]| of the abstract machine
- function s(i) { return "s" + i; } // |stack[i]| of the abstract machine
-
- let stack = {
- sp: -1,
- maxSp: -1,
-
- push(exprCode) {
- let code = s(++this.sp) + " = " + exprCode + ";";
-
- if (this.sp > this.maxSp) { this.maxSp = this.sp; }
-
- return code;
- },
-
- pop(n) {
- if (n === undefined) {
- return s(this.sp--);
- } else {
- let values = Array(n);
-
- for (let i = 0; i < n; i++) {
- values[i] = s(this.sp - n + 1 + i);
- }
-
- this.sp -= n;
-
- return values;
- }
- },
-
- top() {
- return s(this.sp);
- },
-
- index(i) {
- return s(this.sp - i);
- }
- };
-
- function compile(bc) {
- let ip = 0;
- let end = bc.length;
- let parts = [];
- let value;
-
- function compileCondition(cond, argCount) {
- let baseLength = argCount + 3;
- let thenLength = bc[ip + baseLength - 2];
- let elseLength = bc[ip + baseLength - 1];
- let baseSp = stack.sp;
- let thenCode, elseCode, thenSp, elseSp;
-
- ip += baseLength;
- thenCode = compile(bc.slice(ip, ip + thenLength));
- thenSp = stack.sp;
- ip += thenLength;
-
- if (elseLength > 0) {
- stack.sp = baseSp;
- elseCode = compile(bc.slice(ip, ip + elseLength));
- elseSp = stack.sp;
- ip += elseLength;
-
- if (thenSp !== elseSp) {
- throw new Error(
- "Branches of a condition must move the stack pointer in the same way."
- );
- }
- }
-
- parts.push("if (" + cond + ") {");
- parts.push(indent2(thenCode));
- if (elseLength > 0) {
- parts.push("} else {");
- parts.push(indent2(elseCode));
- }
- parts.push("}");
- }
-
- function compileLoop(cond) {
- let baseLength = 2;
- let bodyLength = bc[ip + baseLength - 1];
- let baseSp = stack.sp;
- let bodyCode, bodySp;
-
- ip += baseLength;
- bodyCode = compile(bc.slice(ip, ip + bodyLength));
- bodySp = stack.sp;
- ip += bodyLength;
-
- if (bodySp !== baseSp) {
- throw new Error("Body of a loop can't move the stack pointer.");
- }
-
- parts.push("while (" + cond + ") {");
- parts.push(indent2(bodyCode));
- parts.push("}");
- }
-
- function compileCall() {
- let baseLength = 4;
- let paramsLength = bc[ip + baseLength - 1];
-
- let value = c(bc[ip + 1]) + "("
+ ).join(",\n")),
+ "];"
+ ].join("\n");
+ } else {
+ return ast.consts.map((c, i) => "var peg$c" + i + " = " + c + ";").join("\n");
+ }
+ }
+
+ function generateRuleHeader(ruleNameCode, ruleIndexCode) {
+ let parts = [];
+
+ parts.push("");
+
+ if (options.trace) {
+ parts.push([
+ "peg$tracer.trace({",
+ " type: \"rule.enter\",",
+ " rule: " + ruleNameCode + ",",
+ " location: peg$computeLocation(startPos, startPos)",
+ "});",
+ ""
+ ].join("\n"));
+ }
+
+ if (options.cache) {
+ parts.push([
+ "var key = peg$currPos * " + ast.rules.length + " + " + ruleIndexCode + ";",
+ "var cached = peg$resultsCache[key];",
+ "",
+ "if (cached) {",
+ " peg$currPos = cached.nextPos;",
+ ""
+ ].join("\n"));
+
+ if (options.trace) {
+ parts.push([
+ "if (cached.result !== peg$FAILED) {",
+ " peg$tracer.trace({",
+ " type: \"rule.match\",",
+ " rule: " + ruleNameCode + ",",
+ " result: cached.result,",
+ " location: peg$computeLocation(startPos, peg$currPos)",
+ " });",
+ "} else {",
+ " peg$tracer.trace({",
+ " type: \"rule.fail\",",
+ " rule: " + ruleNameCode + ",",
+ " location: peg$computeLocation(startPos, startPos)",
+ " });",
+ "}",
+ ""
+ ].join("\n"));
+ }
+
+ parts.push([
+ " return cached.result;",
+ "}",
+ ""
+ ].join("\n"));
+ }
+
+ return parts.join("\n");
+ }
+
+ function generateRuleFooter(ruleNameCode, resultCode) {
+ let parts = [];
+
+ if (options.cache) {
+ parts.push([
+ "",
+ "peg$resultsCache[key] = { nextPos: peg$currPos, result: " + resultCode + " };"
+ ].join("\n"));
+ }
+
+ if (options.trace) {
+ parts.push([
+ "",
+ "if (" + resultCode + " !== peg$FAILED) {",
+ " peg$tracer.trace({",
+ " type: \"rule.match\",",
+ " rule: " + ruleNameCode + ",",
+ " result: " + resultCode + ",",
+ " location: peg$computeLocation(startPos, peg$currPos)",
+ " });",
+ "} else {",
+ " peg$tracer.trace({",
+ " type: \"rule.fail\",",
+ " rule: " + ruleNameCode + ",",
+ " location: peg$computeLocation(startPos, startPos)",
+ " });",
+ "}"
+ ].join("\n"));
+ }
+
+ parts.push([
+ "",
+ "return " + resultCode + ";"
+ ].join("\n"));
+
+ return parts.join("\n");
+ }
+
+ function generateInterpreter() {
+ let parts = [];
+
+ function generateCondition(cond, argsLength) {
+ let baseLength = argsLength + 3;
+ let thenLengthCode = "bc[ip + " + (baseLength - 2) + "]";
+ let elseLengthCode = "bc[ip + " + (baseLength - 1) + "]";
+
+ return [
+ "ends.push(end);",
+ "ips.push(ip + " + baseLength + " + " + thenLengthCode + " + " + elseLengthCode + ");",
+ "",
+ "if (" + cond + ") {",
+ " end = ip + " + baseLength + " + " + thenLengthCode + ";",
+ " ip += " + baseLength + ";",
+ "} else {",
+ " end = ip + " + baseLength + " + " + thenLengthCode + " + " + elseLengthCode + ";",
+ " ip += " + baseLength + " + " + thenLengthCode + ";",
+ "}",
+ "",
+ "break;"
+ ].join("\n");
+ }
+
+ function generateLoop(cond) {
+ let baseLength = 2;
+ let bodyLengthCode = "bc[ip + " + (baseLength - 1) + "]";
+
+ return [
+ "if (" + cond + ") {",
+ " ends.push(end);",
+ " ips.push(ip);",
+ "",
+ " end = ip + " + baseLength + " + " + bodyLengthCode + ";",
+ " ip += " + baseLength + ";",
+ "} else {",
+ " ip += " + baseLength + " + " + bodyLengthCode + ";",
+ "}",
+ "",
+ "break;"
+ ].join("\n");
+ }
+
+ function generateCall() {
+ let baseLength = 4;
+ let paramsLengthCode = "bc[ip + " + (baseLength - 1) + "]";
+
+ return [
+ "params = bc.slice(ip + " + baseLength + ", ip + " + baseLength + " + " + paramsLengthCode + ")",
+ " .map(function(p) { return stack[stack.length - 1 - p]; });",
+ "",
+ "stack.splice(",
+ " stack.length - bc[ip + 2],",
+ " bc[ip + 2],",
+ " peg$consts[bc[ip + 1]].apply(null, params)",
+ ");",
+ "",
+ "ip += " + baseLength + " + " + paramsLengthCode + ";",
+ "break;"
+ ].join("\n");
+ }
+
+ parts.push([
+ "function peg$decode(s) {",
+ " return s.split(\"\").map(function(ch) { return ch.charCodeAt(0) - 32; });",
+ "}",
+ "",
+ "function peg$parseRule(index) {"
+ ].join("\n"));
+
+ if (options.trace) {
+ parts.push([
+ " var bc = peg$bytecode[index];",
+ " var ip = 0;",
+ " var ips = [];",
+ " var end = bc.length;",
+ " var ends = [];",
+ " var stack = [];",
+ " var startPos = peg$currPos;",
+ " var params;"
+ ].join("\n"));
+ } else {
+ parts.push([
+ " var bc = peg$bytecode[index];",
+ " var ip = 0;",
+ " var ips = [];",
+ " var end = bc.length;",
+ " var ends = [];",
+ " var stack = [];",
+ " var params;"
+ ].join("\n"));
+ }
+
+ parts.push(indent2(generateRuleHeader("peg$ruleNames[index]", "index")));
+
+ parts.push([
+ // The point of the outer loop and the |ips| & |ends| stacks is to avoid
+ // recursive calls for interpreting parts of bytecode. In other words, we
+ // implement the |interpret| operation of the abstract machine without
+ // function calls. Such calls would likely slow the parser down and more
+ // importantly cause stack overflows for complex grammars.
+ " while (true) {",
+ " while (ip < end) {",
+ " switch (bc[ip]) {",
+ " case " + op.PUSH + ":", // PUSH c
+ " stack.push(peg$consts[bc[ip + 1]]);",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.PUSH_UNDEFINED + ":", // PUSH_UNDEFINED
+ " stack.push(undefined);",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.PUSH_NULL + ":", // PUSH_NULL
+ " stack.push(null);",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.PUSH_FAILED + ":", // PUSH_FAILED
+ " stack.push(peg$FAILED);",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.PUSH_EMPTY_ARRAY + ":", // PUSH_EMPTY_ARRAY
+ " stack.push([]);",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.PUSH_CURR_POS + ":", // PUSH_CURR_POS
+ " stack.push(peg$currPos);",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.POP + ":", // POP
+ " stack.pop();",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.POP_CURR_POS + ":", // POP_CURR_POS
+ " peg$currPos = stack.pop();",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.POP_N + ":", // POP_N n
+ " stack.length -= bc[ip + 1];",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.NIP + ":", // NIP
+ " stack.splice(-2, 1);",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.APPEND + ":", // APPEND
+ " stack[stack.length - 2].push(stack.pop());",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.WRAP + ":", // WRAP n
+ " stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.TEXT + ":", // TEXT
+ " stack.push(input.substring(stack.pop(), peg$currPos));",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.IF + ":", // IF t, f
+ indent10(generateCondition("stack[stack.length - 1]", 0)),
+ "",
+ " case " + op.IF_ERROR + ":", // IF_ERROR t, f
+ indent10(generateCondition(
+ "stack[stack.length - 1] === peg$FAILED",
+ 0
+ )),
+ "",
+ " case " + op.IF_NOT_ERROR + ":", // IF_NOT_ERROR t, f
+ indent10(
+ generateCondition("stack[stack.length - 1] !== peg$FAILED",
+ 0
+ )),
+ "",
+ " case " + op.WHILE_NOT_ERROR + ":", // WHILE_NOT_ERROR b
+ indent10(generateLoop("stack[stack.length - 1] !== peg$FAILED")),
+ "",
+ " case " + op.MATCH_ANY + ":", // MATCH_ANY a, f, ...
+ indent10(generateCondition("input.length > peg$currPos", 0)),
+ "",
+ " case " + op.MATCH_STRING + ":", // MATCH_STRING s, a, f, ...
+ indent10(generateCondition(
+ "input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]",
+ 1
+ )),
+ "",
+ " case " + op.MATCH_STRING_IC + ":", // MATCH_STRING_IC s, a, f, ...
+ indent10(generateCondition(
+ "input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]",
+ 1
+ )),
+ "",
+ " case " + op.MATCH_REGEXP + ":", // MATCH_REGEXP r, a, f, ...
+ indent10(generateCondition(
+ "peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))",
+ 1
+ )),
+ "",
+ " case " + op.ACCEPT_N + ":", // ACCEPT_N n
+ " stack.push(input.substr(peg$currPos, bc[ip + 1]));",
+ " peg$currPos += bc[ip + 1];",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.ACCEPT_STRING + ":", // ACCEPT_STRING s
+ " stack.push(peg$consts[bc[ip + 1]]);",
+ " peg$currPos += peg$consts[bc[ip + 1]].length;",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.FAIL + ":", // FAIL e
+ " stack.push(peg$FAILED);",
+ " if (peg$silentFails === 0) {",
+ " peg$fail(peg$consts[bc[ip + 1]]);",
+ " }",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.LOAD_SAVED_POS + ":", // LOAD_SAVED_POS p
+ " peg$savedPos = stack[stack.length - 1 - bc[ip + 1]];",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.UPDATE_SAVED_POS + ":", // UPDATE_SAVED_POS
+ " peg$savedPos = peg$currPos;",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.CALL + ":", // CALL f, n, pc, p1, p2, ..., pN
+ indent10(generateCall()),
+ "",
+ " case " + op.RULE + ":", // RULE r
+ " stack.push(peg$parseRule(bc[ip + 1]));",
+ " ip += 2;",
+ " break;",
+ "",
+ " case " + op.SILENT_FAILS_ON + ":", // SILENT_FAILS_ON
+ " peg$silentFails++;",
+ " ip++;",
+ " break;",
+ "",
+ " case " + op.SILENT_FAILS_OFF + ":", // SILENT_FAILS_OFF
+ " peg$silentFails--;",
+ " ip++;",
+ " break;",
+ "",
+ " default:",
+ " throw new Error(\"Invalid opcode: \" + bc[ip] + \".\");",
+ " }",
+ " }",
+ "",
+ " if (ends.length > 0) {",
+ " end = ends.pop();",
+ " ip = ips.pop();",
+ " } else {",
+ " break;",
+ " }",
+ " }"
+ ].join("\n"));
+
+ parts.push(indent2(generateRuleFooter("peg$ruleNames[index]", "stack[0]")));
+ parts.push("}");
+
+ return parts.join("\n");
+ }
+
+ function generateRuleFunction(rule) {
+ let parts = [];
+ let stackVars = [];
+ let code;
+
+ function c(i) { return "peg$c" + i; } // |consts[i]| of the abstract machine
+ function s(i) { return "s" + i; } // |stack[i]| of the abstract machine
+
+ let stack = {
+ sp: -1,
+ maxSp: -1,
+
+ push(exprCode) {
+ let code = s(++this.sp) + " = " + exprCode + ";";
+
+ if (this.sp > this.maxSp) { this.maxSp = this.sp; }
+
+ return code;
+ },
+
+ pop(n) {
+ if (n === undefined) {
+ return s(this.sp--);
+ } else {
+ let values = Array(n);
+
+ for (let i = 0; i < n; i++) {
+ values[i] = s(this.sp - n + 1 + i);
+ }
+
+ this.sp -= n;
+
+ return values;
+ }
+ },
+
+ top() {
+ return s(this.sp);
+ },
+
+ index(i) {
+ return s(this.sp - i);
+ }
+ };
+
+ function compile(bc) {
+ let ip = 0;
+ let end = bc.length;
+ let parts = [];
+ let value;
+
+ function compileCondition(cond, argCount) {
+ let baseLength = argCount + 3;
+ let thenLength = bc[ip + baseLength - 2];
+ let elseLength = bc[ip + baseLength - 1];
+ let baseSp = stack.sp;
+ let thenCode, elseCode, thenSp, elseSp;
+
+ ip += baseLength;
+ thenCode = compile(bc.slice(ip, ip + thenLength));
+ thenSp = stack.sp;
+ ip += thenLength;
+
+ if (elseLength > 0) {
+ stack.sp = baseSp;
+ elseCode = compile(bc.slice(ip, ip + elseLength));
+ elseSp = stack.sp;
+ ip += elseLength;
+
+ if (thenSp !== elseSp) {
+ throw new Error(
+ "Branches of a condition must move the stack pointer in the same way."
+ );
+ }
+ }
+
+ parts.push("if (" + cond + ") {");
+ parts.push(indent2(thenCode));
+ if (elseLength > 0) {
+ parts.push("} else {");
+ parts.push(indent2(elseCode));
+ }
+ parts.push("}");
+ }
+
+ function compileLoop(cond) {
+ let baseLength = 2;
+ let bodyLength = bc[ip + baseLength - 1];
+ let baseSp = stack.sp;
+ let bodyCode, bodySp;
+
+ ip += baseLength;
+ bodyCode = compile(bc.slice(ip, ip + bodyLength));
+ bodySp = stack.sp;
+ ip += bodyLength;
+
+ if (bodySp !== baseSp) {
+ throw new Error("Body of a loop can't move the stack pointer.");
+ }
+
+ parts.push("while (" + cond + ") {");
+ parts.push(indent2(bodyCode));
+ parts.push("}");
+ }
+
+ function compileCall() {
+ let baseLength = 4;
+ let paramsLength = bc[ip + baseLength - 1];
+
+ let value = c(bc[ip + 1]) + "("
+ bc.slice(ip + baseLength, ip + baseLength + paramsLength).map(
- p => stack.index(p)
- ).join(", ")
+ p => stack.index(p)
+ ).join(", ")
+ ")";
- stack.pop(bc[ip + 2]);
- parts.push(stack.push(value));
- ip += baseLength + paramsLength;
- }
-
- while (ip < end) {
- switch (bc[ip]) {
- case op.PUSH: // PUSH c
- parts.push(stack.push(c(bc[ip + 1])));
- ip += 2;
- break;
-
- case op.PUSH_CURR_POS: // PUSH_CURR_POS
- parts.push(stack.push("peg$currPos"));
- ip++;
- break;
-
- case op.PUSH_UNDEFINED: // PUSH_UNDEFINED
- parts.push(stack.push("undefined"));
- ip++;
- break;
-
- case op.PUSH_NULL: // PUSH_NULL
- parts.push(stack.push("null"));
- ip++;
- break;
-
- case op.PUSH_FAILED: // PUSH_FAILED
- parts.push(stack.push("peg$FAILED"));
- ip++;
- break;
-
- case op.PUSH_EMPTY_ARRAY: // PUSH_EMPTY_ARRAY
- parts.push(stack.push("[]"));
- ip++;
- break;
-
- case op.POP: // POP
- stack.pop();
- ip++;
- break;
-
- case op.POP_CURR_POS: // POP_CURR_POS
- parts.push("peg$currPos = " + stack.pop() + ";");
- ip++;
- break;
-
- case op.POP_N: // POP_N n
- stack.pop(bc[ip + 1]);
- ip += 2;
- break;
-
- case op.NIP: // NIP
- value = stack.pop();
- stack.pop();
- parts.push(stack.push(value));
- ip++;
- break;
-
- case op.APPEND: // APPEND
- value = stack.pop();
- parts.push(stack.top() + ".push(" + value + ");");
- ip++;
- break;
-
- case op.WRAP: // WRAP n
- parts.push(
- stack.push("[" + stack.pop(bc[ip + 1]).join(", ") + "]")
- );
- ip += 2;
- break;
-
- case op.TEXT: // TEXT
- parts.push(
- stack.push("input.substring(" + stack.pop() + ", peg$currPos)")
- );
- ip++;
- break;
-
- case op.IF: // IF t, f
- compileCondition(stack.top(), 0);
- break;
-
- case op.IF_ERROR: // IF_ERROR t, f
- compileCondition(stack.top() + " === peg$FAILED", 0);
- break;
-
- case op.IF_NOT_ERROR: // IF_NOT_ERROR t, f
- compileCondition(stack.top() + " !== peg$FAILED", 0);
- break;
-
- case op.WHILE_NOT_ERROR: // WHILE_NOT_ERROR b
- compileLoop(stack.top() + " !== peg$FAILED", 0);
- break;
-
- case op.MATCH_ANY: // MATCH_ANY a, f, ...
- compileCondition("input.length > peg$currPos", 0);
- break;
-
- case op.MATCH_STRING: // MATCH_STRING s, a, f, ...
- compileCondition(
- eval(ast.consts[bc[ip + 1]]).length > 1
- ? "input.substr(peg$currPos, "
+ stack.pop(bc[ip + 2]);
+ parts.push(stack.push(value));
+ ip += baseLength + paramsLength;
+ }
+
+ while (ip < end) {
+ switch (bc[ip]) {
+ case op.PUSH: // PUSH c
+ parts.push(stack.push(c(bc[ip + 1])));
+ ip += 2;
+ break;
+
+ case op.PUSH_CURR_POS: // PUSH_CURR_POS
+ parts.push(stack.push("peg$currPos"));
+ ip++;
+ break;
+
+ case op.PUSH_UNDEFINED: // PUSH_UNDEFINED
+ parts.push(stack.push("undefined"));
+ ip++;
+ break;
+
+ case op.PUSH_NULL: // PUSH_NULL
+ parts.push(stack.push("null"));
+ ip++;
+ break;
+
+ case op.PUSH_FAILED: // PUSH_FAILED
+ parts.push(stack.push("peg$FAILED"));
+ ip++;
+ break;
+
+ case op.PUSH_EMPTY_ARRAY: // PUSH_EMPTY_ARRAY
+ parts.push(stack.push("[]"));
+ ip++;
+ break;
+
+ case op.POP: // POP
+ stack.pop();
+ ip++;
+ break;
+
+ case op.POP_CURR_POS: // POP_CURR_POS
+ parts.push("peg$currPos = " + stack.pop() + ";");
+ ip++;
+ break;
+
+ case op.POP_N: // POP_N n
+ stack.pop(bc[ip + 1]);
+ ip += 2;
+ break;
+
+ case op.NIP: // NIP
+ value = stack.pop();
+ stack.pop();
+ parts.push(stack.push(value));
+ ip++;
+ break;
+
+ case op.APPEND: // APPEND
+ value = stack.pop();
+ parts.push(stack.top() + ".push(" + value + ");");
+ ip++;
+ break;
+
+ case op.WRAP: // WRAP n
+ parts.push(
+ stack.push("[" + stack.pop(bc[ip + 1]).join(", ") + "]")
+ );
+ ip += 2;
+ break;
+
+ case op.TEXT: // TEXT
+ parts.push(
+ stack.push("input.substring(" + stack.pop() + ", peg$currPos)")
+ );
+ ip++;
+ break;
+
+ case op.IF: // IF t, f
+ compileCondition(stack.top(), 0);
+ break;
+
+ case op.IF_ERROR: // IF_ERROR t, f
+ compileCondition(stack.top() + " === peg$FAILED", 0);
+ break;
+
+ case op.IF_NOT_ERROR: // IF_NOT_ERROR t, f
+ compileCondition(stack.top() + " !== peg$FAILED", 0);
+ break;
+
+ case op.WHILE_NOT_ERROR: // WHILE_NOT_ERROR b
+ compileLoop(stack.top() + " !== peg$FAILED", 0);
+ break;
+
+ case op.MATCH_ANY: // MATCH_ANY a, f, ...
+ compileCondition("input.length > peg$currPos", 0);
+ break;
+
+ case op.MATCH_STRING: // MATCH_STRING s, a, f, ...
+ compileCondition(
+ eval(ast.consts[bc[ip + 1]]).length > 1
+ ? "input.substr(peg$currPos, "
+ eval(ast.consts[bc[ip + 1]]).length
+ ") === "
+ c(bc[ip + 1])
- : "input.charCodeAt(peg$currPos) === "
+ : "input.charCodeAt(peg$currPos) === "
+ eval(ast.consts[bc[ip + 1]]).charCodeAt(0),
- 1
- );
- break;
+ 1
+ );
+ break;
- case op.MATCH_STRING_IC: // MATCH_STRING_IC s, a, f, ...
- compileCondition(
- "input.substr(peg$currPos, "
+ case op.MATCH_STRING_IC: // MATCH_STRING_IC s, a, f, ...
+ compileCondition(
+ "input.substr(peg$currPos, "
+ eval(ast.consts[bc[ip + 1]]).length
+ ").toLowerCase() === "
+ c(bc[ip + 1]),
- 1
- );
- break;
-
- case op.MATCH_REGEXP: // MATCH_REGEXP r, a, f, ...
- compileCondition(
- c(bc[ip + 1]) + ".test(input.charAt(peg$currPos))",
- 1
- );
- break;
-
- case op.ACCEPT_N: // ACCEPT_N n
- parts.push(stack.push(
- bc[ip + 1] > 1
- ? "input.substr(peg$currPos, " + bc[ip + 1] + ")"
- : "input.charAt(peg$currPos)"
- ));
- parts.push(
- bc[ip + 1] > 1
- ? "peg$currPos += " + bc[ip + 1] + ";"
- : "peg$currPos++;"
- );
- ip += 2;
- break;
-
- case op.ACCEPT_STRING: // ACCEPT_STRING s
- parts.push(stack.push(c(bc[ip + 1])));
- parts.push(
- eval(ast.consts[bc[ip + 1]]).length > 1
- ? "peg$currPos += " + eval(ast.consts[bc[ip + 1]]).length + ";"
- : "peg$currPos++;"
- );
- ip += 2;
- break;
-
- case op.FAIL: // FAIL e
- parts.push(stack.push("peg$FAILED"));
- parts.push("if (peg$silentFails === 0) { peg$fail(" + c(bc[ip + 1]) + "); }");
- ip += 2;
- break;
-
- case op.LOAD_SAVED_POS: // LOAD_SAVED_POS p
- parts.push("peg$savedPos = " + stack.index(bc[ip + 1]) + ";");
- ip += 2;
- break;
-
- case op.UPDATE_SAVED_POS: // UPDATE_SAVED_POS
- parts.push("peg$savedPos = peg$currPos;");
- ip++;
- break;
-
- case op.CALL: // CALL f, n, pc, p1, p2, ..., pN
- compileCall();
- break;
-
- case op.RULE: // RULE r
- parts.push(stack.push("peg$parse" + ast.rules[bc[ip + 1]].name + "()"));
- ip += 2;
- break;
-
- case op.SILENT_FAILS_ON: // SILENT_FAILS_ON
- parts.push("peg$silentFails++;");
- ip++;
- break;
-
- case op.SILENT_FAILS_OFF: // SILENT_FAILS_OFF
- parts.push("peg$silentFails--;");
- ip++;
- break;
-
- default:
- throw new Error("Invalid opcode: " + bc[ip] + ".");
- }
- }
-
- return parts.join("\n");
- }
-
- code = compile(rule.bytecode);
-
- parts.push("function peg$parse" + rule.name + "() {");
-
- if (options.trace) {
- parts.push(" var startPos = peg$currPos;");
- }
-
- for (let i = 0; i <= stack.maxSp; i++) {
- stackVars[i] = s(i);
- }
-
- parts.push(" var " + stackVars.join(", ") + ";");
-
- parts.push(indent2(generateRuleHeader(
- "\"" + js.stringEscape(rule.name) + "\"",
- asts.indexOfRule(ast, rule.name)
- )));
- parts.push(indent2(code));
- parts.push(indent2(generateRuleFooter(
- "\"" + js.stringEscape(rule.name) + "\"",
- s(0)
- )));
-
- parts.push("}");
-
- return parts.join("\n");
- }
-
- function generateToplevel() {
- let parts = [];
-
- parts.push([
- "function peg$subclass(child, parent) {",
- " function C() { this.constructor = child; }",
- " C.prototype = parent.prototype;",
- " child.prototype = new C();",
- "}",
- "",
- "function peg$SyntaxError(message, expected, found, location) {",
- " this.message = message;",
- " this.expected = expected;",
- " this.found = found;",
- " this.location = location;",
- " this.name = \"SyntaxError\";",
- "",
- " if (typeof Error.captureStackTrace === \"function\") {",
- " Error.captureStackTrace(this, peg$SyntaxError);",
- " }",
- "}",
- "",
- "peg$subclass(peg$SyntaxError, Error);",
- "",
- "peg$SyntaxError.buildMessage = function(expected, found) {",
- " var DESCRIBE_EXPECTATION_FNS = {",
- " literal: function(expectation) {",
- " return \"\\\"\" + literalEscape(expectation.text) + \"\\\"\";",
- " },",
- "",
- " class: function(expectation) {",
- " var escapedParts = expectation.parts.map(function(part) {",
- " return Array.isArray(part)",
- " ? classEscape(part[0]) + \"-\" + classEscape(part[1])",
- " : classEscape(part);",
- " });",
- "",
- " return \"[\" + (expectation.inverted ? \"^\" : \"\") + escapedParts + \"]\";",
- " },",
- "",
- " any: function() {",
- " return \"any character\";",
- " },",
- "",
- " end: function() {",
- " return \"end of input\";",
- " },",
- "",
- " other: function(expectation) {",
- " return expectation.description;",
- " }",
- " };",
- "",
- " function hex(ch) {",
- " return ch.charCodeAt(0).toString(16).toUpperCase();",
- " }",
- "",
- " function literalEscape(s) {",
- " return s",
- " .replace(/\\\\/g, \"\\\\\\\\\")", // backslash
- " .replace(/\"/g, \"\\\\\\\"\")", // closing double quote
- " .replace(/\\0/g, \"\\\\0\")", // null
- " .replace(/\\t/g, \"\\\\t\")", // horizontal tab
- " .replace(/\\n/g, \"\\\\n\")", // line feed
- " .replace(/\\r/g, \"\\\\r\")", // carriage return
- " .replace(/[\\x00-\\x0F]/g, function(ch) { return \"\\\\x0\" + hex(ch); })",
- " .replace(/[\\x10-\\x1F\\x7F-\\x9F]/g, function(ch) { return \"\\\\x\" + hex(ch); });",
- " }",
- "",
- " function classEscape(s) {",
- " return s",
- " .replace(/\\\\/g, \"\\\\\\\\\")", // backslash
- " .replace(/\\]/g, \"\\\\]\")", // closing bracket
- " .replace(/\\^/g, \"\\\\^\")", // caret
- " .replace(/-/g, \"\\\\-\")", // dash
- " .replace(/\\0/g, \"\\\\0\")", // null
- " .replace(/\\t/g, \"\\\\t\")", // horizontal tab
- " .replace(/\\n/g, \"\\\\n\")", // line feed
- " .replace(/\\r/g, \"\\\\r\")", // carriage return
- " .replace(/[\\x00-\\x0F]/g, function(ch) { return \"\\\\x0\" + hex(ch); })",
- " .replace(/[\\x10-\\x1F\\x7F-\\x9F]/g, function(ch) { return \"\\\\x\" + hex(ch); });",
- " }",
- "",
- " function describeExpectation(expectation) {",
- " return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);",
- " }",
- "",
- " function describeExpected(expected) {",
- " var descriptions = expected.map(describeExpectation);",
- " var i, j;",
- "",
- " descriptions.sort();",
- "",
- " if (descriptions.length > 0) {",
- " for (i = 1, j = 1; i < descriptions.length; i++) {",
- " if (descriptions[i - 1] !== descriptions[i]) {",
- " descriptions[j] = descriptions[i];",
- " j++;",
- " }",
- " }",
- " descriptions.length = j;",
- " }",
- "",
- " switch (descriptions.length) {",
- " case 1:",
- " return descriptions[0];",
- "",
- " case 2:",
- " return descriptions[0] + \" or \" + descriptions[1];",
- "",
- " default:",
- " return descriptions.slice(0, -1).join(\", \")",
- " + \", or \"",
- " + descriptions[descriptions.length - 1];",
- " }",
- " }",
- "",
- " function describeFound(found) {",
- " return found ? \"\\\"\" + literalEscape(found) + \"\\\"\" : \"end of input\";",
- " }",
- "",
- " return \"Expected \" + describeExpected(expected) + \" but \" + describeFound(found) + \" found.\";",
- "};",
- ""
- ].join("\n"));
-
- if (options.trace) {
- parts.push([
- "function peg$DefaultTracer() {",
- " this.indentLevel = 0;",
- "}",
- "",
- "peg$DefaultTracer.prototype.trace = function(event) {",
- " var that = this;",
- "",
- " function log(event) {",
- " function repeat(string, n) {",
- " var result = \"\", i;",
- "",
- " for (i = 0; i < n; i++) {",
- " result += string;",
- " }",
- "",
- " return result;",
- " }",
- "",
- " function pad(string, length) {",
- " return string + repeat(\" \", length - string.length);",
- " }",
- "",
- " if (typeof console === \"object\") {", // IE 8-10
- " console.log(",
- " event.location.start.line + \":\" + event.location.start.column + \"-\"",
- " + event.location.end.line + \":\" + event.location.end.column + \" \"",
- " + pad(event.type, 10) + \" \"",
- " + repeat(\" \", that.indentLevel) + event.rule",
- " );",
- " }",
- " }",
- "",
- " switch (event.type) {",
- " case \"rule.enter\":",
- " log(event);",
- " this.indentLevel++;",
- " break;",
- "",
- " case \"rule.match\":",
- " this.indentLevel--;",
- " log(event);",
- " break;",
- "",
- " case \"rule.fail\":",
- " this.indentLevel--;",
- " log(event);",
- " break;",
- "",
- " default:",
- " throw new Error(\"Invalid event type: \" + event.type + \".\");",
- " }",
- "};",
- ""
- ].join("\n"));
- }
-
- parts.push([
- "function peg$parse(input, options) {",
- " options = options !== undefined ? options : {};",
- "",
- " var peg$FAILED = {};",
- ""
- ].join("\n"));
-
- if (options.optimize === "size") {
- let startRuleIndices = "{ "
+ 1
+ );
+ break;
+
+ case op.MATCH_REGEXP: // MATCH_REGEXP r, a, f, ...
+ compileCondition(
+ c(bc[ip + 1]) + ".test(input.charAt(peg$currPos))",
+ 1
+ );
+ break;
+
+ case op.ACCEPT_N: // ACCEPT_N n
+ parts.push(stack.push(
+ bc[ip + 1] > 1
+ ? "input.substr(peg$currPos, " + bc[ip + 1] + ")"
+ : "input.charAt(peg$currPos)"
+ ));
+ parts.push(
+ bc[ip + 1] > 1
+ ? "peg$currPos += " + bc[ip + 1] + ";"
+ : "peg$currPos++;"
+ );
+ ip += 2;
+ break;
+
+ case op.ACCEPT_STRING: // ACCEPT_STRING s
+ parts.push(stack.push(c(bc[ip + 1])));
+ parts.push(
+ eval(ast.consts[bc[ip + 1]]).length > 1
+ ? "peg$currPos += " + eval(ast.consts[bc[ip + 1]]).length + ";"
+ : "peg$currPos++;"
+ );
+ ip += 2;
+ break;
+
+ case op.FAIL: // FAIL e
+ parts.push(stack.push("peg$FAILED"));
+ parts.push("if (peg$silentFails === 0) { peg$fail(" + c(bc[ip + 1]) + "); }");
+ ip += 2;
+ break;
+
+ case op.LOAD_SAVED_POS: // LOAD_SAVED_POS p
+ parts.push("peg$savedPos = " + stack.index(bc[ip + 1]) + ";");
+ ip += 2;
+ break;
+
+ case op.UPDATE_SAVED_POS: // UPDATE_SAVED_POS
+ parts.push("peg$savedPos = peg$currPos;");
+ ip++;
+ break;
+
+ case op.CALL: // CALL f, n, pc, p1, p2, ..., pN
+ compileCall();
+ break;
+
+ case op.RULE: // RULE r
+ parts.push(stack.push("peg$parse" + ast.rules[bc[ip + 1]].name + "()"));
+ ip += 2;
+ break;
+
+ case op.SILENT_FAILS_ON: // SILENT_FAILS_ON
+ parts.push("peg$silentFails++;");
+ ip++;
+ break;
+
+ case op.SILENT_FAILS_OFF: // SILENT_FAILS_OFF
+ parts.push("peg$silentFails--;");
+ ip++;
+ break;
+
+ default:
+ throw new Error("Invalid opcode: " + bc[ip] + ".");
+ }
+ }
+
+ return parts.join("\n");
+ }
+
+ code = compile(rule.bytecode);
+
+ parts.push("function peg$parse" + rule.name + "() {");
+
+ if (options.trace) {
+ parts.push(" var startPos = peg$currPos;");
+ }
+
+ for (let i = 0; i <= stack.maxSp; i++) {
+ stackVars[i] = s(i);
+ }
+
+ parts.push(" var " + stackVars.join(", ") + ";");
+
+ parts.push(indent2(generateRuleHeader(
+ "\"" + js.stringEscape(rule.name) + "\"",
+ asts.indexOfRule(ast, rule.name)
+ )));
+ parts.push(indent2(code));
+ parts.push(indent2(generateRuleFooter(
+ "\"" + js.stringEscape(rule.name) + "\"",
+ s(0)
+ )));
+
+ parts.push("}");
+
+ return parts.join("\n");
+ }
+
+ function generateToplevel() {
+ let parts = [];
+
+ parts.push([
+ "function peg$subclass(child, parent) {",
+ " function C() { this.constructor = child; }",
+ " C.prototype = parent.prototype;",
+ " child.prototype = new C();",
+ "}",
+ "",
+ "function peg$SyntaxError(message, expected, found, location) {",
+ " this.message = message;",
+ " this.expected = expected;",
+ " this.found = found;",
+ " this.location = location;",
+ " this.name = \"SyntaxError\";",
+ "",
+ " if (typeof Error.captureStackTrace === \"function\") {",
+ " Error.captureStackTrace(this, peg$SyntaxError);",
+ " }",
+ "}",
+ "",
+ "peg$subclass(peg$SyntaxError, Error);",
+ "",
+ "peg$SyntaxError.buildMessage = function(expected, found) {",
+ " var DESCRIBE_EXPECTATION_FNS = {",
+ " literal: function(expectation) {",
+ " return \"\\\"\" + literalEscape(expectation.text) + \"\\\"\";",
+ " },",
+ "",
+ " class: function(expectation) {",
+ " var escapedParts = expectation.parts.map(function(part) {",
+ " return Array.isArray(part)",
+ " ? classEscape(part[0]) + \"-\" + classEscape(part[1])",
+ " : classEscape(part);",
+ " });",
+ "",
+ " return \"[\" + (expectation.inverted ? \"^\" : \"\") + escapedParts + \"]\";",
+ " },",
+ "",
+ " any: function() {",
+ " return \"any character\";",
+ " },",
+ "",
+ " end: function() {",
+ " return \"end of input\";",
+ " },",
+ "",
+ " other: function(expectation) {",
+ " return expectation.description;",
+ " }",
+ " };",
+ "",
+ " function hex(ch) {",
+ " return ch.charCodeAt(0).toString(16).toUpperCase();",
+ " }",
+ "",
+ " function literalEscape(s) {",
+ " return s",
+ " .replace(/\\\\/g, \"\\\\\\\\\")", // backslash
+ " .replace(/\"/g, \"\\\\\\\"\")", // closing double quote
+ " .replace(/\\0/g, \"\\\\0\")", // null
+ " .replace(/\\t/g, \"\\\\t\")", // horizontal tab
+ " .replace(/\\n/g, \"\\\\n\")", // line feed
+ " .replace(/\\r/g, \"\\\\r\")", // carriage return
+ " .replace(/[\\x00-\\x0F]/g, function(ch) { return \"\\\\x0\" + hex(ch); })",
+ " .replace(/[\\x10-\\x1F\\x7F-\\x9F]/g, function(ch) { return \"\\\\x\" + hex(ch); });",
+ " }",
+ "",
+ " function classEscape(s) {",
+ " return s",
+ " .replace(/\\\\/g, \"\\\\\\\\\")", // backslash
+ " .replace(/\\]/g, \"\\\\]\")", // closing bracket
+ " .replace(/\\^/g, \"\\\\^\")", // caret
+ " .replace(/-/g, \"\\\\-\")", // dash
+ " .replace(/\\0/g, \"\\\\0\")", // null
+ " .replace(/\\t/g, \"\\\\t\")", // horizontal tab
+ " .replace(/\\n/g, \"\\\\n\")", // line feed
+ " .replace(/\\r/g, \"\\\\r\")", // carriage return
+ " .replace(/[\\x00-\\x0F]/g, function(ch) { return \"\\\\x0\" + hex(ch); })",
+ " .replace(/[\\x10-\\x1F\\x7F-\\x9F]/g, function(ch) { return \"\\\\x\" + hex(ch); });",
+ " }",
+ "",
+ " function describeExpectation(expectation) {",
+ " return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);",
+ " }",
+ "",
+ " function describeExpected(expected) {",
+ " var descriptions = expected.map(describeExpectation);",
+ " var i, j;",
+ "",
+ " descriptions.sort();",
+ "",
+ " if (descriptions.length > 0) {",
+ " for (i = 1, j = 1; i < descriptions.length; i++) {",
+ " if (descriptions[i - 1] !== descriptions[i]) {",
+ " descriptions[j] = descriptions[i];",
+ " j++;",
+ " }",
+ " }",
+ " descriptions.length = j;",
+ " }",
+ "",
+ " switch (descriptions.length) {",
+ " case 1:",
+ " return descriptions[0];",
+ "",
+ " case 2:",
+ " return descriptions[0] + \" or \" + descriptions[1];",
+ "",
+ " default:",
+ " return descriptions.slice(0, -1).join(\", \")",
+ " + \", or \"",
+ " + descriptions[descriptions.length - 1];",
+ " }",
+ " }",
+ "",
+ " function describeFound(found) {",
+ " return found ? \"\\\"\" + literalEscape(found) + \"\\\"\" : \"end of input\";",
+ " }",
+ "",
+ " return \"Expected \" + describeExpected(expected) + \" but \" + describeFound(found) + \" found.\";",
+ "};",
+ ""
+ ].join("\n"));
+
+ if (options.trace) {
+ parts.push([
+ "function peg$DefaultTracer() {",
+ " this.indentLevel = 0;",
+ "}",
+ "",
+ "peg$DefaultTracer.prototype.trace = function(event) {",
+ " var that = this;",
+ "",
+ " function log(event) {",
+ " function repeat(string, n) {",
+ " var result = \"\", i;",
+ "",
+ " for (i = 0; i < n; i++) {",
+ " result += string;",
+ " }",
+ "",
+ " return result;",
+ " }",
+ "",
+ " function pad(string, length) {",
+ " return string + repeat(\" \", length - string.length);",
+ " }",
+ "",
+ " if (typeof console === \"object\") {", // IE 8-10
+ " console.log(",
+ " event.location.start.line + \":\" + event.location.start.column + \"-\"",
+ " + event.location.end.line + \":\" + event.location.end.column + \" \"",
+ " + pad(event.type, 10) + \" \"",
+ " + repeat(\" \", that.indentLevel) + event.rule",
+ " );",
+ " }",
+ " }",
+ "",
+ " switch (event.type) {",
+ " case \"rule.enter\":",
+ " log(event);",
+ " this.indentLevel++;",
+ " break;",
+ "",
+ " case \"rule.match\":",
+ " this.indentLevel--;",
+ " log(event);",
+ " break;",
+ "",
+ " case \"rule.fail\":",
+ " this.indentLevel--;",
+ " log(event);",
+ " break;",
+ "",
+ " default:",
+ " throw new Error(\"Invalid event type: \" + event.type + \".\");",
+ " }",
+ "};",
+ ""
+ ].join("\n"));
+ }
+
+ parts.push([
+ "function peg$parse(input, options) {",
+ " options = options !== undefined ? options : {};",
+ "",
+ " var peg$FAILED = {};",
+ ""
+ ].join("\n"));
+
+ if (options.optimize === "size") {
+ let startRuleIndices = "{ "
+ options.allowedStartRules.map(
- r => r + ": " + asts.indexOfRule(ast, r)
- ).join(", ")
+ r => r + ": " + asts.indexOfRule(ast, r)
+ ).join(", ")
+ " }";
- let startRuleIndex = asts.indexOfRule(ast, options.allowedStartRules[0]);
-
- parts.push([
- " var peg$startRuleIndices = " + startRuleIndices + ";",
- " var peg$startRuleIndex = " + startRuleIndex + ";"
- ].join("\n"));
- } else {
- let startRuleFunctions = "{ "
+ let startRuleIndex = asts.indexOfRule(ast, options.allowedStartRules[0]);
+
+ parts.push([
+ " var peg$startRuleIndices = " + startRuleIndices + ";",
+ " var peg$startRuleIndex = " + startRuleIndex + ";"
+ ].join("\n"));
+ } else {
+ let startRuleFunctions = "{ "
+ options.allowedStartRules.map(
- r => r + ": peg$parse" + r
- ).join(", ")
+ r => r + ": peg$parse" + r
+ ).join(", ")
+ " }";
- let startRuleFunction = "peg$parse" + options.allowedStartRules[0];
-
- parts.push([
- " var peg$startRuleFunctions = " + startRuleFunctions + ";",
- " var peg$startRuleFunction = " + startRuleFunction + ";"
- ].join("\n"));
- }
-
- parts.push("");
-
- parts.push(indent2(generateTables()));
-
- parts.push([
- "",
- " var peg$currPos = 0;",
- " var peg$savedPos = 0;",
- " var peg$posDetailsCache = [{ line: 1, column: 1 }];",
- " var peg$maxFailPos = 0;",
- " var peg$maxFailExpected = [];",
- " var peg$silentFails = 0;", // 0 = report failures, > 0 = silence failures
- ""
- ].join("\n"));
-
- if (options.cache) {
- parts.push([
- " var peg$resultsCache = {};",
- ""
- ].join("\n"));
- }
-
- if (options.trace) {
- if (options.optimize === "size") {
- let ruleNames = "["
+ let startRuleFunction = "peg$parse" + options.allowedStartRules[0];
+
+ parts.push([
+ " var peg$startRuleFunctions = " + startRuleFunctions + ";",
+ " var peg$startRuleFunction = " + startRuleFunction + ";"
+ ].join("\n"));
+ }
+
+ parts.push("");
+
+ parts.push(indent2(generateTables()));
+
+ parts.push([
+ "",
+ " var peg$currPos = 0;",
+ " var peg$savedPos = 0;",
+ " var peg$posDetailsCache = [{ line: 1, column: 1 }];",
+ " var peg$maxFailPos = 0;",
+ " var peg$maxFailExpected = [];",
+ " var peg$silentFails = 0;", // 0 = report failures, > 0 = silence failures
+ ""
+ ].join("\n"));
+
+ if (options.cache) {
+ parts.push([
+ " var peg$resultsCache = {};",
+ ""
+ ].join("\n"));
+ }
+
+ if (options.trace) {
+ if (options.optimize === "size") {
+ let ruleNames = "["
+ ast.rules.map(
- r => "\"" + js.stringEscape(r.name) + "\""
- ).join(", ")
+ r => "\"" + js.stringEscape(r.name) + "\""
+ ).join(", ")
+ "]";
- parts.push([
- " var peg$ruleNames = " + ruleNames + ";",
- ""
- ].join("\n"));
- }
-
- parts.push([
- " var peg$tracer = \"tracer\" in options ? options.tracer : new peg$DefaultTracer();",
- ""
- ].join("\n"));
- }
-
- parts.push([
- " var peg$result;",
- ""
- ].join("\n"));
-
- if (options.optimize === "size") {
- parts.push([
- " if (\"startRule\" in options) {",
- " if (!(options.startRule in peg$startRuleIndices)) {",
- " throw new Error(\"Can't start parsing from rule \\\"\" + options.startRule + \"\\\".\");",
- " }",
- "",
- " peg$startRuleIndex = peg$startRuleIndices[options.startRule];",
- " }"
- ].join("\n"));
- } else {
- parts.push([
- " if (\"startRule\" in options) {",
- " if (!(options.startRule in peg$startRuleFunctions)) {",
- " throw new Error(\"Can't start parsing from rule \\\"\" + options.startRule + \"\\\".\");",
- " }",
- "",
- " peg$startRuleFunction = peg$startRuleFunctions[options.startRule];",
- " }"
- ].join("\n"));
- }
-
- parts.push([
- "",
- " function text() {",
- " return input.substring(peg$savedPos, peg$currPos);",
- " }",
- "",
- " function location() {",
- " return peg$computeLocation(peg$savedPos, peg$currPos);",
- " }",
- "",
- " function expected(description, location) {",
- " location = location !== undefined",
- " ? location",
- " : peg$computeLocation(peg$savedPos, peg$currPos);",
- "",
- " throw peg$buildStructuredError(",
- " [peg$otherExpectation(description)],",
- " input.substring(peg$savedPos, peg$currPos),",
- " location",
- " );",
- " }",
- "",
- " function error(message, location) {",
- " location = location !== undefined",
- " ? location",
- " : peg$computeLocation(peg$savedPos, peg$currPos);",
- "",
- " throw peg$buildSimpleError(message, location);",
- " }",
- "",
- " function peg$literalExpectation(text, ignoreCase) {",
- " return { type: \"literal\", text: text, ignoreCase: ignoreCase };",
- " }",
- "",
- " function peg$classExpectation(parts, inverted, ignoreCase) {",
- " return { type: \"class\", parts: parts, inverted: inverted, ignoreCase: ignoreCase };",
- " }",
- "",
- " function peg$anyExpectation() {",
- " return { type: \"any\" };",
- " }",
- "",
- " function peg$endExpectation() {",
- " return { type: \"end\" };",
- " }",
- "",
- " function peg$otherExpectation(description) {",
- " return { type: \"other\", description: description };",
- " }",
- "",
- " function peg$computePosDetails(pos) {",
- " var details = peg$posDetailsCache[pos];",
- " var p;",
- "",
- " if (details) {",
- " return details;",
- " } else {",
- " p = pos - 1;",
- " while (!peg$posDetailsCache[p]) {",
- " p--;",
- " }",
- "",
- " details = peg$posDetailsCache[p];",
- " details = {",
- " line: details.line,",
- " column: details.column",
- " };",
- "",
- " while (p < pos) {",
- " if (input.charCodeAt(p) === 10) {",
- " details.line++;",
- " details.column = 1;",
- " } else {",
- " details.column++;",
- " }",
- "",
- " p++;",
- " }",
- "",
- " peg$posDetailsCache[pos] = details;",
- "",
- " return details;",
- " }",
- " }",
- "",
- " function peg$computeLocation(startPos, endPos) {",
- " var startPosDetails = peg$computePosDetails(startPos);",
- " var endPosDetails = peg$computePosDetails(endPos);",
- "",
- " return {",
- " start: {",
- " offset: startPos,",
- " line: startPosDetails.line,",
- " column: startPosDetails.column",
- " },",
- " end: {",
- " offset: endPos,",
- " line: endPosDetails.line,",
- " column: endPosDetails.column",
- " }",
- " };",
- " }",
- "",
- " function peg$fail(expected) {",
- " if (peg$currPos < peg$maxFailPos) { return; }",
- "",
- " if (peg$currPos > peg$maxFailPos) {",
- " peg$maxFailPos = peg$currPos;",
- " peg$maxFailExpected = [];",
- " }",
- "",
- " peg$maxFailExpected.push(expected);",
- " }",
- "",
- " function peg$buildSimpleError(message, location) {",
- " return new peg$SyntaxError(message, null, null, location);",
- " }",
- "",
- " function peg$buildStructuredError(expected, found, location) {",
- " return new peg$SyntaxError(",
- " peg$SyntaxError.buildMessage(expected, found),",
- " expected,",
- " found,",
- " location",
- " );",
- " }",
- ""
- ].join("\n"));
-
- if (options.optimize === "size") {
- parts.push(indent2(generateInterpreter()));
- parts.push("");
- } else {
- ast.rules.forEach(rule => {
- parts.push(indent2(generateRuleFunction(rule)));
- parts.push("");
- });
- }
-
- if (ast.initializer) {
- parts.push(indent2(ast.initializer.code));
- parts.push("");
- }
-
- if (options.optimize === "size") {
- parts.push(" peg$result = peg$parseRule(peg$startRuleIndex);");
- } else {
- parts.push(" peg$result = peg$startRuleFunction();");
- }
-
- parts.push([
- "",
- " if (peg$result !== peg$FAILED && peg$currPos === input.length) {",
- " return peg$result;",
- " } else {",
- " if (peg$result !== peg$FAILED && peg$currPos < input.length) {",
- " peg$fail(peg$endExpectation());",
- " }",
- "",
- " throw peg$buildStructuredError(",
- " peg$maxFailExpected,",
- " peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,",
- " peg$maxFailPos < input.length",
- " ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)",
- " : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)",
- " );",
- " }",
- "}"
- ].join("\n"));
-
- return parts.join("\n");
- }
-
- function generateWrapper(toplevelCode) {
- function generateGeneratedByComment() {
- return [
- "// Generated by PEG.js 0.10.0.",
- "//",
- "// https://pegjs.org/"
- ].join("\n");
- }
-
- function generateParserObject() {
- return options.trace
- ? [
- "{",
- " SyntaxError: peg$SyntaxError,",
- " DefaultTracer: peg$DefaultTracer,",
- " parse: peg$parse",
- "}"
- ].join("\n")
- : [
- "{",
- " SyntaxError: peg$SyntaxError,",
- " parse: peg$parse",
- "}"
- ].join("\n");
- }
-
- let generators = {
- bare() {
- return [
- generateGeneratedByComment(),
- "(function() {",
- " \"use strict\";",
- "",
- indent2(toplevelCode),
- "",
- indent2("return " + generateParserObject() + ";"),
- "})()"
- ].join("\n");
- },
-
- commonjs() {
- let parts = [];
- let dependencyVars = Object.keys(options.dependencies);
-
- parts.push([
- generateGeneratedByComment(),
- "",
- "\"use strict\";",
- ""
- ].join("\n"));
-
- if (dependencyVars.length > 0) {
- dependencyVars.forEach(variable => {
- parts.push("var " + variable
+ parts.push([
+ " var peg$ruleNames = " + ruleNames + ";",
+ ""
+ ].join("\n"));
+ }
+
+ parts.push([
+ " var peg$tracer = \"tracer\" in options ? options.tracer : new peg$DefaultTracer();",
+ ""
+ ].join("\n"));
+ }
+
+ parts.push([
+ " var peg$result;",
+ ""
+ ].join("\n"));
+
+ if (options.optimize === "size") {
+ parts.push([
+ " if (\"startRule\" in options) {",
+ " if (!(options.startRule in peg$startRuleIndices)) {",
+ " throw new Error(\"Can't start parsing from rule \\\"\" + options.startRule + \"\\\".\");",
+ " }",
+ "",
+ " peg$startRuleIndex = peg$startRuleIndices[options.startRule];",
+ " }"
+ ].join("\n"));
+ } else {
+ parts.push([
+ " if (\"startRule\" in options) {",
+ " if (!(options.startRule in peg$startRuleFunctions)) {",
+ " throw new Error(\"Can't start parsing from rule \\\"\" + options.startRule + \"\\\".\");",
+ " }",
+ "",
+ " peg$startRuleFunction = peg$startRuleFunctions[options.startRule];",
+ " }"
+ ].join("\n"));
+ }
+
+ parts.push([
+ "",
+ " function text() {",
+ " return input.substring(peg$savedPos, peg$currPos);",
+ " }",
+ "",
+ " function location() {",
+ " return peg$computeLocation(peg$savedPos, peg$currPos);",
+ " }",
+ "",
+ " function expected(description, location) {",
+ " location = location !== undefined",
+ " ? location",
+ " : peg$computeLocation(peg$savedPos, peg$currPos);",
+ "",
+ " throw peg$buildStructuredError(",
+ " [peg$otherExpectation(description)],",
+ " input.substring(peg$savedPos, peg$currPos),",
+ " location",
+ " );",
+ " }",
+ "",
+ " function error(message, location) {",
+ " location = location !== undefined",
+ " ? location",
+ " : peg$computeLocation(peg$savedPos, peg$currPos);",
+ "",
+ " throw peg$buildSimpleError(message, location);",
+ " }",
+ "",
+ " function peg$literalExpectation(text, ignoreCase) {",
+ " return { type: \"literal\", text: text, ignoreCase: ignoreCase };",
+ " }",
+ "",
+ " function peg$classExpectation(parts, inverted, ignoreCase) {",
+ " return { type: \"class\", parts: parts, inverted: inverted, ignoreCase: ignoreCase };",
+ " }",
+ "",
+ " function peg$anyExpectation() {",
+ " return { type: \"any\" };",
+ " }",
+ "",
+ " function peg$endExpectation() {",
+ " return { type: \"end\" };",
+ " }",
+ "",
+ " function peg$otherExpectation(description) {",
+ " return { type: \"other\", description: description };",
+ " }",
+ "",
+ " function peg$computePosDetails(pos) {",
+ " var details = peg$posDetailsCache[pos];",
+ " var p;",
+ "",
+ " if (details) {",
+ " return details;",
+ " } else {",
+ " p = pos - 1;",
+ " while (!peg$posDetailsCache[p]) {",
+ " p--;",
+ " }",
+ "",
+ " details = peg$posDetailsCache[p];",
+ " details = {",
+ " line: details.line,",
+ " column: details.column",
+ " };",
+ "",
+ " while (p < pos) {",
+ " if (input.charCodeAt(p) === 10) {",
+ " details.line++;",
+ " details.column = 1;",
+ " } else {",
+ " details.column++;",
+ " }",
+ "",
+ " p++;",
+ " }",
+ "",
+ " peg$posDetailsCache[pos] = details;",
+ "",
+ " return details;",
+ " }",
+ " }",
+ "",
+ " function peg$computeLocation(startPos, endPos) {",
+ " var startPosDetails = peg$computePosDetails(startPos);",
+ " var endPosDetails = peg$computePosDetails(endPos);",
+ "",
+ " return {",
+ " start: {",
+ " offset: startPos,",
+ " line: startPosDetails.line,",
+ " column: startPosDetails.column",
+ " },",
+ " end: {",
+ " offset: endPos,",
+ " line: endPosDetails.line,",
+ " column: endPosDetails.column",
+ " }",
+ " };",
+ " }",
+ "",
+ " function peg$fail(expected) {",
+ " if (peg$currPos < peg$maxFailPos) { return; }",
+ "",
+ " if (peg$currPos > peg$maxFailPos) {",
+ " peg$maxFailPos = peg$currPos;",
+ " peg$maxFailExpected = [];",
+ " }",
+ "",
+ " peg$maxFailExpected.push(expected);",
+ " }",
+ "",
+ " function peg$buildSimpleError(message, location) {",
+ " return new peg$SyntaxError(message, null, null, location);",
+ " }",
+ "",
+ " function peg$buildStructuredError(expected, found, location) {",
+ " return new peg$SyntaxError(",
+ " peg$SyntaxError.buildMessage(expected, found),",
+ " expected,",
+ " found,",
+ " location",
+ " );",
+ " }",
+ ""
+ ].join("\n"));
+
+ if (options.optimize === "size") {
+ parts.push(indent2(generateInterpreter()));
+ parts.push("");
+ } else {
+ ast.rules.forEach(rule => {
+ parts.push(indent2(generateRuleFunction(rule)));
+ parts.push("");
+ });
+ }
+
+ if (ast.initializer) {
+ parts.push(indent2(ast.initializer.code));
+ parts.push("");
+ }
+
+ if (options.optimize === "size") {
+ parts.push(" peg$result = peg$parseRule(peg$startRuleIndex);");
+ } else {
+ parts.push(" peg$result = peg$startRuleFunction();");
+ }
+
+ parts.push([
+ "",
+ " if (peg$result !== peg$FAILED && peg$currPos === input.length) {",
+ " return peg$result;",
+ " } else {",
+ " if (peg$result !== peg$FAILED && peg$currPos < input.length) {",
+ " peg$fail(peg$endExpectation());",
+ " }",
+ "",
+ " throw peg$buildStructuredError(",
+ " peg$maxFailExpected,",
+ " peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,",
+ " peg$maxFailPos < input.length",
+ " ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)",
+ " : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)",
+ " );",
+ " }",
+ "}"
+ ].join("\n"));
+
+ return parts.join("\n");
+ }
+
+ function generateWrapper(toplevelCode) {
+ function generateGeneratedByComment() {
+ return [
+ "// Generated by PEG.js 0.10.0.",
+ "//",
+ "// https://pegjs.org/"
+ ].join("\n");
+ }
+
+ function generateParserObject() {
+ return options.trace
+ ? [
+ "{",
+ " SyntaxError: peg$SyntaxError,",
+ " DefaultTracer: peg$DefaultTracer,",
+ " parse: peg$parse",
+ "}"
+ ].join("\n")
+ : [
+ "{",
+ " SyntaxError: peg$SyntaxError,",
+ " parse: peg$parse",
+ "}"
+ ].join("\n");
+ }
+
+ let generators = {
+ bare() {
+ return [
+ generateGeneratedByComment(),
+ "(function() {",
+ " \"use strict\";",
+ "",
+ indent2(toplevelCode),
+ "",
+ indent2("return " + generateParserObject() + ";"),
+ "})()"
+ ].join("\n");
+ },
+
+ commonjs() {
+ let parts = [];
+ let dependencyVars = Object.keys(options.dependencies);
+
+ parts.push([
+ generateGeneratedByComment(),
+ "",
+ "\"use strict\";",
+ ""
+ ].join("\n"));
+
+ if (dependencyVars.length > 0) {
+ dependencyVars.forEach(variable => {
+ parts.push("var " + variable
+ " = require(\""
+ js.stringEscape(options.dependencies[variable])
+ "\");"
- );
- });
- parts.push("");
- }
-
- parts.push([
- toplevelCode,
- "",
- "module.exports = " + generateParserObject() + ";",
- ""
- ].join("\n"));
-
- return parts.join("\n");
- },
-
- amd() {
- let dependencyVars = Object.keys(options.dependencies);
- let dependencyIds = dependencyVars.map(v => options.dependencies[v]);
- let dependencies = "["
+ );
+ });
+ parts.push("");
+ }
+
+ parts.push([
+ toplevelCode,
+ "",
+ "module.exports = " + generateParserObject() + ";",
+ ""
+ ].join("\n"));
+
+ return parts.join("\n");
+ },
+
+ amd() {
+ let dependencyVars = Object.keys(options.dependencies);
+ let dependencyIds = dependencyVars.map(v => options.dependencies[v]);
+ let dependencies = "["
+ dependencyIds.map(
- id => "\"" + js.stringEscape(id) + "\""
- ).join(", ")
+ id => "\"" + js.stringEscape(id) + "\""
+ ).join(", ")
+ "]";
- let params = dependencyVars.join(", ");
-
- return [
- generateGeneratedByComment(),
- "define(" + dependencies + ", function(" + params + ") {",
- " \"use strict\";",
- "",
- indent2(toplevelCode),
- "",
- indent2("return " + generateParserObject() + ";"),
- "});",
- ""
- ].join("\n");
- },
-
- globals() {
- return [
- generateGeneratedByComment(),
- "(function(root) {",
- " \"use strict\";",
- "",
- indent2(toplevelCode),
- "",
- indent2("root." + options.exportVar + " = " + generateParserObject() + ";"),
- "})(this);",
- ""
- ].join("\n");
- },
-
- umd() {
- let parts = [];
- let dependencyVars = Object.keys(options.dependencies);
- let dependencyIds = dependencyVars.map(v => options.dependencies[v]);
- let dependencies = "["
+ let params = dependencyVars.join(", ");
+
+ return [
+ generateGeneratedByComment(),
+ "define(" + dependencies + ", function(" + params + ") {",
+ " \"use strict\";",
+ "",
+ indent2(toplevelCode),
+ "",
+ indent2("return " + generateParserObject() + ";"),
+ "});",
+ ""
+ ].join("\n");
+ },
+
+ globals() {
+ return [
+ generateGeneratedByComment(),
+ "(function(root) {",
+ " \"use strict\";",
+ "",
+ indent2(toplevelCode),
+ "",
+ indent2("root." + options.exportVar + " = " + generateParserObject() + ";"),
+ "})(this);",
+ ""
+ ].join("\n");
+ },
+
+ umd() {
+ let parts = [];
+ let dependencyVars = Object.keys(options.dependencies);
+ let dependencyIds = dependencyVars.map(v => options.dependencies[v]);
+ let dependencies = "["
+ dependencyIds.map(
- id => "\"" + js.stringEscape(id) + "\""
- ).join(", ")
+ id => "\"" + js.stringEscape(id) + "\""
+ ).join(", ")
+ "]";
- let requires = dependencyIds.map(
- id => "require(\"" + js.stringEscape(id) + "\")"
- ).join(", ");
- let params = dependencyVars.join(", ");
-
- parts.push([
- generateGeneratedByComment(),
- "(function(root, factory) {",
- " if (typeof define === \"function\" && define.amd) {",
- " define(" + dependencies + ", factory);",
- " } else if (typeof module === \"object\" && module.exports) {",
- " module.exports = factory(" + requires + ");"
- ].join("\n"));
-
- if (options.exportVar !== null) {
- parts.push([
- " } else {",
- " root." + options.exportVar + " = factory();"
- ].join("\n"));
- }
-
- parts.push([
- " }",
- "})(this, function(" + params + ") {",
- " \"use strict\";",
- "",
- indent2(toplevelCode),
- "",
- indent2("return " + generateParserObject() + ";"),
- "});",
- ""
- ].join("\n"));
-
- return parts.join("\n");
- }
- };
-
- return generators[options.format]();
- }
-
- ast.code = generateWrapper(generateToplevel());
+ let requires = dependencyIds.map(
+ id => "require(\"" + js.stringEscape(id) + "\")"
+ ).join(", ");
+ let params = dependencyVars.join(", ");
+
+ parts.push([
+ generateGeneratedByComment(),
+ "(function(root, factory) {",
+ " if (typeof define === \"function\" && define.amd) {",
+ " define(" + dependencies + ", factory);",
+ " } else if (typeof module === \"object\" && module.exports) {",
+ " module.exports = factory(" + requires + ");"
+ ].join("\n"));
+
+ if (options.exportVar !== null) {
+ parts.push([
+ " } else {",
+ " root." + options.exportVar + " = factory();"
+ ].join("\n"));
+ }
+
+ parts.push([
+ " }",
+ "})(this, function(" + params + ") {",
+ " \"use strict\";",
+ "",
+ indent2(toplevelCode),
+ "",
+ indent2("return " + generateParserObject() + ";"),
+ "});",
+ ""
+ ].join("\n"));
+
+ return parts.join("\n");
+ }
+ };
+
+ return generators[options.format]();
+ }
+
+ ast.code = generateWrapper(generateToplevel());
}
module.exports = generateJS;
diff --git a/lib/compiler/passes/remove-proxy-rules.js b/lib/compiler/passes/remove-proxy-rules.js
index a95cc3c..75b4492 100644
--- a/lib/compiler/passes/remove-proxy-rules.js
+++ b/lib/compiler/passes/remove-proxy-rules.js
@@ -4,36 +4,36 @@ let visitor = require("../visitor");
// Removes proxy rules -- that is, rules that only delegate to other rule.
function removeProxyRules(ast, options) {
- function isProxyRule(node) {
- return node.type === "rule" && node.expression.type === "rule_ref";
- }
-
- function replaceRuleRefs(ast, from, to) {
- let replace = visitor.build({
- rule_ref(node) {
- if (node.name === from) {
- node.name = to;
- }
- }
- });
-
- replace(ast);
- }
-
- let indices = [];
-
- ast.rules.forEach((rule, i) => {
- if (isProxyRule(rule)) {
- replaceRuleRefs(ast, rule.name, rule.expression.name);
- if (options.allowedStartRules.indexOf(rule.name) === -1) {
- indices.push(i);
- }
- }
- });
-
- indices.reverse();
-
- indices.forEach(i => { ast.rules.splice(i, 1); });
+ function isProxyRule(node) {
+ return node.type === "rule" && node.expression.type === "rule_ref";
+ }
+
+ function replaceRuleRefs(ast, from, to) {
+ let replace = visitor.build({
+ rule_ref(node) {
+ if (node.name === from) {
+ node.name = to;
+ }
+ }
+ });
+
+ replace(ast);
+ }
+
+ let indices = [];
+
+ ast.rules.forEach((rule, i) => {
+ if (isProxyRule(rule)) {
+ replaceRuleRefs(ast, rule.name, rule.expression.name);
+ if (options.allowedStartRules.indexOf(rule.name) === -1) {
+ indices.push(i);
+ }
+ }
+ });
+
+ indices.reverse();
+
+ indices.forEach(i => { ast.rules.splice(i, 1); });
}
module.exports = removeProxyRules;
diff --git a/lib/compiler/passes/report-duplicate-labels.js b/lib/compiler/passes/report-duplicate-labels.js
index 7e15be6..8bce1f8 100644
--- a/lib/compiler/passes/report-duplicate-labels.js
+++ b/lib/compiler/passes/report-duplicate-labels.js
@@ -5,58 +5,58 @@ let visitor = require("../visitor");
// Checks that each label is defined only once within each scope.
function reportDuplicateLabels(ast) {
- function cloneEnv(env) {
- let clone = {};
+ function cloneEnv(env) {
+ let clone = {};
- Object.keys(env).forEach(name => {
- clone[name] = env[name];
- });
+ Object.keys(env).forEach(name => {
+ clone[name] = env[name];
+ });
- return clone;
- }
+ return clone;
+ }
- function checkExpressionWithClonedEnv(node, env) {
- check(node.expression, cloneEnv(env));
- }
+ function checkExpressionWithClonedEnv(node, env) {
+ check(node.expression, cloneEnv(env));
+ }
- let check = visitor.build({
- rule(node) {
- check(node.expression, { });
- },
+ let check = visitor.build({
+ rule(node) {
+ check(node.expression, { });
+ },
- choice(node, env) {
- node.alternatives.forEach(alternative => {
- check(alternative, cloneEnv(env));
- });
- },
+ choice(node, env) {
+ node.alternatives.forEach(alternative => {
+ check(alternative, cloneEnv(env));
+ });
+ },
- action: checkExpressionWithClonedEnv,
+ action: checkExpressionWithClonedEnv,
- labeled(node, env) {
- if (Object.prototype.hasOwnProperty.call(env, node.label)) {
- throw new GrammarError(
- "Label \"" + node.label + "\" is already defined "
+ labeled(node, env) {
+ if (Object.prototype.hasOwnProperty.call(env, node.label)) {
+ throw new GrammarError(
+ "Label \"" + node.label + "\" is already defined "
+ "at line " + env[node.label].start.line + ", "
+ "column " + env[node.label].start.column + ".",
- node.location
- );
- }
+ node.location
+ );
+ }
- check(node.expression, env);
+ check(node.expression, env);
- env[node.label] = node.location;
- },
+ env[node.label] = node.location;
+ },
- text: checkExpressionWithClonedEnv,
- simple_and: checkExpressionWithClonedEnv,
- simple_not: checkExpressionWithClonedEnv,
- optional: checkExpressionWithClonedEnv,
- zero_or_more: checkExpressionWithClonedEnv,
- one_or_more: checkExpressionWithClonedEnv,
- group: checkExpressionWithClonedEnv
- });
+ text: checkExpressionWithClonedEnv,
+ simple_and: checkExpressionWithClonedEnv,
+ simple_not: checkExpressionWithClonedEnv,
+ optional: checkExpressionWithClonedEnv,
+ zero_or_more: checkExpressionWithClonedEnv,
+ one_or_more: checkExpressionWithClonedEnv,
+ group: checkExpressionWithClonedEnv
+ });
- check(ast);
+ check(ast);
}
module.exports = reportDuplicateLabels;
diff --git a/lib/compiler/passes/report-duplicate-rules.js b/lib/compiler/passes/report-duplicate-rules.js
index ba318bb..775b5a1 100644
--- a/lib/compiler/passes/report-duplicate-rules.js
+++ b/lib/compiler/passes/report-duplicate-rules.js
@@ -5,24 +5,24 @@ let visitor = require("../visitor");
// Checks that each rule is defined only once.
function reportDuplicateRules(ast) {
- let rules = {};
+ let rules = {};
- let check = visitor.build({
- rule(node) {
- if (Object.prototype.hasOwnProperty.call(rules, node.name)) {
- throw new GrammarError(
- "Rule \"" + node.name + "\" is already defined "
+ let check = visitor.build({
+ rule(node) {
+ if (Object.prototype.hasOwnProperty.call(rules, node.name)) {
+ throw new GrammarError(
+ "Rule \"" + node.name + "\" is already defined "
+ "at line " + rules[node.name].start.line + ", "
+ "column " + rules[node.name].start.column + ".",
- node.location
- );
- }
+ node.location
+ );
+ }
- rules[node.name] = node.location;
- }
- });
+ rules[node.name] = node.location;
+ }
+ });
- check(ast);
+ check(ast);
}
module.exports = reportDuplicateRules;
diff --git a/lib/compiler/passes/report-infinite-recursion.js b/lib/compiler/passes/report-infinite-recursion.js
index d5ba312..77c09ff 100644
--- a/lib/compiler/passes/report-infinite-recursion.js
+++ b/lib/compiler/passes/report-infinite-recursion.js
@@ -15,40 +15,40 @@ let visitor = require("../visitor");
// In general, if a rule reference can be reached without consuming any input,
// it can lead to left recursion.
function reportInfiniteRecursion(ast) {
- let visitedRules = [];
+ let visitedRules = [];
- let check = visitor.build({
- rule(node) {
- visitedRules.push(node.name);
- check(node.expression);
- visitedRules.pop(node.name);
- },
+ let check = visitor.build({
+ rule(node) {
+ visitedRules.push(node.name);
+ check(node.expression);
+ visitedRules.pop(node.name);
+ },
- sequence(node) {
- node.elements.every(element => {
- check(element);
+ sequence(node) {
+ node.elements.every(element => {
+ check(element);
- return !asts.alwaysConsumesOnSuccess(ast, element);
- });
- },
+ return !asts.alwaysConsumesOnSuccess(ast, element);
+ });
+ },
- rule_ref(node) {
- if (visitedRules.indexOf(node.name) !== -1) {
- visitedRules.push(node.name);
+ rule_ref(node) {
+ if (visitedRules.indexOf(node.name) !== -1) {
+ visitedRules.push(node.name);
- throw new GrammarError(
- "Possible infinite loop when parsing (left recursion: "
+ throw new GrammarError(
+ "Possible infinite loop when parsing (left recursion: "
+ visitedRules.join(" -> ")
+ ").",
- node.location
- );
- }
+ node.location
+ );
+ }
- check(asts.findRule(ast, node.name));
- }
- });
+ check(asts.findRule(ast, node.name));
+ }
+ });
- check(ast);
+ check(ast);
}
module.exports = reportInfiniteRecursion;
diff --git a/lib/compiler/passes/report-infinite-repetition.js b/lib/compiler/passes/report-infinite-repetition.js
index f521931..7b52439 100644
--- a/lib/compiler/passes/report-infinite-repetition.js
+++ b/lib/compiler/passes/report-infinite-repetition.js
@@ -7,27 +7,27 @@ let visitor = require("../visitor");
// Reports expressions that don't consume any input inside |*| or |+| in the
// grammar, which prevents infinite loops in the generated parser.
function reportInfiniteRepetition(ast) {
- let check = visitor.build({
- zero_or_more(node) {
- if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
- throw new GrammarError(
- "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
- node.location
- );
- }
- },
+ let check = visitor.build({
+ zero_or_more(node) {
+ if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
+ throw new GrammarError(
+ "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
+ node.location
+ );
+ }
+ },
- one_or_more(node) {
- if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
- throw new GrammarError(
- "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
- node.location
- );
- }
- }
- });
+ one_or_more(node) {
+ if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
+ throw new GrammarError(
+ "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
+ node.location
+ );
+ }
+ }
+ });
- check(ast);
+ check(ast);
}
module.exports = reportInfiniteRepetition;
diff --git a/lib/compiler/passes/report-undefined-rules.js b/lib/compiler/passes/report-undefined-rules.js
index 0f5d941..2ed7f7f 100644
--- a/lib/compiler/passes/report-undefined-rules.js
+++ b/lib/compiler/passes/report-undefined-rules.js
@@ -6,18 +6,18 @@ let visitor = require("../visitor");
// Checks that all referenced rules exist.
function reportUndefinedRules(ast) {
- let check = visitor.build({
- rule_ref(node) {
- if (!asts.findRule(ast, node.name)) {
- throw new GrammarError(
- "Rule \"" + node.name + "\" is not defined.",
- node.location
- );
- }
- }
- });
+ let check = visitor.build({
+ rule_ref(node) {
+ if (!asts.findRule(ast, node.name)) {
+ throw new GrammarError(
+ "Rule \"" + node.name + "\" is not defined.",
+ node.location
+ );
+ }
+ }
+ });
- check(ast);
+ check(ast);
}
module.exports = reportUndefinedRules;
diff --git a/lib/compiler/visitor.js b/lib/compiler/visitor.js
index 8f5d08a..a42c324 100644
--- a/lib/compiler/visitor.js
+++ b/lib/compiler/visitor.js
@@ -2,74 +2,74 @@
// Simple AST node visitor builder.
let visitor = {
- build(functions) {
- function visit(node) {
- return functions[node.type].apply(null, arguments);
- }
+ build(functions) {
+ function visit(node) {
+ return functions[node.type].apply(null, arguments);
+ }
- function visitNop() {
- // Do nothing.
- }
+ function visitNop() {
+ // Do nothing.
+ }
- function visitExpression(node) {
- let extraArgs = Array.prototype.slice.call(arguments, 1);
+ function visitExpression(node) {
+ let extraArgs = Array.prototype.slice.call(arguments, 1);
- visit.apply(null, [node.expression].concat(extraArgs));
- }
+ visit.apply(null, [node.expression].concat(extraArgs));
+ }
- function visitChildren(property) {
- return function(node) {
- let extraArgs = Array.prototype.slice.call(arguments, 1);
+ function visitChildren(property) {
+ return function(node) {
+ let extraArgs = Array.prototype.slice.call(arguments, 1);
- node[property].forEach(child => {
- visit.apply(null, [child].concat(extraArgs));
- });
- };
- }
+ node[property].forEach(child => {
+ visit.apply(null, [child].concat(extraArgs));
+ });
+ };
+ }
- const DEFAULT_FUNCTIONS = {
- grammar(node) {
- let extraArgs = Array.prototype.slice.call(arguments, 1);
+ const DEFAULT_FUNCTIONS = {
+ grammar(node) {
+ let extraArgs = Array.prototype.slice.call(arguments, 1);
- if (node.initializer) {
- visit.apply(null, [node.initializer].concat(extraArgs));
- }
+ if (node.initializer) {
+ visit.apply(null, [node.initializer].concat(extraArgs));
+ }
- node.rules.forEach(rule => {
- visit.apply(null, [rule].concat(extraArgs));
- });
- },
+ node.rules.forEach(rule => {
+ visit.apply(null, [rule].concat(extraArgs));
+ });
+ },
- initializer: visitNop,
- rule: visitExpression,
- named: visitExpression,
- choice: visitChildren("alternatives"),
- action: visitExpression,
- sequence: visitChildren("elements"),
- labeled: visitExpression,
- text: visitExpression,
- simple_and: visitExpression,
- simple_not: visitExpression,
- optional: visitExpression,
- zero_or_more: visitExpression,
- one_or_more: visitExpression,
- group: visitExpression,
- semantic_and: visitNop,
- semantic_not: visitNop,
- rule_ref: visitNop,
- literal: visitNop,
- class: visitNop,
- any: visitNop
- };
+ initializer: visitNop,
+ rule: visitExpression,
+ named: visitExpression,
+ choice: visitChildren("alternatives"),
+ action: visitExpression,
+ sequence: visitChildren("elements"),
+ labeled: visitExpression,
+ text: visitExpression,
+ simple_and: visitExpression,
+ simple_not: visitExpression,
+ optional: visitExpression,
+ zero_or_more: visitExpression,
+ one_or_more: visitExpression,
+ group: visitExpression,
+ semantic_and: visitNop,
+ semantic_not: visitNop,
+ rule_ref: visitNop,
+ literal: visitNop,
+ class: visitNop,
+ any: visitNop
+ };
- Object.keys(DEFAULT_FUNCTIONS).forEach(type => {
- if (!Object.prototype.hasOwnProperty.call(functions, type)) {
- functions[type] = DEFAULT_FUNCTIONS[type];
- }
- });
+ Object.keys(DEFAULT_FUNCTIONS).forEach(type => {
+ if (!Object.prototype.hasOwnProperty.call(functions, type)) {
+ functions[type] = DEFAULT_FUNCTIONS[type];
+ }
+ });
- return visit;
- }
+ return visit;
+ }
};
module.exports = visitor;
diff --git a/lib/grammar-error.js b/lib/grammar-error.js
index ce3fb98..8b698c6 100644
--- a/lib/grammar-error.js
+++ b/lib/grammar-error.js
@@ -2,15 +2,15 @@
// Thrown when the grammar contains an error.
class GrammarError {
- constructor(message, location) {
- this.name = "GrammarError";
- this.message = message;
- this.location = location;
+ constructor(message, location) {
+ this.name = "GrammarError";
+ this.message = message;
+ this.location = location;
- if (typeof Error.captureStackTrace === "function") {
- Error.captureStackTrace(this, GrammarError);
- }
- }
+ if (typeof Error.captureStackTrace === "function") {
+ Error.captureStackTrace(this, GrammarError);
+ }
+ }
}
module.exports = GrammarError;
diff --git a/lib/peg.js b/lib/peg.js
index a4639fe..4da4195 100644
--- a/lib/peg.js
+++ b/lib/peg.js
@@ -5,50 +5,50 @@ let compiler = require("./compiler");
let parser = require("./parser");
let peg = {
- // PEG.js version (uses semantic versioning).
- VERSION: "0.10.0",
-
- GrammarError: GrammarError,
- parser: parser,
- compiler: compiler,
-
- // Generates a parser from a specified grammar and returns it.
- //
- // The grammar must be a string in the format described by the metagramar in
- // the parser.pegjs file.
- //
- // Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
- // |peg.GrammarError| if it contains a semantic error. Note that not all
- // errors are detected during the generation and some may protrude to the
- // generated parser and cause its malfunction.
- generate(grammar, options) {
- options = options !== undefined ? options : {};
-
- function convertPasses(passes) {
- let converted = {};
-
- Object.keys(passes).forEach(stage => {
- converted[stage] = Object.keys(passes[stage])
- .map(name => passes[stage][name]);
- });
-
- return converted;
- }
-
- let plugins = "plugins" in options ? options.plugins : [];
- let config = {
- parser: peg.parser,
- passes: convertPasses(peg.compiler.passes)
- };
-
- plugins.forEach(p => { p.use(config, options); });
-
- return peg.compiler.compile(
- config.parser.parse(grammar),
- config.passes,
- options
- );
- }
+ // PEG.js version (uses semantic versioning).
+ VERSION: "0.10.0",
+
+ GrammarError: GrammarError,
+ parser: parser,
+ compiler: compiler,
+
+ // Generates a parser from a specified grammar and returns it.
+ //
+ // The grammar must be a string in the format described by the metagramar in
+ // the parser.pegjs file.
+ //
+ // Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
+ // |peg.GrammarError| if it contains a semantic error. Note that not all
+ // errors are detected during the generation and some may protrude to the
+ // generated parser and cause its malfunction.
+ generate(grammar, options) {
+ options = options !== undefined ? options : {};
+
+ function convertPasses(passes) {
+ let converted = {};
+
+ Object.keys(passes).forEach(stage => {
+ converted[stage] = Object.keys(passes[stage])
+ .map(name => passes[stage][name]);
+ });
+
+ return converted;
+ }
+
+ let plugins = "plugins" in options ? options.plugins : [];
+ let config = {
+ parser: peg.parser,
+ passes: convertPasses(peg.compiler.passes)
+ };
+
+ plugins.forEach(p => { p.use(config, options); });
+
+ return peg.compiler.compile(
+ config.parser.parse(grammar),
+ config.passes,
+ options
+ );
+ }
};
module.exports = peg;
diff --git a/test/api/generated-parser-api.spec.js b/test/api/generated-parser-api.spec.js
index b1827bb..23cc461 100644
--- a/test/api/generated-parser-api.spec.js
+++ b/test/api/generated-parser-api.spec.js
@@ -9,160 +9,160 @@ let sinon = require("sinon");
let expect = chai.expect;
describe("generated parser API", function() {
- describe("parse", function() {
- it("parses input", function() {
- let parser = peg.generate("start = 'a'");
-
- expect(parser.parse("a")).to.equal("a");
- });
-
- it("throws an exception on syntax error", function() {
- let parser = peg.generate("start = 'a'");
-
- expect(() => { parser.parse("b"); }).to.throw();
- });
-
- describe("start rule", function() {
- let parser = peg.generate([
- "a = 'x' { return 'a'; }",
- "b = 'x' { return 'b'; }",
- "c = 'x' { return 'c'; }"
- ].join("\n"), { allowedStartRules: ["b", "c"] });
-
- describe("when |startRule| is not set", function() {
- it("starts parsing from the first allowed rule", function() {
- expect(parser.parse("x")).to.equal("b");
- });
- });
-
- describe("when |startRule| is set to an allowed rule", function() {
- it("starts parsing from specified rule", function() {
- expect(parser.parse("x", { startRule: "b" })).to.equal("b");
- expect(parser.parse("x", { startRule: "c" })).to.equal("c");
- });
- });
-
- describe("when |startRule| is set to a disallowed start rule", function() {
- it("throws an exception", function() {
- expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
- });
- });
- });
-
- describe("tracing", function() {
- let parser = peg.generate([
- "start = a / b",
- "a = 'a'",
- "b = 'b'"
- ].join("\n"), { trace: true });
-
- describe("default tracer", function() {
- it("traces using console.log (if console is defined)", function() {
- let messages = [
- "1:1-1:1 rule.enter start",
- "1:1-1:1 rule.enter a",
- "1:1-1:1 rule.fail a",
- "1:1-1:1 rule.enter b",
- "1:1-1:2 rule.match b",
- "1:1-1:2 rule.match start"
- ];
-
- if (typeof console === "object") {
- sinon.stub(console, "log");
- }
-
- try {
- parser.parse("b");
-
- if (typeof console === "object") {
- expect(console.log.callCount).to.equal(messages.length);
- messages.forEach((message, index) => {
- let call = console.log.getCall(index);
- expect(call.calledWithExactly(message)).to.equal(true);
- });
- }
- } finally {
- if (typeof console === "object") {
- console.log.restore();
- }
- }
- });
- });
-
- describe("custom tracers", function() {
- describe("trace", function() {
- it("receives tracing events", function() {
- let events = [
- {
- type: "rule.enter",
- rule: "start",
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 0, line: 1, column: 1 }
- }
- },
- {
- type: "rule.enter",
- rule: "a",
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 0, line: 1, column: 1 }
- }
- },
- {
- type: "rule.fail",
- rule: "a",
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 0, line: 1, column: 1 }
- }
- },
- {
- type: "rule.enter",
- rule: "b",
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 0, line: 1, column: 1 }
- }
- },
- {
- type: "rule.match",
- rule: "b",
- result: "b",
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 1, line: 1, column: 2 }
- }
- },
- {
- type: "rule.match",
- rule: "start",
- result: "b",
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 1, line: 1, column: 2 }
- }
- }
- ];
-
- let tracer = { trace: sinon.spy() };
-
- parser.parse("b", { tracer: tracer });
-
- expect(tracer.trace.callCount).to.equal(events.length);
- events.forEach((event, index) => {
- let call = tracer.trace.getCall(index);
- expect(call.calledWithExactly(event)).to.equal(true);
- });
- });
- });
- });
- });
-
- it("accepts custom options", function() {
- let parser = peg.generate("start = 'a'");
-
- parser.parse("a", { foo: 42 });
- });
- });
+ describe("parse", function() {
+ it("parses input", function() {
+ let parser = peg.generate("start = 'a'");
+
+ expect(parser.parse("a")).to.equal("a");
+ });
+
+ it("throws an exception on syntax error", function() {
+ let parser = peg.generate("start = 'a'");
+
+ expect(() => { parser.parse("b"); }).to.throw();
+ });
+
+ describe("start rule", function() {
+ let parser = peg.generate([
+ "a = 'x' { return 'a'; }",
+ "b = 'x' { return 'b'; }",
+ "c = 'x' { return 'c'; }"
+ ].join("\n"), { allowedStartRules: ["b", "c"] });
+
+ describe("when |startRule| is not set", function() {
+ it("starts parsing from the first allowed rule", function() {
+ expect(parser.parse("x")).to.equal("b");
+ });
+ });
+
+ describe("when |startRule| is set to an allowed rule", function() {
+ it("starts parsing from specified rule", function() {
+ expect(parser.parse("x", { startRule: "b" })).to.equal("b");
+ expect(parser.parse("x", { startRule: "c" })).to.equal("c");
+ });
+ });
+
+ describe("when |startRule| is set to a disallowed start rule", function() {
+ it("throws an exception", function() {
+ expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
+ });
+ });
+ });
+
+ describe("tracing", function() {
+ let parser = peg.generate([
+ "start = a / b",
+ "a = 'a'",
+ "b = 'b'"
+ ].join("\n"), { trace: true });
+
+ describe("default tracer", function() {
+ it("traces using console.log (if console is defined)", function() {
+ let messages = [
+ "1:1-1:1 rule.enter start",
+ "1:1-1:1 rule.enter a",
+ "1:1-1:1 rule.fail a",
+ "1:1-1:1 rule.enter b",
+ "1:1-1:2 rule.match b",
+ "1:1-1:2 rule.match start"
+ ];
+
+ if (typeof console === "object") {
+ sinon.stub(console, "log");
+ }
+
+ try {
+ parser.parse("b");
+
+ if (typeof console === "object") {
+ expect(console.log.callCount).to.equal(messages.length);
+ messages.forEach((message, index) => {
+ let call = console.log.getCall(index);
+ expect(call.calledWithExactly(message)).to.equal(true);
+ });
+ }
+ } finally {
+ if (typeof console === "object") {
+ console.log.restore();
+ }
+ }
+ });
+ });
+
+ describe("custom tracers", function() {
+ describe("trace", function() {
+ it("receives tracing events", function() {
+ let events = [
+ {
+ type: "rule.enter",
+ rule: "start",
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 0, line: 1, column: 1 }
+ }
+ },
+ {
+ type: "rule.enter",
+ rule: "a",
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 0, line: 1, column: 1 }
+ }
+ },
+ {
+ type: "rule.fail",
+ rule: "a",
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 0, line: 1, column: 1 }
+ }
+ },
+ {
+ type: "rule.enter",
+ rule: "b",
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 0, line: 1, column: 1 }
+ }
+ },
+ {
+ type: "rule.match",
+ rule: "b",
+ result: "b",
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 1, line: 1, column: 2 }
+ }
+ },
+ {
+ type: "rule.match",
+ rule: "start",
+ result: "b",
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 1, line: 1, column: 2 }
+ }
+ }
+ ];
+
+ let tracer = { trace: sinon.spy() };
+
+ parser.parse("b", { tracer: tracer });
+
+ expect(tracer.trace.callCount).to.equal(events.length);
+ events.forEach((event, index) => {
+ let call = tracer.trace.getCall(index);
+ expect(call.calledWithExactly(event)).to.equal(true);
+ });
+ });
+ });
+ });
+ });
+
+ it("accepts custom options", function() {
+ let parser = peg.generate("start = 'a'");
+
+ parser.parse("a", { foo: 42 });
+ });
+ });
});
diff --git a/test/api/pegjs-api.spec.js b/test/api/pegjs-api.spec.js
index 75a4cbb..df2f4bd 100644
--- a/test/api/pegjs-api.spec.js
+++ b/test/api/pegjs-api.spec.js
@@ -7,194 +7,194 @@ let sinon = require("sinon");
let expect = chai.expect;
describe("PEG.js API", function() {
- describe("generate", function() {
- it("generates a parser", function() {
- let parser = peg.generate("start = 'a'");
-
- expect(parser).to.be.an("object");
- expect(parser.parse("a")).to.equal("a");
- });
-
- it("throws an exception on syntax error", function() {
- expect(() => { peg.generate("start = @"); }).to.throw();
- });
-
- it("throws an exception on semantic error", function() {
- expect(() => { peg.generate("start = undefined"); }).to.throw();
- });
-
- describe("allowed start rules", function() {
- let grammar = [
- "a = 'x'",
- "b = 'x'",
- "c = 'x'"
- ].join("\n");
-
- // The |allowedStartRules| option is implemented separately for each
- // optimization mode, so we need to test it in both.
-
- describe("when optimizing for parsing speed", function() {
- describe("when |allowedStartRules| is not set", function() {
- it("generated parser can start only from the first rule", function() {
- let parser = peg.generate(grammar, { optimize: "speed" });
-
- expect(parser.parse("x", { startRule: "a" })).to.equal("x");
- expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
- expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
- });
- });
-
- describe("when |allowedStartRules| is set", function() {
- it("generated parser can start only from specified rules", function() {
- let parser = peg.generate(grammar, {
- optimize: "speed",
- allowedStartRules: ["b", "c"]
- });
-
- expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
- expect(parser.parse("x", { startRule: "b" })).to.equal("x");
- expect(parser.parse("x", { startRule: "c" })).to.equal("x");
- });
- });
- });
-
- describe("when optimizing for code size", function() {
- describe("when |allowedStartRules| is not set", function() {
- it("generated parser can start only from the first rule", function() {
- let parser = peg.generate(grammar, { optimize: "size" });
-
- expect(parser.parse("x", { startRule: "a" })).to.equal("x");
- expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
- expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
- });
- });
-
- describe("when |allowedStartRules| is set", function() {
- it("generated parser can start only from specified rules", function() {
- let parser = peg.generate(grammar, {
- optimize: "size",
- allowedStartRules: ["b", "c"]
- });
-
- expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
- expect(parser.parse("x", { startRule: "b" })).to.equal("x");
- expect(parser.parse("x", { startRule: "c" })).to.equal("x");
- });
- });
- });
- });
-
- describe("intermediate results caching", function() {
- let grammar = [
- "{ var n = 0; }",
- "start = (a 'b') / (a 'c') { return n; }",
- "a = 'a' { n++; }"
- ].join("\n");
-
- describe("when |cache| is not set", function() {
- it("generated parser doesn't cache intermediate parse results", function() {
- let parser = peg.generate(grammar);
-
- expect(parser.parse("ac")).to.equal(2);
- });
- });
-
- describe("when |cache| is set to |false|", function() {
- it("generated parser doesn't cache intermediate parse results", function() {
- let parser = peg.generate(grammar, { cache: false });
-
- expect(parser.parse("ac")).to.equal(2);
- });
- });
-
- describe("when |cache| is set to |true|", function() {
- it("generated parser caches intermediate parse results", function() {
- let parser = peg.generate(grammar, { cache: true });
-
- expect(parser.parse("ac")).to.equal(1);
- });
- });
- });
-
- describe("tracing", function() {
- let grammar = "start = 'a'";
-
- describe("when |trace| is not set", function() {
- it("generated parser doesn't trace", function() {
- let parser = peg.generate(grammar);
- let tracer = { trace: sinon.spy() };
-
- parser.parse("a", { tracer: tracer });
-
- expect(tracer.trace.called).to.equal(false);
- });
- });
-
- describe("when |trace| is set to |false|", function() {
- it("generated parser doesn't trace", function() {
- let parser = peg.generate(grammar, { trace: false });
- let tracer = { trace: sinon.spy() };
-
- parser.parse("a", { tracer: tracer });
-
- expect(tracer.trace.called).to.equal(false);
- });
- });
-
- describe("when |trace| is set to |true|", function() {
- it("generated parser traces", function() {
- let parser = peg.generate(grammar, { trace: true });
- let tracer = { trace: sinon.spy() };
-
- parser.parse("a", { tracer: tracer });
-
- expect(tracer.trace.called).to.equal(true);
- });
- });
- });
-
- // The |optimize| option isn't tested because there is no meaningful way to
- // write the tests without turning this into a performance test.
-
- describe("output", function() {
- let grammar = "start = 'a'";
-
- describe("when |output| is not set", function() {
- it("returns generated parser object", function() {
- let parser = peg.generate(grammar);
-
- expect(parser).to.be.an("object");
- expect(parser.parse("a")).to.equal("a");
- });
- });
-
- describe("when |output| is set to |\"parser\"|", function() {
- it("returns generated parser object", function() {
- let parser = peg.generate(grammar, { output: "parser" });
-
- expect(parser).to.be.an("object");
- expect(parser.parse("a")).to.equal("a");
- });
- });
-
- describe("when |output| is set to |\"source\"|", function() {
- it("returns generated parser source code", function() {
- let source = peg.generate(grammar, { output: "source" });
-
- expect(source).to.be.a("string");
- expect(eval(source).parse("a")).to.equal("a");
- });
- });
- });
+ describe("generate", function() {
+ it("generates a parser", function() {
+ let parser = peg.generate("start = 'a'");
+
+ expect(parser).to.be.an("object");
+ expect(parser.parse("a")).to.equal("a");
+ });
+
+ it("throws an exception on syntax error", function() {
+ expect(() => { peg.generate("start = @"); }).to.throw();
+ });
+
+ it("throws an exception on semantic error", function() {
+ expect(() => { peg.generate("start = undefined"); }).to.throw();
+ });
+
+ describe("allowed start rules", function() {
+ let grammar = [
+ "a = 'x'",
+ "b = 'x'",
+ "c = 'x'"
+ ].join("\n");
+
+ // The |allowedStartRules| option is implemented separately for each
+ // optimization mode, so we need to test it in both.
+
+ describe("when optimizing for parsing speed", function() {
+ describe("when |allowedStartRules| is not set", function() {
+ it("generated parser can start only from the first rule", function() {
+ let parser = peg.generate(grammar, { optimize: "speed" });
+
+ expect(parser.parse("x", { startRule: "a" })).to.equal("x");
+ expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
+ expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
+ });
+ });
+
+ describe("when |allowedStartRules| is set", function() {
+ it("generated parser can start only from specified rules", function() {
+ let parser = peg.generate(grammar, {
+ optimize: "speed",
+ allowedStartRules: ["b", "c"]
+ });
+
+ expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
+ expect(parser.parse("x", { startRule: "b" })).to.equal("x");
+ expect(parser.parse("x", { startRule: "c" })).to.equal("x");
+ });
+ });
+ });
+
+ describe("when optimizing for code size", function() {
+ describe("when |allowedStartRules| is not set", function() {
+ it("generated parser can start only from the first rule", function() {
+ let parser = peg.generate(grammar, { optimize: "size" });
+
+ expect(parser.parse("x", { startRule: "a" })).to.equal("x");
+ expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
+ expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
+ });
+ });
+
+ describe("when |allowedStartRules| is set", function() {
+ it("generated parser can start only from specified rules", function() {
+ let parser = peg.generate(grammar, {
+ optimize: "size",
+ allowedStartRules: ["b", "c"]
+ });
+
+ expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
+ expect(parser.parse("x", { startRule: "b" })).to.equal("x");
+ expect(parser.parse("x", { startRule: "c" })).to.equal("x");
+ });
+ });
+ });
+ });
+
+ describe("intermediate results caching", function() {
+ let grammar = [
+ "{ var n = 0; }",
+ "start = (a 'b') / (a 'c') { return n; }",
+ "a = 'a' { n++; }"
+ ].join("\n");
+
+ describe("when |cache| is not set", function() {
+ it("generated parser doesn't cache intermediate parse results", function() {
+ let parser = peg.generate(grammar);
+
+ expect(parser.parse("ac")).to.equal(2);
+ });
+ });
+
+ describe("when |cache| is set to |false|", function() {
+ it("generated parser doesn't cache intermediate parse results", function() {
+ let parser = peg.generate(grammar, { cache: false });
+
+ expect(parser.parse("ac")).to.equal(2);
+ });
+ });
+
+ describe("when |cache| is set to |true|", function() {
+ it("generated parser caches intermediate parse results", function() {
+ let parser = peg.generate(grammar, { cache: true });
+
+ expect(parser.parse("ac")).to.equal(1);
+ });
+ });
+ });
+
+ describe("tracing", function() {
+ let grammar = "start = 'a'";
+
+ describe("when |trace| is not set", function() {
+ it("generated parser doesn't trace", function() {
+ let parser = peg.generate(grammar);
+ let tracer = { trace: sinon.spy() };
+
+ parser.parse("a", { tracer: tracer });
+
+ expect(tracer.trace.called).to.equal(false);
+ });
+ });
+
+ describe("when |trace| is set to |false|", function() {
+ it("generated parser doesn't trace", function() {
+ let parser = peg.generate(grammar, { trace: false });
+ let tracer = { trace: sinon.spy() };
+
+ parser.parse("a", { tracer: tracer });
+
+ expect(tracer.trace.called).to.equal(false);
+ });
+ });
+
+ describe("when |trace| is set to |true|", function() {
+ it("generated parser traces", function() {
+ let parser = peg.generate(grammar, { trace: true });
+ let tracer = { trace: sinon.spy() };
+
+ parser.parse("a", { tracer: tracer });
+
+ expect(tracer.trace.called).to.equal(true);
+ });
+ });
+ });
+
+ // The |optimize| option isn't tested because there is no meaningful way to
+ // write the tests without turning this into a performance test.
+
+ describe("output", function() {
+ let grammar = "start = 'a'";
+
+ describe("when |output| is not set", function() {
+ it("returns generated parser object", function() {
+ let parser = peg.generate(grammar);
+
+ expect(parser).to.be.an("object");
+ expect(parser.parse("a")).to.equal("a");
+ });
+ });
+
+ describe("when |output| is set to |\"parser\"|", function() {
+ it("returns generated parser object", function() {
+ let parser = peg.generate(grammar, { output: "parser" });
+
+ expect(parser).to.be.an("object");
+ expect(parser.parse("a")).to.equal("a");
+ });
+ });
+
+ describe("when |output| is set to |\"source\"|", function() {
+ it("returns generated parser source code", function() {
+ let source = peg.generate(grammar, { output: "source" });
+
+ expect(source).to.be.a("string");
+ expect(eval(source).parse("a")).to.equal("a");
+ });
+ });
+ });
- // The |format|, |exportVars|, and |dependencies| options are not tested
- // becasue there is no meaningful way to thest their effects without turning
- // this into an integration test.
+ // The |format|, |exportVars|, and |dependencies| options are not tested
+ // becasue there is no meaningful way to thest their effects without turning
+ // this into an integration test.
- // The |plugins| option is tested in plugin API tests.
+ // The |plugins| option is tested in plugin API tests.
- it("accepts custom options", function() {
- peg.generate("start = 'a'", { foo: 42 });
- });
- });
+ it("accepts custom options", function() {
+ peg.generate("start = 'a'", { foo: 42 });
+ });
+ });
});
diff --git a/test/api/plugin-api.spec.js b/test/api/plugin-api.spec.js
index 84a5bf4..d1738e6 100644
--- a/test/api/plugin-api.spec.js
+++ b/test/api/plugin-api.spec.js
@@ -6,123 +6,123 @@ 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");
- });
- });
+ 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");
+ });
+ });
});
diff --git a/test/behavior/generated-parser-behavior.spec.js b/test/behavior/generated-parser-behavior.spec.js
index 0ef1d56..cb71b99 100644
--- a/test/behavior/generated-parser-behavior.spec.js
+++ b/test/behavior/generated-parser-behavior.spec.js
@@ -9,1548 +9,1548 @@ let sinon = require("sinon");
let expect = chai.expect;
describe("generated parser behavior", function() {
- function varyOptimizationOptions(block) {
- function clone(object) {
- let result = {};
-
- Object.keys(object).forEach(key => {
- result[key] = object[key];
- });
-
- return result;
- }
-
- let optionsVariants = [
- { cache: false, optimize: "speed", trace: false },
- { cache: false, optimize: "speed", trace: true },
- { cache: false, optimize: "size", trace: false },
- { cache: false, optimize: "size", trace: true },
- { cache: true, optimize: "speed", trace: false },
- { cache: true, optimize: "speed", trace: true },
- { cache: true, optimize: "size", trace: false },
- { cache: true, optimize: "size", trace: true }
- ];
-
- optionsVariants.forEach(variant => {
- describe(
- "with options " + chai.util.inspect(variant),
- function() { block(clone(variant)); }
- );
- });
- }
-
- function withConsoleStub(block) {
- if (typeof console === "object") {
- sinon.stub(console, "log");
- }
-
- try {
- return block();
- } finally {
- if (typeof console === "object") {
- console.log.restore();
- }
- }
- }
-
- function helpers(chai, utils) {
- let Assertion = chai.Assertion;
-
- Assertion.addMethod("parse", function(input, expected, options) {
- options = options !== undefined ? options : {};
-
- let result = withConsoleStub(() =>
- utils.flag(this, "object").parse(input, options)
- );
-
- if (expected !== undefined) {
- this.assert(
- utils.eql(result, expected),
- "expected #{this} to parse input as #{exp} but got #{act}",
- "expected #{this} to not parse input as #{exp}",
- expected,
- result,
- !utils.flag(this, "negate")
- );
- }
- });
-
- Assertion.addMethod("failToParse", function(input, props, options) {
- options = options !== undefined ? options : {};
-
- let passed, result;
-
- try {
- result = withConsoleStub(() =>
- utils.flag(this, "object").parse(input, options)
- );
- passed = true;
- } catch (e) {
- result = e;
- passed = false;
- }
-
- this.assert(
- !passed,
- "expected #{this} to fail to parse input but got #{act}",
- "expected #{this} to not fail to parse input but #{act} was thrown",
- null,
- result
- );
-
- if (!passed && props !== undefined) {
- Object.keys(props).forEach(key => {
- new Assertion(result).to.have.property(key)
- .that.is.deep.equal(props[key]);
- });
- }
- });
- }
-
- // Helper activation needs to put inside a |beforeEach| block because the
- // helpers conflict with the ones in test/unit/parser.spec.js.
- beforeEach(function() {
- chai.use(helpers);
- });
-
- varyOptimizationOptions(function(options) {
- describe("initializer", function() {
- it("executes the code before parsing starts", function() {
- let parser = peg.generate([
- "{ var result = 42; }",
- "start = 'a' { return result; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("a", 42);
- });
-
- describe("available variables and functions", function() {
- it("|options| contains options", function() {
- let parser = peg.generate([
- "{ var result = options; }",
- "start = 'a' { return result; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("a", { a: 42 }, { a: 42 });
- });
- });
- });
-
- describe("rule", function() {
- if (options.cache) {
- it("caches rule match results", function() {
- let parser = peg.generate([
- "{ var n = 0; }",
- "start = (a 'b') / (a 'c') { return n; }",
- "a = 'a' { n++; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("ac", 1);
- });
- } else {
- it("doesn't cache rule match results", function() {
- let parser = peg.generate([
- "{ var n = 0; }",
- "start = (a 'b') / (a 'c') { return n; }",
- "a = 'a' { n++; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("ac", 2);
- });
- }
-
- describe("when the expression matches", function() {
- it("returns its match result", function() {
- let parser = peg.generate("start = 'a'");
-
- expect(parser).to.parse("a", "a");
- });
- });
-
- describe("when the expression doesn't match", function() {
- describe("without display name", function() {
- it("reports match failure and doesn't record any expectation", function() {
- let parser = peg.generate("start = 'a'");
-
- expect(parser).to.failToParse("b", {
- expected: [{ type: "literal", text: "a", ignoreCase: false }]
- });
- });
- });
-
- describe("with display name", function() {
- it("reports match failure and records an expectation of type \"other\"", function() {
- let parser = peg.generate("start 'start' = 'a'");
-
- expect(parser).to.failToParse("b", {
- expected: [{ type: "other", description: "start" }]
- });
- });
-
- it("discards any expectations recorded when matching the expression", function() {
- let parser = peg.generate("start 'start' = 'a'");
-
- expect(parser).to.failToParse("b", {
- expected: [{ type: "other", description: "start" }]
- });
- });
- });
- });
- });
-
- describe("literal", function() {
- describe("matching", function() {
- it("matches empty literals", function() {
- let parser = peg.generate("start = ''", options);
-
- expect(parser).to.parse("");
- });
-
- it("matches one-character literals", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.failToParse("b");
- });
-
- it("matches multi-character literals", function() {
- let parser = peg.generate("start = 'abcd'", options);
-
- expect(parser).to.parse("abcd");
- expect(parser).to.failToParse("efgh");
- });
-
- it("is case sensitive without the \"i\" flag", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.failToParse("A");
- });
-
- it("is case insensitive with the \"i\" flag", function() {
- let parser = peg.generate("start = 'a'i", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.parse("A");
- });
- });
-
- describe("when it matches", function() {
- it("returns the matched text", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.parse("a", "a");
- });
-
- it("consumes the matched text", function() {
- let parser = peg.generate("start = 'a' .", options);
-
- expect(parser).to.parse("ab");
- });
- });
-
- describe("when it doesn't match", function() {
- it("reports match failure and records an expectation of type \"literal\"", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("b", {
- expected: [{ type: "literal", text: "a", ignoreCase: false }]
- });
- });
- });
- });
-
- describe("character class", function() {
- describe("matching", function() {
- it("matches empty classes", function() {
- let parser = peg.generate("start = []", options);
-
- expect(parser).to.failToParse("a");
- });
-
- it("matches classes with a character list", function() {
- let parser = peg.generate("start = [abc]", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.parse("b");
- expect(parser).to.parse("c");
- expect(parser).to.failToParse("d");
- });
-
- it("matches classes with a character range", function() {
- let parser = peg.generate("start = [a-c]", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.parse("b");
- expect(parser).to.parse("c");
- expect(parser).to.failToParse("d");
- });
-
- it("matches inverted classes", function() {
- let parser = peg.generate("start = [^a]", options);
-
- expect(parser).to.failToParse("a");
- expect(parser).to.parse("b");
- });
-
- it("is case sensitive without the \"i\" flag", function() {
- let parser = peg.generate("start = [a]", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.failToParse("A");
- });
-
- it("is case insensitive with the \"i\" flag", function() {
- let parser = peg.generate("start = [a]i", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.parse("A");
- });
- });
-
- describe("when it matches", function() {
- it("returns the matched character", function() {
- let parser = peg.generate("start = [a]", options);
-
- expect(parser).to.parse("a", "a");
- });
-
- it("consumes the matched character", function() {
- let parser = peg.generate("start = [a] .", options);
-
- expect(parser).to.parse("ab");
- });
- });
-
- describe("when it doesn't match", function() {
- it("reports match failure and records an expectation of type \"class\"", function() {
- let parser = peg.generate("start = [a]", options);
-
- expect(parser).to.failToParse("b", {
- expected: [{ type: "class", parts: ["a"], inverted: false, ignoreCase: false }]
- });
- });
- });
- });
-
- describe("dot", function() {
- describe("matching", function() {
- it("matches any character", function() {
- let parser = peg.generate("start = .", options);
-
- expect(parser).to.parse("a");
- expect(parser).to.parse("b");
- expect(parser).to.parse("c");
- });
- });
-
- describe("when it matches", function() {
- it("returns the matched character", function() {
- let parser = peg.generate("start = .", options);
-
- expect(parser).to.parse("a", "a");
- });
-
- it("consumes the matched character", function() {
- let parser = peg.generate("start = . .", options);
-
- expect(parser).to.parse("ab");
- });
- });
-
- describe("when it doesn't match", function() {
- it("reports match failure and records an expectation of type \"any\"", function() {
- let parser = peg.generate("start = .", options);
-
- expect(parser).to.failToParse("", {
- expected: [{ type: "any" }]
- });
- });
- });
- });
-
- describe("rule reference", function() {
- describe("when referenced rule's expression matches", function() {
- it("returns its result", function() {
- let parser = peg.generate([
- "start = a",
- "a = 'a'"
- ].join("\n"), options);
-
- expect(parser).to.parse("a", "a");
- });
- });
-
- describe("when referenced rule's expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate([
- "start = a",
- "a = 'a'"
- ].join("\n"), options);
-
- expect(parser).to.failToParse("b");
- });
- });
- });
-
- describe("positive semantic predicate", function() {
- describe("when the code returns a truthy value", function() {
- it("returns |undefined|", function() {
- // The |""| is needed so that the parser doesn't return just
- // |undefined| which we can't compare against in |toParse| due to the
- // way optional parameters work.
- let parser = peg.generate("start = &{ return true; } ''", options);
-
- expect(parser).to.parse("", [undefined, ""]);
- });
- });
-
- describe("when the code returns a falsey value", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = &{ return false; }", options);
-
- expect(parser).to.failToParse("");
- });
- });
-
- describe("label variables", function() {
- describe("in containing sequence", function() {
- it("can access variables defined by preceding labeled elements", function() {
- let parser = peg.generate(
- "start = a:'a' &{ return a === 'a'; }",
- options
- );
-
- expect(parser).to.parse("a");
- });
-
- it("cannot access variable defined by labeled predicate element", function() {
- let parser = peg.generate(
- "start = 'a' b:&{ return b === undefined; } 'c'",
- options
- );
-
- expect(parser).to.failToParse("ac");
- });
-
- it("cannot access variables defined by following labeled elements", function() {
- let parser = peg.generate(
- "start = &{ return a === 'a'; } a:'a'",
- options
- );
-
- expect(parser).to.failToParse("a");
- });
-
- it("cannot access variables defined by subexpressions", function() {
- let testcases = [
- {
- grammar: "start = (a:'a') &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')? &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')* &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')+ &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = $(a:'a') &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = &(a:'a') 'a' &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = !(a:'a') 'b' &{ return a === 'a'; }",
- input: "b"
- },
- {
- grammar: "start = b:(a:'a') &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = ('a' b:'b' 'c') &{ return b === 'b'; }",
- input: "abc"
- },
- {
- grammar: "start = (a:'a' { return a; }) &{ return a === 'a'; }",
- input: "a"
- },
- {
- grammar: "start = ('a' / b:'b' / 'c') &{ return b === 'b'; }",
- input: "b"
- }
- ];
-
- testcases.forEach(testcase => {
- let parser = peg.generate(testcase.grammar, options);
- expect(parser).to.failToParse(testcase.input);
- });
- });
- });
-
- describe("in outer sequence", function() {
- it("can access variables defined by preceding labeled elements", function() {
- let parser = peg.generate(
- "start = a:'a' ('b' &{ return a === 'a'; })",
- options
- );
-
- expect(parser).to.parse("ab");
- });
-
- it("cannot access variable defined by labeled predicate element", function() {
- let parser = peg.generate(
- "start = 'a' b:('b' &{ return b === undefined; }) 'c'",
- options
- );
-
- expect(parser).to.failToParse("abc");
- });
-
- it("cannot access variables defined by following labeled elements", function() {
- let parser = peg.generate(
- "start = ('a' &{ return b === 'b'; }) b:'b'",
- options
- );
-
- expect(parser).to.failToParse("ab");
- });
- });
- });
-
- describe("initializer variables & functions", function() {
- it("can access variables defined in the initializer", function() {
- let parser = peg.generate([
- "{ var v = 42 }",
- "start = &{ return v === 42; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("");
- });
-
- it("can access functions defined in the initializer", function() {
- let parser = peg.generate([
- "{ function f() { return 42; } }",
- "start = &{ return f() === 42; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("");
- });
- });
-
- describe("available variables & functions", function() {
- it("|options| contains options", function() {
- let parser = peg.generate([
- "{ var result; }",
- "start = &{ result = options; return true; } { return result; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("", { a: 42 }, { a: 42 });
- });
-
- it("|location| returns current location info", function() {
- let parser = peg.generate([
- "{ var result; }",
- "start = line (nl+ line)* { return result; }",
- "line = thing (' '+ thing)*",
- "thing = digit / mark",
- "digit = [0-9]",
- "mark = &{ result = location(); return true; } 'x'",
- "nl = '\\r'? '\\n'"
- ].join("\n"), options);
-
- expect(parser).to.parse("1\n2\n\n3\n\n\n4 5 x", {
- start: { offset: 13, line: 7, column: 5 },
- end: { offset: 13, line: 7, column: 5 }
- });
-
- // Newline representations
- expect(parser).to.parse("1\nx", { // Unix
- start: { offset: 2, line: 2, column: 1 },
- end: { offset: 2, line: 2, column: 1 }
- });
- expect(parser).to.parse("1\r\nx", { // Windows
- start: { offset: 3, line: 2, column: 1 },
- end: { offset: 3, line: 2, column: 1 }
- });
- });
- });
- });
-
- describe("negative semantic predicate", function() {
- describe("when the code returns a falsey value", function() {
- it("returns |undefined|", function() {
- // The |""| is needed so that the parser doesn't return just
- // |undefined| which we can't compare against in |toParse| due to the
- // way optional parameters work.
- let parser = peg.generate("start = !{ return false; } ''", options);
-
- expect(parser).to.parse("", [undefined, ""]);
- });
- });
-
- describe("when the code returns a truthy value", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = !{ return true; }", options);
-
- expect(parser).to.failToParse("");
- });
- });
-
- describe("label variables", function() {
- describe("in containing sequence", function() {
- it("can access variables defined by preceding labeled elements", function() {
- let parser = peg.generate(
- "start = a:'a' !{ return a !== 'a'; }",
- options
- );
-
- expect(parser).to.parse("a");
- });
-
- it("cannot access variable defined by labeled predicate element", function() {
- let parser = peg.generate(
- "start = 'a' b:!{ return b !== undefined; } 'c'",
- options
- );
-
- expect(parser).to.failToParse("ac");
- });
-
- it("cannot access variables defined by following labeled elements", function() {
- let parser = peg.generate(
- "start = !{ return a !== 'a'; } a:'a'",
- options
- );
-
- expect(parser).to.failToParse("a");
- });
-
- it("cannot access variables defined by subexpressions", function() {
- let testcases = [
- {
- grammar: "start = (a:'a') !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')? !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')* !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')+ !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = $(a:'a') !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = &(a:'a') 'a' !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = !(a:'a') 'b' !{ return a !== 'a'; }",
- input: "b"
- },
- {
- grammar: "start = b:(a:'a') !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = ('a' b:'b' 'c') !{ return b !== 'b'; }",
- input: "abc"
- },
- {
- grammar: "start = (a:'a' { return a; }) !{ return a !== 'a'; }",
- input: "a"
- },
- {
- grammar: "start = ('a' / b:'b' / 'c') !{ return b !== 'b'; }",
- input: "b"
- }
- ];
-
- testcases.forEach(testcase => {
- let parser = peg.generate(testcase.grammar, options);
- expect(parser).to.failToParse(testcase.input);
- });
- });
- });
-
- describe("in outer sequence", function() {
- it("can access variables defined by preceding labeled elements", function() {
- let parser = peg.generate(
- "start = a:'a' ('b' !{ return a !== 'a'; })",
- options
- );
-
- expect(parser).to.parse("ab");
- });
-
- it("cannot access variable defined by labeled predicate element", function() {
- let parser = peg.generate(
- "start = 'a' b:('b' !{ return b !== undefined; }) 'c'",
- options
- );
-
- expect(parser).to.failToParse("abc");
- });
-
- it("cannot access variables defined by following labeled elements", function() {
- let parser = peg.generate(
- "start = ('a' !{ return b !== 'b'; }) b:'b'",
- options
- );
-
- expect(parser).to.failToParse("ab");
- });
- });
- });
-
- describe("initializer variables & functions", function() {
- it("can access variables defined in the initializer", function() {
- let parser = peg.generate([
- "{ var v = 42 }",
- "start = !{ return v !== 42; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("");
- });
-
- it("can access functions defined in the initializer", function() {
- let parser = peg.generate([
- "{ function f() { return 42; } }",
- "start = !{ return f() !== 42; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("");
- });
- });
-
- describe("available variables & functions", function() {
- it("|options| contains options", function() {
- let parser = peg.generate([
- "{ var result; }",
- "start = !{ result = options; return false; } { return result; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("", { a: 42 }, { a: 42 });
- });
-
- it("|location| returns current location info", function() {
- let parser = peg.generate([
- "{ var result; }",
- "start = line (nl+ line)* { return result; }",
- "line = thing (' '+ thing)*",
- "thing = digit / mark",
- "digit = [0-9]",
- "mark = !{ result = location(); return false; } 'x'",
- "nl = '\\r'? '\\n'"
- ].join("\n"), options);
-
- expect(parser).to.parse("1\n2\n\n3\n\n\n4 5 x", {
- start: { offset: 13, line: 7, column: 5 },
- end: { offset: 13, line: 7, column: 5 }
- });
-
- // Newline representations
- expect(parser).to.parse("1\nx", { // Unix
- start: { offset: 2, line: 2, column: 1 },
- end: { offset: 2, line: 2, column: 1 }
- });
- expect(parser).to.parse("1\r\nx", { // Windows
- start: { offset: 3, line: 2, column: 1 },
- end: { offset: 3, line: 2, column: 1 }
- });
- });
- });
- });
-
- describe("group", function() {
- describe("when the expression matches", function() {
- it("returns its match result", function() {
- let parser = peg.generate("start = ('a')", options);
-
- expect(parser).to.parse("a", "a");
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = ('a')", options);
-
- expect(parser).to.failToParse("b");
- });
- });
- });
-
- describe("optional", function() {
- describe("when the expression matches", function() {
- it("returns its match result", function() {
- let parser = peg.generate("start = 'a'?", options);
-
- expect(parser).to.parse("a", "a");
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("returns |null|", function() {
- let parser = peg.generate("start = 'a'?", options);
-
- expect(parser).to.parse("", null);
- });
- });
- });
-
- describe("zero or more", function() {
- describe("when the expression matches zero or more times", function() {
- it("returns an array of its match results", function() {
- let parser = peg.generate("start = 'a'*", options);
-
- expect(parser).to.parse("", []);
- expect(parser).to.parse("a", ["a"]);
- expect(parser).to.parse("aaa", ["a", "a", "a"]);
- });
- });
- });
-
- describe("one or more", function() {
- describe("when the expression matches one or more times", function() {
- it("returns an array of its match results", function() {
- let parser = peg.generate("start = 'a'+", options);
-
- expect(parser).to.parse("a", ["a"]);
- expect(parser).to.parse("aaa", ["a", "a", "a"]);
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = 'a'+", options);
-
- expect(parser).to.failToParse("");
- });
- });
- });
-
- describe("text", function() {
- describe("when the expression matches", function() {
- it("returns the matched text", function() {
- let parser = peg.generate("start = $('a' 'b' 'c')", options);
-
- expect(parser).to.parse("abc", "abc");
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = $('a')", options);
-
- expect(parser).to.failToParse("b");
- });
- });
- });
-
- describe("positive simple predicate", function() {
- describe("when the expression matches", function() {
- it("returns |undefined|", function() {
- let parser = peg.generate("start = &'a' 'a'", options);
-
- expect(parser).to.parse("a", [undefined, "a"]);
- });
-
- it("resets parse position", function() {
- let parser = peg.generate("start = &'a' 'a'", options);
-
- expect(parser).to.parse("a");
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = &'a'", options);
-
- expect(parser).to.failToParse("b");
- });
-
- it("discards any expectations recorded when matching the expression", function() {
- let parser = peg.generate("start = 'a' / &'b' / 'c'", options);
-
- expect(parser).to.failToParse("d", {
- expected: [
- { type: "literal", text: "a", ignoreCase: false },
- { type: "literal", text: "c", ignoreCase: false }
- ]
- });
- });
- });
- });
-
- describe("negative simple predicate", function() {
- describe("when the expression matches", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = !'a'", options);
-
- expect(parser).to.failToParse("a");
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("returns |undefined|", function() {
- let parser = peg.generate("start = !'a' 'b'", options);
-
- expect(parser).to.parse("b", [undefined, "b"]);
- });
-
- it("resets parse position", function() {
- let parser = peg.generate("start = !'a' 'b'", options);
-
- expect(parser).to.parse("b");
- });
-
- it("discards any expectations recorded when matching the expression", function() {
- let parser = peg.generate("start = 'a' / !'b' / 'c'", options);
-
- expect(parser).to.failToParse("b", {
- expected: [
- { type: "literal", text: "a", ignoreCase: false },
- { type: "literal", text: "c", ignoreCase: false }
- ]
- });
- });
- });
- });
-
- describe("label", function() {
- describe("when the expression matches", function() {
- it("returns its match result", function() {
- let parser = peg.generate("start = a:'a'", options);
-
- expect(parser).to.parse("a", "a");
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = a:'a'", options);
-
- expect(parser).to.failToParse("b");
- });
- });
- });
-
- describe("sequence", function() {
- describe("when all expressions match", function() {
- it("returns an array of their match results", function() {
- let parser = peg.generate("start = 'a' 'b' 'c'", options);
-
- expect(parser).to.parse("abc", ["a", "b", "c"]);
- });
- });
-
- describe("when any expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = 'a' 'b' 'c'", options);
-
- expect(parser).to.failToParse("dbc");
- expect(parser).to.failToParse("adc");
- expect(parser).to.failToParse("abd");
- });
-
- it("resets parse position", function() {
- let parser = peg.generate("start = 'a' 'b' / 'a'", options);
-
- expect(parser).to.parse("a", "a");
- });
- });
- });
-
- describe("action", function() {
- describe("when the expression matches", function() {
- it("returns the value returned by the code", function() {
- let parser = peg.generate("start = 'a' { return 42; }", options);
-
- expect(parser).to.parse("a", 42);
- });
-
- describe("label variables", function() {
- describe("in the expression", function() {
- it("can access variable defined by labeled expression", function() {
- let parser = peg.generate("start = a:'a' { return a; }", options);
-
- expect(parser).to.parse("a", "a");
- });
-
- it("can access variables defined by labeled sequence elements", function() {
- let parser = peg.generate(
- "start = a:'a' b:'b' c:'c' { return [a, b, c]; }",
- options
- );
-
- expect(parser).to.parse("abc", ["a", "b", "c"]);
- });
-
- it("cannot access variables defined by subexpressions", function() {
- let testcases = [
- {
- grammar: "start = (a:'a') { return a; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')? { return a; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')* { return a; }",
- input: "a"
- },
- {
- grammar: "start = (a:'a')+ { return a; }",
- input: "a"
- },
- {
- grammar: "start = $(a:'a') { return a; }",
- input: "a"
- },
- {
- grammar: "start = &(a:'a') 'a' { return a; }",
- input: "a"
- },
- {
- grammar: "start = !(a:'a') 'b' { return a; }",
- input: "b"
- },
- {
- grammar: "start = b:(a:'a') { return a; }",
- input: "a"
- },
- {
- grammar: "start = ('a' b:'b' 'c') { return b; }",
- input: "abc"
- },
- {
- grammar: "start = (a:'a' { return a; }) { return a; }",
- input: "a"
- },
- {
- grammar: "start = ('a' / b:'b' / 'c') { return b; }",
- input: "b"
- }
- ];
-
- testcases.forEach(testcase => {
- let parser = peg.generate(testcase.grammar, options);
- expect(parser).to.failToParse(testcase.input);
- });
- });
- });
-
- describe("in outer sequence", function() {
- it("can access variables defined by preceding labeled elements", function() {
- let parser = peg.generate(
- "start = a:'a' ('b' { return a; })",
- options
- );
-
- expect(parser).to.parse("ab", ["a", "a"]);
- });
-
- it("cannot access variable defined by labeled action element", function() {
- let parser = peg.generate(
- "start = 'a' b:('b' { return b; }) c:'c'",
- options
- );
-
- expect(parser).to.failToParse("abc");
- });
-
- it("cannot access variables defined by following labeled elements", function() {
- let parser = peg.generate(
- "start = ('a' { return b; }) b:'b'",
- options
- );
-
- expect(parser).to.failToParse("ab");
- });
- });
- });
-
- describe("initializer variables & functions", function() {
- it("can access variables defined in the initializer", function() {
- let parser = peg.generate([
- "{ var v = 42 }",
- "start = 'a' { return v; }"
- ].join("\n"), options);
-
- expect(parser).to.parse("a", 42);
- });
-
- it("can access functions defined in the initializer", function() {
- let parser = peg.generate([
- "{ function f() { return 42; } }",
- "start = 'a' { return f(); }"
- ].join("\n"), options);
-
- expect(parser).to.parse("a", 42);
- });
- });
-
- describe("available variables & functions", function() {
- it("|options| contains options", function() {
- let parser = peg.generate(
- "start = 'a' { return options; }",
- options
- );
-
- expect(parser).to.parse("a", { a: 42 }, { a: 42 });
- });
-
- it("|text| returns text matched by the expression", function() {
- let parser = peg.generate(
- "start = 'a' { return text(); }",
- options
- );
-
- expect(parser).to.parse("a", "a");
- });
-
- it("|location| returns location info of the expression", function() {
- let parser = peg.generate([
- "{ var result; }",
- "start = line (nl+ line)* { return result; }",
- "line = thing (' '+ thing)*",
- "thing = digit / mark",
- "digit = [0-9]",
- "mark = 'x' { result = location(); }",
- "nl = '\\r'? '\\n'"
- ].join("\n"), options);
-
- expect(parser).to.parse("1\n2\n\n3\n\n\n4 5 x", {
- start: { offset: 13, line: 7, column: 5 },
- end: { offset: 14, line: 7, column: 6 }
- });
-
- // Newline representations
- expect(parser).to.parse("1\nx", { // Unix
- start: { offset: 2, line: 2, column: 1 },
- end: { offset: 3, line: 2, column: 2 }
- });
- expect(parser).to.parse("1\r\nx", { // Windows
- start: { offset: 3, line: 2, column: 1 },
- end: { offset: 4, line: 2, column: 2 }
- });
- });
-
- describe("|expected|", function() {
- it("terminates parsing and throws an exception", function() {
- let parser = peg.generate(
- "start = 'a' { expected('a'); }",
- options
- );
-
- expect(parser).to.failToParse("a", {
- message: "Expected a but \"a\" found.",
- expected: [{ type: "other", description: "a" }],
- found: "a",
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 1, line: 1, column: 2 }
- }
- });
- });
-
- it("allows to set custom location info", function() {
- let parser = peg.generate([
- "start = 'a' {",
- " expected('a', {",
- " start: { offset: 1, line: 1, column: 2 },",
- " end: { offset: 2, line: 1, column: 3 }",
- " });",
- "}"
- ].join("\n"), options);
-
- expect(parser).to.failToParse("a", {
- message: "Expected a but \"a\" found.",
- expected: [{ type: "other", description: "a" }],
- found: "a",
- location: {
- start: { offset: 1, line: 1, column: 2 },
- end: { offset: 2, line: 1, column: 3 }
- }
- });
- });
- });
-
- describe("|error|", function() {
- it("terminates parsing and throws an exception", function() {
- let parser = peg.generate(
- "start = 'a' { error('a'); }",
- options
- );
-
- expect(parser).to.failToParse("a", {
- message: "a",
- found: null,
- expected: null,
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 1, line: 1, column: 2 }
- }
- });
- });
-
- it("allows to set custom location info", function() {
- let parser = peg.generate([
- "start = 'a' {",
- " error('a', {",
- " start: { offset: 1, line: 1, column: 2 },",
- " end: { offset: 2, line: 1, column: 3 }",
- " });",
- "}"
- ].join("\n"), options);
-
- expect(parser).to.failToParse("a", {
- message: "a",
- expected: null,
- found: null,
- location: {
- start: { offset: 1, line: 1, column: 2 },
- end: { offset: 2, line: 1, column: 3 }
- }
- });
- });
- });
- });
- });
-
- describe("when the expression doesn't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = 'a' { return 42; }", options);
-
- expect(parser).to.failToParse("b");
- });
-
- it("doesn't execute the code", function() {
- let parser = peg.generate(
- "start = 'a' { throw 'Boom!'; } / 'b'",
- options
- );
-
- expect(parser).to.parse("b");
- });
- });
- });
-
- describe("choice", function() {
- describe("when any expression matches", function() {
- it("returns its match result", function() {
- let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
-
- expect(parser).to.parse("a", "a");
- expect(parser).to.parse("b", "b");
- expect(parser).to.parse("c", "c");
- });
- });
-
- describe("when all expressions don't match", function() {
- it("reports match failure", function() {
- let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
-
- expect(parser).to.failToParse("d");
- });
- });
- });
-
- describe("error reporting", function() {
- describe("behavior", function() {
- it("reports only the rightmost error", function() {
- let parser = peg.generate("start = 'a' 'b' / 'a' 'c' 'd'", options);
-
- expect(parser).to.failToParse("ace", {
- expected: [{ type: "literal", text: "d", ignoreCase: false }]
- });
- });
- });
-
- describe("expectations reporting", function() {
- it("reports expectations correctly with no alternative", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("ab", {
- expected: [{ type: "end" }]
- });
- });
-
- it("reports expectations correctly with one alternative", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("b", {
- expected: [{ type: "literal", text: "a", ignoreCase: false }]
- });
- });
-
- it("reports expectations correctly with multiple alternatives", function() {
- let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
-
- expect(parser).to.failToParse("d", {
- expected: [
- { type: "literal", text: "a", ignoreCase: false },
- { type: "literal", text: "b", ignoreCase: false },
- { type: "literal", text: "c", ignoreCase: false }
- ]
- });
- });
- });
-
- describe("found string reporting", function() {
- it("reports found string correctly at the end of input", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("", { found: null });
- });
-
- it("reports found string correctly in the middle of input", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("b", { found: "b" });
- });
- });
-
- describe("message building", function() {
- it("builds message correctly with no alternative", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("ab", {
- message: "Expected end of input but \"b\" found."
- });
- });
-
- it("builds message correctly with one alternative", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("b", {
- message: "Expected \"a\" but \"b\" found."
- });
- });
-
- it("builds message correctly with multiple alternatives", function() {
- let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
-
- expect(parser).to.failToParse("d", {
- message: "Expected \"a\", \"b\", or \"c\" but \"d\" found."
- });
- });
-
- it("builds message correctly at the end of input", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("", {
- message: "Expected \"a\" but end of input found."
- });
- });
-
- it("builds message correctly in the middle of input", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("b", {
- message: "Expected \"a\" but \"b\" found."
- });
- });
-
- it("removes duplicates from expectations", function() {
- let parser = peg.generate("start = 'a' / 'a'", options);
-
- expect(parser).to.failToParse("b", {
- message: "Expected \"a\" but \"b\" found."
- });
- });
-
- it("sorts expectations", function() {
- let parser = peg.generate("start = 'c' / 'b' / 'a'", options);
-
- expect(parser).to.failToParse("d", {
- message: "Expected \"a\", \"b\", or \"c\" but \"d\" found."
- });
- });
- });
-
- describe("position reporting", function() {
- it("reports position correctly at the end of input", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("", {
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 0, line: 1, column: 1 }
- }
- });
- });
-
- it("reports position correctly in the middle of input", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("b", {
- location: {
- start: { offset: 0, line: 1, column: 1 },
- end: { offset: 1, line: 1, column: 2 }
- }
- });
- });
-
- it("reports position correctly with trailing input", function() {
- let parser = peg.generate("start = 'a'", options);
-
- expect(parser).to.failToParse("aa", {
- location: {
- start: { offset: 1, line: 1, column: 2 },
- end: { offset: 2, line: 1, column: 3 }
- }
- });
- });
-
- it("reports position correctly in complex cases", function() {
- let parser = peg.generate([
- "start = line (nl+ line)*",
- "line = digit (' '+ digit)*",
- "digit = [0-9]",
- "nl = '\\r'? '\\n'"
- ].join("\n"), options);
-
- expect(parser).to.failToParse("1\n2\n\n3\n\n\n4 5 x", {
- location: {
- start: { offset: 13, line: 7, column: 5 },
- end: { offset: 14, line: 7, column: 6 }
- }
- });
-
- // Newline representations
- expect(parser).to.failToParse("1\nx", { // Old Mac
- location: {
- start: { offset: 2, line: 2, column: 1 },
- end: { offset: 3, line: 2, column: 2 }
- }
- });
- expect(parser).to.failToParse("1\r\nx", { // Windows
- location: {
- start: { offset: 3, line: 2, column: 1 },
- end: { offset: 4, line: 2, column: 2 }
- }
- });
- });
- });
- });
-
- // Following examples are from Wikipedia, see
- // http://en.wikipedia.org/w/index.php?title=Parsing_expression_grammar&oldid=335106938.
- describe("complex examples", function() {
- it("handles arithmetics example correctly", function() {
- // Value ← [0-9]+ / '(' Expr ')'
- // Product ← Value (('*' / '/') Value)*
- // Sum ← Product (('+' / '-') Product)*
- // Expr ← Sum
- let parser = peg.generate([
- "Expr = Sum",
- "Sum = head:Product tail:(('+' / '-') Product)* {",
- " return tail.reduce(function(result, element) {",
- " if (element[0] === '+') { return result + element[1]; }",
- " if (element[0] === '-') { return result - element[1]; }",
- " }, head);",
- " }",
- "Product = head:Value tail:(('*' / '/') Value)* {",
- " return tail.reduce(function(result, element) {",
- " if (element[0] === '*') { return result * element[1]; }",
- " if (element[0] === '/') { return result / element[1]; }",
- " }, head);",
- " }",
- "Value = digits:[0-9]+ { return parseInt(digits.join(''), 10); }",
- " / '(' expr:Expr ')' { return expr; }"
- ].join("\n"), options);
-
- // The "value" rule
- expect(parser).to.parse("0", 0);
- expect(parser).to.parse("123", 123);
- expect(parser).to.parse("(42+43)", 42 + 43);
-
- // The "product" rule
- expect(parser).to.parse("42", 42);
- expect(parser).to.parse("42*43", 42 * 43);
- expect(parser).to.parse("42*43*44*45", 42 * 43 * 44 * 45);
- expect(parser).to.parse("42/43", 42 / 43);
- expect(parser).to.parse("42/43/44/45", 42 / 43 / 44 / 45);
-
- // The "sum" rule
- expect(parser).to.parse("42*43", 42 * 43);
- expect(parser).to.parse("42*43+44*45", 42 * 43 + 44 * 45);
- expect(parser).to.parse("42*43+44*45+46*47+48*49", 42 * 43 + 44 * 45 + 46 * 47 + 48 * 49);
- expect(parser).to.parse("42*43-44*45", 42 * 43 - 44 * 45);
- expect(parser).to.parse("42*43-44*45-46*47-48*49", 42 * 43 - 44 * 45 - 46 * 47 - 48 * 49);
-
- // The "expr" rule
- expect(parser).to.parse("42+43", 42 + 43);
-
- // Complex test
- expect(parser).to.parse("(1+2)*(3+4)", (1 + 2) * (3 + 4));
- });
-
- it("handles non-context-free language correctly", function() {
- // The following parsing expression grammar describes the classic
- // non-context-free language { a^n b^n c^n : n >= 1 }:
- //
- // S ← &(A c) a+ B !(a/b/c)
- // A ← a A? b
- // B ← b B? c
- let parser = peg.generate([
- "S = &(A 'c') a:'a'+ B:B !('a' / 'b' / 'c') { return a.join('') + B; }",
- "A = a:'a' A:A? b:'b' { return [a, A, b].join(''); }",
- "B = b:'b' B:B? c:'c' { return [b, B, c].join(''); }"
- ].join("\n"), options);
-
- expect(parser).to.parse("abc", "abc");
- expect(parser).to.parse("aaabbbccc", "aaabbbccc");
- expect(parser).to.failToParse("aabbbccc");
- expect(parser).to.failToParse("aaaabbbccc");
- expect(parser).to.failToParse("aaabbccc");
- expect(parser).to.failToParse("aaabbbbccc");
- expect(parser).to.failToParse("aaabbbcc");
- expect(parser).to.failToParse("aaabbbcccc");
- });
-
- it("handles nested comments example correctly", function() {
- // Begin ← "(*"
- // End ← "*)"
- // C ← Begin N* End
- // N ← C / (!Begin !End Z)
- // Z ← any single character
- let parser = peg.generate([
- "C = begin:Begin ns:N* end:End { return begin + ns.join('') + end; }",
- "N = C",
- " / !Begin !End z:Z { return z; }",
- "Z = .",
- "Begin = '(*'",
- "End = '*)'"
- ].join("\n"), options);
-
- expect(parser).to.parse("(**)", "(**)");
- expect(parser).to.parse("(*abc*)", "(*abc*)");
- expect(parser).to.parse("(*(**)*)", "(*(**)*)");
- expect(parser).to.parse(
- "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)",
- "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)"
- );
- });
- });
- });
+ function varyOptimizationOptions(block) {
+ function clone(object) {
+ let result = {};
+
+ Object.keys(object).forEach(key => {
+ result[key] = object[key];
+ });
+
+ return result;
+ }
+
+ let optionsVariants = [
+ { cache: false, optimize: "speed", trace: false },
+ { cache: false, optimize: "speed", trace: true },
+ { cache: false, optimize: "size", trace: false },
+ { cache: false, optimize: "size", trace: true },
+ { cache: true, optimize: "speed", trace: false },
+ { cache: true, optimize: "speed", trace: true },
+ { cache: true, optimize: "size", trace: false },
+ { cache: true, optimize: "size", trace: true }
+ ];
+
+ optionsVariants.forEach(variant => {
+ describe(
+ "with options " + chai.util.inspect(variant),
+ function() { block(clone(variant)); }
+ );
+ });
+ }
+
+ function withConsoleStub(block) {
+ if (typeof console === "object") {
+ sinon.stub(console, "log");
+ }
+
+ try {
+ return block();
+ } finally {
+ if (typeof console === "object") {
+ console.log.restore();
+ }
+ }
+ }
+
+ function helpers(chai, utils) {
+ let Assertion = chai.Assertion;
+
+ Assertion.addMethod("parse", function(input, expected, options) {
+ options = options !== undefined ? options : {};
+
+ let result = withConsoleStub(() =>
+ utils.flag(this, "object").parse(input, options)
+ );
+
+ if (expected !== undefined) {
+ this.assert(
+ utils.eql(result, expected),
+ "expected #{this} to parse input as #{exp} but got #{act}",
+ "expected #{this} to not parse input as #{exp}",
+ expected,
+ result,
+ !utils.flag(this, "negate")
+ );
+ }
+ });
+
+ Assertion.addMethod("failToParse", function(input, props, options) {
+ options = options !== undefined ? options : {};
+
+ let passed, result;
+
+ try {
+ result = withConsoleStub(() =>
+ utils.flag(this, "object").parse(input, options)
+ );
+ passed = true;
+ } catch (e) {
+ result = e;
+ passed = false;
+ }
+
+ this.assert(
+ !passed,
+ "expected #{this} to fail to parse input but got #{act}",
+ "expected #{this} to not fail to parse input but #{act} was thrown",
+ null,
+ result
+ );
+
+ if (!passed && props !== undefined) {
+ Object.keys(props).forEach(key => {
+ new Assertion(result).to.have.property(key)
+ .that.is.deep.equal(props[key]);
+ });
+ }
+ });
+ }
+
+ // Helper activation needs to put inside a |beforeEach| block because the
+ // helpers conflict with the ones in test/unit/parser.spec.js.
+ beforeEach(function() {
+ chai.use(helpers);
+ });
+
+ varyOptimizationOptions(function(options) {
+ describe("initializer", function() {
+ it("executes the code before parsing starts", function() {
+ let parser = peg.generate([
+ "{ var result = 42; }",
+ "start = 'a' { return result; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("a", 42);
+ });
+
+ describe("available variables and functions", function() {
+ it("|options| contains options", function() {
+ let parser = peg.generate([
+ "{ var result = options; }",
+ "start = 'a' { return result; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("a", { a: 42 }, { a: 42 });
+ });
+ });
+ });
+
+ describe("rule", function() {
+ if (options.cache) {
+ it("caches rule match results", function() {
+ let parser = peg.generate([
+ "{ var n = 0; }",
+ "start = (a 'b') / (a 'c') { return n; }",
+ "a = 'a' { n++; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("ac", 1);
+ });
+ } else {
+ it("doesn't cache rule match results", function() {
+ let parser = peg.generate([
+ "{ var n = 0; }",
+ "start = (a 'b') / (a 'c') { return n; }",
+ "a = 'a' { n++; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("ac", 2);
+ });
+ }
+
+ describe("when the expression matches", function() {
+ it("returns its match result", function() {
+ let parser = peg.generate("start = 'a'");
+
+ expect(parser).to.parse("a", "a");
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ describe("without display name", function() {
+ it("reports match failure and doesn't record any expectation", function() {
+ let parser = peg.generate("start = 'a'");
+
+ expect(parser).to.failToParse("b", {
+ expected: [{ type: "literal", text: "a", ignoreCase: false }]
+ });
+ });
+ });
+
+ describe("with display name", function() {
+ it("reports match failure and records an expectation of type \"other\"", function() {
+ let parser = peg.generate("start 'start' = 'a'");
+
+ expect(parser).to.failToParse("b", {
+ expected: [{ type: "other", description: "start" }]
+ });
+ });
+
+ it("discards any expectations recorded when matching the expression", function() {
+ let parser = peg.generate("start 'start' = 'a'");
+
+ expect(parser).to.failToParse("b", {
+ expected: [{ type: "other", description: "start" }]
+ });
+ });
+ });
+ });
+ });
+
+ describe("literal", function() {
+ describe("matching", function() {
+ it("matches empty literals", function() {
+ let parser = peg.generate("start = ''", options);
+
+ expect(parser).to.parse("");
+ });
+
+ it("matches one-character literals", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.failToParse("b");
+ });
+
+ it("matches multi-character literals", function() {
+ let parser = peg.generate("start = 'abcd'", options);
+
+ expect(parser).to.parse("abcd");
+ expect(parser).to.failToParse("efgh");
+ });
+
+ it("is case sensitive without the \"i\" flag", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.failToParse("A");
+ });
+
+ it("is case insensitive with the \"i\" flag", function() {
+ let parser = peg.generate("start = 'a'i", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.parse("A");
+ });
+ });
+
+ describe("when it matches", function() {
+ it("returns the matched text", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+
+ it("consumes the matched text", function() {
+ let parser = peg.generate("start = 'a' .", options);
+
+ expect(parser).to.parse("ab");
+ });
+ });
+
+ describe("when it doesn't match", function() {
+ it("reports match failure and records an expectation of type \"literal\"", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("b", {
+ expected: [{ type: "literal", text: "a", ignoreCase: false }]
+ });
+ });
+ });
+ });
+
+ describe("character class", function() {
+ describe("matching", function() {
+ it("matches empty classes", function() {
+ let parser = peg.generate("start = []", options);
+
+ expect(parser).to.failToParse("a");
+ });
+
+ it("matches classes with a character list", function() {
+ let parser = peg.generate("start = [abc]", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.parse("b");
+ expect(parser).to.parse("c");
+ expect(parser).to.failToParse("d");
+ });
+
+ it("matches classes with a character range", function() {
+ let parser = peg.generate("start = [a-c]", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.parse("b");
+ expect(parser).to.parse("c");
+ expect(parser).to.failToParse("d");
+ });
+
+ it("matches inverted classes", function() {
+ let parser = peg.generate("start = [^a]", options);
+
+ expect(parser).to.failToParse("a");
+ expect(parser).to.parse("b");
+ });
+
+ it("is case sensitive without the \"i\" flag", function() {
+ let parser = peg.generate("start = [a]", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.failToParse("A");
+ });
+
+ it("is case insensitive with the \"i\" flag", function() {
+ let parser = peg.generate("start = [a]i", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.parse("A");
+ });
+ });
+
+ describe("when it matches", function() {
+ it("returns the matched character", function() {
+ let parser = peg.generate("start = [a]", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+
+ it("consumes the matched character", function() {
+ let parser = peg.generate("start = [a] .", options);
+
+ expect(parser).to.parse("ab");
+ });
+ });
+
+ describe("when it doesn't match", function() {
+ it("reports match failure and records an expectation of type \"class\"", function() {
+ let parser = peg.generate("start = [a]", options);
+
+ expect(parser).to.failToParse("b", {
+ expected: [{ type: "class", parts: ["a"], inverted: false, ignoreCase: false }]
+ });
+ });
+ });
+ });
+
+ describe("dot", function() {
+ describe("matching", function() {
+ it("matches any character", function() {
+ let parser = peg.generate("start = .", options);
+
+ expect(parser).to.parse("a");
+ expect(parser).to.parse("b");
+ expect(parser).to.parse("c");
+ });
+ });
+
+ describe("when it matches", function() {
+ it("returns the matched character", function() {
+ let parser = peg.generate("start = .", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+
+ it("consumes the matched character", function() {
+ let parser = peg.generate("start = . .", options);
+
+ expect(parser).to.parse("ab");
+ });
+ });
+
+ describe("when it doesn't match", function() {
+ it("reports match failure and records an expectation of type \"any\"", function() {
+ let parser = peg.generate("start = .", options);
+
+ expect(parser).to.failToParse("", {
+ expected: [{ type: "any" }]
+ });
+ });
+ });
+ });
+
+ describe("rule reference", function() {
+ describe("when referenced rule's expression matches", function() {
+ it("returns its result", function() {
+ let parser = peg.generate([
+ "start = a",
+ "a = 'a'"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("a", "a");
+ });
+ });
+
+ describe("when referenced rule's expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate([
+ "start = a",
+ "a = 'a'"
+ ].join("\n"), options);
+
+ expect(parser).to.failToParse("b");
+ });
+ });
+ });
+
+ describe("positive semantic predicate", function() {
+ describe("when the code returns a truthy value", function() {
+ it("returns |undefined|", function() {
+ // The |""| is needed so that the parser doesn't return just
+ // |undefined| which we can't compare against in |toParse| due to the
+ // way optional parameters work.
+ let parser = peg.generate("start = &{ return true; } ''", options);
+
+ expect(parser).to.parse("", [undefined, ""]);
+ });
+ });
+
+ describe("when the code returns a falsey value", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = &{ return false; }", options);
+
+ expect(parser).to.failToParse("");
+ });
+ });
+
+ describe("label variables", function() {
+ describe("in containing sequence", function() {
+ it("can access variables defined by preceding labeled elements", function() {
+ let parser = peg.generate(
+ "start = a:'a' &{ return a === 'a'; }",
+ options
+ );
+
+ expect(parser).to.parse("a");
+ });
+
+ it("cannot access variable defined by labeled predicate element", function() {
+ let parser = peg.generate(
+ "start = 'a' b:&{ return b === undefined; } 'c'",
+ options
+ );
+
+ expect(parser).to.failToParse("ac");
+ });
+
+ it("cannot access variables defined by following labeled elements", function() {
+ let parser = peg.generate(
+ "start = &{ return a === 'a'; } a:'a'",
+ options
+ );
+
+ expect(parser).to.failToParse("a");
+ });
+
+ it("cannot access variables defined by subexpressions", function() {
+ let testcases = [
+ {
+ grammar: "start = (a:'a') &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')? &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')* &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')+ &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = $(a:'a') &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = &(a:'a') 'a' &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = !(a:'a') 'b' &{ return a === 'a'; }",
+ input: "b"
+ },
+ {
+ grammar: "start = b:(a:'a') &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = ('a' b:'b' 'c') &{ return b === 'b'; }",
+ input: "abc"
+ },
+ {
+ grammar: "start = (a:'a' { return a; }) &{ return a === 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = ('a' / b:'b' / 'c') &{ return b === 'b'; }",
+ input: "b"
+ }
+ ];
+
+ testcases.forEach(testcase => {
+ let parser = peg.generate(testcase.grammar, options);
+ expect(parser).to.failToParse(testcase.input);
+ });
+ });
+ });
+
+ describe("in outer sequence", function() {
+ it("can access variables defined by preceding labeled elements", function() {
+ let parser = peg.generate(
+ "start = a:'a' ('b' &{ return a === 'a'; })",
+ options
+ );
+
+ expect(parser).to.parse("ab");
+ });
+
+ it("cannot access variable defined by labeled predicate element", function() {
+ let parser = peg.generate(
+ "start = 'a' b:('b' &{ return b === undefined; }) 'c'",
+ options
+ );
+
+ expect(parser).to.failToParse("abc");
+ });
+
+ it("cannot access variables defined by following labeled elements", function() {
+ let parser = peg.generate(
+ "start = ('a' &{ return b === 'b'; }) b:'b'",
+ options
+ );
+
+ expect(parser).to.failToParse("ab");
+ });
+ });
+ });
+
+ describe("initializer variables & functions", function() {
+ it("can access variables defined in the initializer", function() {
+ let parser = peg.generate([
+ "{ var v = 42 }",
+ "start = &{ return v === 42; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("");
+ });
+
+ it("can access functions defined in the initializer", function() {
+ let parser = peg.generate([
+ "{ function f() { return 42; } }",
+ "start = &{ return f() === 42; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("");
+ });
+ });
+
+ describe("available variables & functions", function() {
+ it("|options| contains options", function() {
+ let parser = peg.generate([
+ "{ var result; }",
+ "start = &{ result = options; return true; } { return result; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("", { a: 42 }, { a: 42 });
+ });
+
+ it("|location| returns current location info", function() {
+ let parser = peg.generate([
+ "{ var result; }",
+ "start = line (nl+ line)* { return result; }",
+ "line = thing (' '+ thing)*",
+ "thing = digit / mark",
+ "digit = [0-9]",
+ "mark = &{ result = location(); return true; } 'x'",
+ "nl = '\\r'? '\\n'"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("1\n2\n\n3\n\n\n4 5 x", {
+ start: { offset: 13, line: 7, column: 5 },
+ end: { offset: 13, line: 7, column: 5 }
+ });
+
+ // Newline representations
+ expect(parser).to.parse("1\nx", { // Unix
+ start: { offset: 2, line: 2, column: 1 },
+ end: { offset: 2, line: 2, column: 1 }
+ });
+ expect(parser).to.parse("1\r\nx", { // Windows
+ start: { offset: 3, line: 2, column: 1 },
+ end: { offset: 3, line: 2, column: 1 }
+ });
+ });
+ });
+ });
+
+ describe("negative semantic predicate", function() {
+ describe("when the code returns a falsey value", function() {
+ it("returns |undefined|", function() {
+ // The |""| is needed so that the parser doesn't return just
+ // |undefined| which we can't compare against in |toParse| due to the
+ // way optional parameters work.
+ let parser = peg.generate("start = !{ return false; } ''", options);
+
+ expect(parser).to.parse("", [undefined, ""]);
+ });
+ });
+
+ describe("when the code returns a truthy value", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = !{ return true; }", options);
+
+ expect(parser).to.failToParse("");
+ });
+ });
+
+ describe("label variables", function() {
+ describe("in containing sequence", function() {
+ it("can access variables defined by preceding labeled elements", function() {
+ let parser = peg.generate(
+ "start = a:'a' !{ return a !== 'a'; }",
+ options
+ );
+
+ expect(parser).to.parse("a");
+ });
+
+ it("cannot access variable defined by labeled predicate element", function() {
+ let parser = peg.generate(
+ "start = 'a' b:!{ return b !== undefined; } 'c'",
+ options
+ );
+
+ expect(parser).to.failToParse("ac");
+ });
+
+ it("cannot access variables defined by following labeled elements", function() {
+ let parser = peg.generate(
+ "start = !{ return a !== 'a'; } a:'a'",
+ options
+ );
+
+ expect(parser).to.failToParse("a");
+ });
+
+ it("cannot access variables defined by subexpressions", function() {
+ let testcases = [
+ {
+ grammar: "start = (a:'a') !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')? !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')* !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')+ !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = $(a:'a') !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = &(a:'a') 'a' !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = !(a:'a') 'b' !{ return a !== 'a'; }",
+ input: "b"
+ },
+ {
+ grammar: "start = b:(a:'a') !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = ('a' b:'b' 'c') !{ return b !== 'b'; }",
+ input: "abc"
+ },
+ {
+ grammar: "start = (a:'a' { return a; }) !{ return a !== 'a'; }",
+ input: "a"
+ },
+ {
+ grammar: "start = ('a' / b:'b' / 'c') !{ return b !== 'b'; }",
+ input: "b"
+ }
+ ];
+
+ testcases.forEach(testcase => {
+ let parser = peg.generate(testcase.grammar, options);
+ expect(parser).to.failToParse(testcase.input);
+ });
+ });
+ });
+
+ describe("in outer sequence", function() {
+ it("can access variables defined by preceding labeled elements", function() {
+ let parser = peg.generate(
+ "start = a:'a' ('b' !{ return a !== 'a'; })",
+ options
+ );
+
+ expect(parser).to.parse("ab");
+ });
+
+ it("cannot access variable defined by labeled predicate element", function() {
+ let parser = peg.generate(
+ "start = 'a' b:('b' !{ return b !== undefined; }) 'c'",
+ options
+ );
+
+ expect(parser).to.failToParse("abc");
+ });
+
+ it("cannot access variables defined by following labeled elements", function() {
+ let parser = peg.generate(
+ "start = ('a' !{ return b !== 'b'; }) b:'b'",
+ options
+ );
+
+ expect(parser).to.failToParse("ab");
+ });
+ });
+ });
+
+ describe("initializer variables & functions", function() {
+ it("can access variables defined in the initializer", function() {
+ let parser = peg.generate([
+ "{ var v = 42 }",
+ "start = !{ return v !== 42; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("");
+ });
+
+ it("can access functions defined in the initializer", function() {
+ let parser = peg.generate([
+ "{ function f() { return 42; } }",
+ "start = !{ return f() !== 42; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("");
+ });
+ });
+
+ describe("available variables & functions", function() {
+ it("|options| contains options", function() {
+ let parser = peg.generate([
+ "{ var result; }",
+ "start = !{ result = options; return false; } { return result; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("", { a: 42 }, { a: 42 });
+ });
+
+ it("|location| returns current location info", function() {
+ let parser = peg.generate([
+ "{ var result; }",
+ "start = line (nl+ line)* { return result; }",
+ "line = thing (' '+ thing)*",
+ "thing = digit / mark",
+ "digit = [0-9]",
+ "mark = !{ result = location(); return false; } 'x'",
+ "nl = '\\r'? '\\n'"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("1\n2\n\n3\n\n\n4 5 x", {
+ start: { offset: 13, line: 7, column: 5 },
+ end: { offset: 13, line: 7, column: 5 }
+ });
+
+ // Newline representations
+ expect(parser).to.parse("1\nx", { // Unix
+ start: { offset: 2, line: 2, column: 1 },
+ end: { offset: 2, line: 2, column: 1 }
+ });
+ expect(parser).to.parse("1\r\nx", { // Windows
+ start: { offset: 3, line: 2, column: 1 },
+ end: { offset: 3, line: 2, column: 1 }
+ });
+ });
+ });
+ });
+
+ describe("group", function() {
+ describe("when the expression matches", function() {
+ it("returns its match result", function() {
+ let parser = peg.generate("start = ('a')", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = ('a')", options);
+
+ expect(parser).to.failToParse("b");
+ });
+ });
+ });
+
+ describe("optional", function() {
+ describe("when the expression matches", function() {
+ it("returns its match result", function() {
+ let parser = peg.generate("start = 'a'?", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("returns |null|", function() {
+ let parser = peg.generate("start = 'a'?", options);
+
+ expect(parser).to.parse("", null);
+ });
+ });
+ });
+
+ describe("zero or more", function() {
+ describe("when the expression matches zero or more times", function() {
+ it("returns an array of its match results", function() {
+ let parser = peg.generate("start = 'a'*", options);
+
+ expect(parser).to.parse("", []);
+ expect(parser).to.parse("a", ["a"]);
+ expect(parser).to.parse("aaa", ["a", "a", "a"]);
+ });
+ });
+ });
+
+ describe("one or more", function() {
+ describe("when the expression matches one or more times", function() {
+ it("returns an array of its match results", function() {
+ let parser = peg.generate("start = 'a'+", options);
+
+ expect(parser).to.parse("a", ["a"]);
+ expect(parser).to.parse("aaa", ["a", "a", "a"]);
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = 'a'+", options);
+
+ expect(parser).to.failToParse("");
+ });
+ });
+ });
+
+ describe("text", function() {
+ describe("when the expression matches", function() {
+ it("returns the matched text", function() {
+ let parser = peg.generate("start = $('a' 'b' 'c')", options);
+
+ expect(parser).to.parse("abc", "abc");
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = $('a')", options);
+
+ expect(parser).to.failToParse("b");
+ });
+ });
+ });
+
+ describe("positive simple predicate", function() {
+ describe("when the expression matches", function() {
+ it("returns |undefined|", function() {
+ let parser = peg.generate("start = &'a' 'a'", options);
+
+ expect(parser).to.parse("a", [undefined, "a"]);
+ });
+
+ it("resets parse position", function() {
+ let parser = peg.generate("start = &'a' 'a'", options);
+
+ expect(parser).to.parse("a");
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = &'a'", options);
+
+ expect(parser).to.failToParse("b");
+ });
+
+ it("discards any expectations recorded when matching the expression", function() {
+ let parser = peg.generate("start = 'a' / &'b' / 'c'", options);
+
+ expect(parser).to.failToParse("d", {
+ expected: [
+ { type: "literal", text: "a", ignoreCase: false },
+ { type: "literal", text: "c", ignoreCase: false }
+ ]
+ });
+ });
+ });
+ });
+
+ describe("negative simple predicate", function() {
+ describe("when the expression matches", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = !'a'", options);
+
+ expect(parser).to.failToParse("a");
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("returns |undefined|", function() {
+ let parser = peg.generate("start = !'a' 'b'", options);
+
+ expect(parser).to.parse("b", [undefined, "b"]);
+ });
+
+ it("resets parse position", function() {
+ let parser = peg.generate("start = !'a' 'b'", options);
+
+ expect(parser).to.parse("b");
+ });
+
+ it("discards any expectations recorded when matching the expression", function() {
+ let parser = peg.generate("start = 'a' / !'b' / 'c'", options);
+
+ expect(parser).to.failToParse("b", {
+ expected: [
+ { type: "literal", text: "a", ignoreCase: false },
+ { type: "literal", text: "c", ignoreCase: false }
+ ]
+ });
+ });
+ });
+ });
+
+ describe("label", function() {
+ describe("when the expression matches", function() {
+ it("returns its match result", function() {
+ let parser = peg.generate("start = a:'a'", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = a:'a'", options);
+
+ expect(parser).to.failToParse("b");
+ });
+ });
+ });
+
+ describe("sequence", function() {
+ describe("when all expressions match", function() {
+ it("returns an array of their match results", function() {
+ let parser = peg.generate("start = 'a' 'b' 'c'", options);
+
+ expect(parser).to.parse("abc", ["a", "b", "c"]);
+ });
+ });
+
+ describe("when any expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = 'a' 'b' 'c'", options);
+
+ expect(parser).to.failToParse("dbc");
+ expect(parser).to.failToParse("adc");
+ expect(parser).to.failToParse("abd");
+ });
+
+ it("resets parse position", function() {
+ let parser = peg.generate("start = 'a' 'b' / 'a'", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+ });
+ });
+
+ describe("action", function() {
+ describe("when the expression matches", function() {
+ it("returns the value returned by the code", function() {
+ let parser = peg.generate("start = 'a' { return 42; }", options);
+
+ expect(parser).to.parse("a", 42);
+ });
+
+ describe("label variables", function() {
+ describe("in the expression", function() {
+ it("can access variable defined by labeled expression", function() {
+ let parser = peg.generate("start = a:'a' { return a; }", options);
+
+ expect(parser).to.parse("a", "a");
+ });
+
+ it("can access variables defined by labeled sequence elements", function() {
+ let parser = peg.generate(
+ "start = a:'a' b:'b' c:'c' { return [a, b, c]; }",
+ options
+ );
+
+ expect(parser).to.parse("abc", ["a", "b", "c"]);
+ });
+
+ it("cannot access variables defined by subexpressions", function() {
+ let testcases = [
+ {
+ grammar: "start = (a:'a') { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')? { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')* { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = (a:'a')+ { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = $(a:'a') { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = &(a:'a') 'a' { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = !(a:'a') 'b' { return a; }",
+ input: "b"
+ },
+ {
+ grammar: "start = b:(a:'a') { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = ('a' b:'b' 'c') { return b; }",
+ input: "abc"
+ },
+ {
+ grammar: "start = (a:'a' { return a; }) { return a; }",
+ input: "a"
+ },
+ {
+ grammar: "start = ('a' / b:'b' / 'c') { return b; }",
+ input: "b"
+ }
+ ];
+
+ testcases.forEach(testcase => {
+ let parser = peg.generate(testcase.grammar, options);
+ expect(parser).to.failToParse(testcase.input);
+ });
+ });
+ });
+
+ describe("in outer sequence", function() {
+ it("can access variables defined by preceding labeled elements", function() {
+ let parser = peg.generate(
+ "start = a:'a' ('b' { return a; })",
+ options
+ );
+
+ expect(parser).to.parse("ab", ["a", "a"]);
+ });
+
+ it("cannot access variable defined by labeled action element", function() {
+ let parser = peg.generate(
+ "start = 'a' b:('b' { return b; }) c:'c'",
+ options
+ );
+
+ expect(parser).to.failToParse("abc");
+ });
+
+ it("cannot access variables defined by following labeled elements", function() {
+ let parser = peg.generate(
+ "start = ('a' { return b; }) b:'b'",
+ options
+ );
+
+ expect(parser).to.failToParse("ab");
+ });
+ });
+ });
+
+ describe("initializer variables & functions", function() {
+ it("can access variables defined in the initializer", function() {
+ let parser = peg.generate([
+ "{ var v = 42 }",
+ "start = 'a' { return v; }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("a", 42);
+ });
+
+ it("can access functions defined in the initializer", function() {
+ let parser = peg.generate([
+ "{ function f() { return 42; } }",
+ "start = 'a' { return f(); }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("a", 42);
+ });
+ });
+
+ describe("available variables & functions", function() {
+ it("|options| contains options", function() {
+ let parser = peg.generate(
+ "start = 'a' { return options; }",
+ options
+ );
+
+ expect(parser).to.parse("a", { a: 42 }, { a: 42 });
+ });
+
+ it("|text| returns text matched by the expression", function() {
+ let parser = peg.generate(
+ "start = 'a' { return text(); }",
+ options
+ );
+
+ expect(parser).to.parse("a", "a");
+ });
+
+ it("|location| returns location info of the expression", function() {
+ let parser = peg.generate([
+ "{ var result; }",
+ "start = line (nl+ line)* { return result; }",
+ "line = thing (' '+ thing)*",
+ "thing = digit / mark",
+ "digit = [0-9]",
+ "mark = 'x' { result = location(); }",
+ "nl = '\\r'? '\\n'"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("1\n2\n\n3\n\n\n4 5 x", {
+ start: { offset: 13, line: 7, column: 5 },
+ end: { offset: 14, line: 7, column: 6 }
+ });
+
+ // Newline representations
+ expect(parser).to.parse("1\nx", { // Unix
+ start: { offset: 2, line: 2, column: 1 },
+ end: { offset: 3, line: 2, column: 2 }
+ });
+ expect(parser).to.parse("1\r\nx", { // Windows
+ start: { offset: 3, line: 2, column: 1 },
+ end: { offset: 4, line: 2, column: 2 }
+ });
+ });
+
+ describe("|expected|", function() {
+ it("terminates parsing and throws an exception", function() {
+ let parser = peg.generate(
+ "start = 'a' { expected('a'); }",
+ options
+ );
+
+ expect(parser).to.failToParse("a", {
+ message: "Expected a but \"a\" found.",
+ expected: [{ type: "other", description: "a" }],
+ found: "a",
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 1, line: 1, column: 2 }
+ }
+ });
+ });
+
+ it("allows to set custom location info", function() {
+ let parser = peg.generate([
+ "start = 'a' {",
+ " expected('a', {",
+ " start: { offset: 1, line: 1, column: 2 },",
+ " end: { offset: 2, line: 1, column: 3 }",
+ " });",
+ "}"
+ ].join("\n"), options);
+
+ expect(parser).to.failToParse("a", {
+ message: "Expected a but \"a\" found.",
+ expected: [{ type: "other", description: "a" }],
+ found: "a",
+ location: {
+ start: { offset: 1, line: 1, column: 2 },
+ end: { offset: 2, line: 1, column: 3 }
+ }
+ });
+ });
+ });
+
+ describe("|error|", function() {
+ it("terminates parsing and throws an exception", function() {
+ let parser = peg.generate(
+ "start = 'a' { error('a'); }",
+ options
+ );
+
+ expect(parser).to.failToParse("a", {
+ message: "a",
+ found: null,
+ expected: null,
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 1, line: 1, column: 2 }
+ }
+ });
+ });
+
+ it("allows to set custom location info", function() {
+ let parser = peg.generate([
+ "start = 'a' {",
+ " error('a', {",
+ " start: { offset: 1, line: 1, column: 2 },",
+ " end: { offset: 2, line: 1, column: 3 }",
+ " });",
+ "}"
+ ].join("\n"), options);
+
+ expect(parser).to.failToParse("a", {
+ message: "a",
+ expected: null,
+ found: null,
+ location: {
+ start: { offset: 1, line: 1, column: 2 },
+ end: { offset: 2, line: 1, column: 3 }
+ }
+ });
+ });
+ });
+ });
+ });
+
+ describe("when the expression doesn't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = 'a' { return 42; }", options);
+
+ expect(parser).to.failToParse("b");
+ });
+
+ it("doesn't execute the code", function() {
+ let parser = peg.generate(
+ "start = 'a' { throw 'Boom!'; } / 'b'",
+ options
+ );
+
+ expect(parser).to.parse("b");
+ });
+ });
+ });
+
+ describe("choice", function() {
+ describe("when any expression matches", function() {
+ it("returns its match result", function() {
+ let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
+
+ expect(parser).to.parse("a", "a");
+ expect(parser).to.parse("b", "b");
+ expect(parser).to.parse("c", "c");
+ });
+ });
+
+ describe("when all expressions don't match", function() {
+ it("reports match failure", function() {
+ let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
+
+ expect(parser).to.failToParse("d");
+ });
+ });
+ });
+
+ describe("error reporting", function() {
+ describe("behavior", function() {
+ it("reports only the rightmost error", function() {
+ let parser = peg.generate("start = 'a' 'b' / 'a' 'c' 'd'", options);
+
+ expect(parser).to.failToParse("ace", {
+ expected: [{ type: "literal", text: "d", ignoreCase: false }]
+ });
+ });
+ });
+
+ describe("expectations reporting", function() {
+ it("reports expectations correctly with no alternative", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("ab", {
+ expected: [{ type: "end" }]
+ });
+ });
+
+ it("reports expectations correctly with one alternative", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("b", {
+ expected: [{ type: "literal", text: "a", ignoreCase: false }]
+ });
+ });
+
+ it("reports expectations correctly with multiple alternatives", function() {
+ let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
+
+ expect(parser).to.failToParse("d", {
+ expected: [
+ { type: "literal", text: "a", ignoreCase: false },
+ { type: "literal", text: "b", ignoreCase: false },
+ { type: "literal", text: "c", ignoreCase: false }
+ ]
+ });
+ });
+ });
+
+ describe("found string reporting", function() {
+ it("reports found string correctly at the end of input", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("", { found: null });
+ });
+
+ it("reports found string correctly in the middle of input", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("b", { found: "b" });
+ });
+ });
+
+ describe("message building", function() {
+ it("builds message correctly with no alternative", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("ab", {
+ message: "Expected end of input but \"b\" found."
+ });
+ });
+
+ it("builds message correctly with one alternative", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("b", {
+ message: "Expected \"a\" but \"b\" found."
+ });
+ });
+
+ it("builds message correctly with multiple alternatives", function() {
+ let parser = peg.generate("start = 'a' / 'b' / 'c'", options);
+
+ expect(parser).to.failToParse("d", {
+ message: "Expected \"a\", \"b\", or \"c\" but \"d\" found."
+ });
+ });
+
+ it("builds message correctly at the end of input", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("", {
+ message: "Expected \"a\" but end of input found."
+ });
+ });
+
+ it("builds message correctly in the middle of input", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("b", {
+ message: "Expected \"a\" but \"b\" found."
+ });
+ });
+
+ it("removes duplicates from expectations", function() {
+ let parser = peg.generate("start = 'a' / 'a'", options);
+
+ expect(parser).to.failToParse("b", {
+ message: "Expected \"a\" but \"b\" found."
+ });
+ });
+
+ it("sorts expectations", function() {
+ let parser = peg.generate("start = 'c' / 'b' / 'a'", options);
+
+ expect(parser).to.failToParse("d", {
+ message: "Expected \"a\", \"b\", or \"c\" but \"d\" found."
+ });
+ });
+ });
+
+ describe("position reporting", function() {
+ it("reports position correctly at the end of input", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("", {
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 0, line: 1, column: 1 }
+ }
+ });
+ });
+
+ it("reports position correctly in the middle of input", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("b", {
+ location: {
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 1, line: 1, column: 2 }
+ }
+ });
+ });
+
+ it("reports position correctly with trailing input", function() {
+ let parser = peg.generate("start = 'a'", options);
+
+ expect(parser).to.failToParse("aa", {
+ location: {
+ start: { offset: 1, line: 1, column: 2 },
+ end: { offset: 2, line: 1, column: 3 }
+ }
+ });
+ });
+
+ it("reports position correctly in complex cases", function() {
+ let parser = peg.generate([
+ "start = line (nl+ line)*",
+ "line = digit (' '+ digit)*",
+ "digit = [0-9]",
+ "nl = '\\r'? '\\n'"
+ ].join("\n"), options);
+
+ expect(parser).to.failToParse("1\n2\n\n3\n\n\n4 5 x", {
+ location: {
+ start: { offset: 13, line: 7, column: 5 },
+ end: { offset: 14, line: 7, column: 6 }
+ }
+ });
+
+ // Newline representations
+ expect(parser).to.failToParse("1\nx", { // Old Mac
+ location: {
+ start: { offset: 2, line: 2, column: 1 },
+ end: { offset: 3, line: 2, column: 2 }
+ }
+ });
+ expect(parser).to.failToParse("1\r\nx", { // Windows
+ location: {
+ start: { offset: 3, line: 2, column: 1 },
+ end: { offset: 4, line: 2, column: 2 }
+ }
+ });
+ });
+ });
+ });
+
+ // Following examples are from Wikipedia, see
+ // http://en.wikipedia.org/w/index.php?title=Parsing_expression_grammar&oldid=335106938.
+ describe("complex examples", function() {
+ it("handles arithmetics example correctly", function() {
+ // Value ← [0-9]+ / '(' Expr ')'
+ // Product ← Value (('*' / '/') Value)*
+ // Sum ← Product (('+' / '-') Product)*
+ // Expr ← Sum
+ let parser = peg.generate([
+ "Expr = Sum",
+ "Sum = head:Product tail:(('+' / '-') Product)* {",
+ " return tail.reduce(function(result, element) {",
+ " if (element[0] === '+') { return result + element[1]; }",
+ " if (element[0] === '-') { return result - element[1]; }",
+ " }, head);",
+ " }",
+ "Product = head:Value tail:(('*' / '/') Value)* {",
+ " return tail.reduce(function(result, element) {",
+ " if (element[0] === '*') { return result * element[1]; }",
+ " if (element[0] === '/') { return result / element[1]; }",
+ " }, head);",
+ " }",
+ "Value = digits:[0-9]+ { return parseInt(digits.join(''), 10); }",
+ " / '(' expr:Expr ')' { return expr; }"
+ ].join("\n"), options);
+
+ // The "value" rule
+ expect(parser).to.parse("0", 0);
+ expect(parser).to.parse("123", 123);
+ expect(parser).to.parse("(42+43)", 42 + 43);
+
+ // The "product" rule
+ expect(parser).to.parse("42", 42);
+ expect(parser).to.parse("42*43", 42 * 43);
+ expect(parser).to.parse("42*43*44*45", 42 * 43 * 44 * 45);
+ expect(parser).to.parse("42/43", 42 / 43);
+ expect(parser).to.parse("42/43/44/45", 42 / 43 / 44 / 45);
+
+ // The "sum" rule
+ expect(parser).to.parse("42*43", 42 * 43);
+ expect(parser).to.parse("42*43+44*45", 42 * 43 + 44 * 45);
+ expect(parser).to.parse("42*43+44*45+46*47+48*49", 42 * 43 + 44 * 45 + 46 * 47 + 48 * 49);
+ expect(parser).to.parse("42*43-44*45", 42 * 43 - 44 * 45);
+ expect(parser).to.parse("42*43-44*45-46*47-48*49", 42 * 43 - 44 * 45 - 46 * 47 - 48 * 49);
+
+ // The "expr" rule
+ expect(parser).to.parse("42+43", 42 + 43);
+
+ // Complex test
+ expect(parser).to.parse("(1+2)*(3+4)", (1 + 2) * (3 + 4));
+ });
+
+ it("handles non-context-free language correctly", function() {
+ // The following parsing expression grammar describes the classic
+ // non-context-free language { a^n b^n c^n : n >= 1 }:
+ //
+ // S ← &(A c) a+ B !(a/b/c)
+ // A ← a A? b
+ // B ← b B? c
+ let parser = peg.generate([
+ "S = &(A 'c') a:'a'+ B:B !('a' / 'b' / 'c') { return a.join('') + B; }",
+ "A = a:'a' A:A? b:'b' { return [a, A, b].join(''); }",
+ "B = b:'b' B:B? c:'c' { return [b, B, c].join(''); }"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("abc", "abc");
+ expect(parser).to.parse("aaabbbccc", "aaabbbccc");
+ expect(parser).to.failToParse("aabbbccc");
+ expect(parser).to.failToParse("aaaabbbccc");
+ expect(parser).to.failToParse("aaabbccc");
+ expect(parser).to.failToParse("aaabbbbccc");
+ expect(parser).to.failToParse("aaabbbcc");
+ expect(parser).to.failToParse("aaabbbcccc");
+ });
+
+ it("handles nested comments example correctly", function() {
+ // Begin ← "(*"
+ // End ← "*)"
+ // C ← Begin N* End
+ // N ← C / (!Begin !End Z)
+ // Z ← any single character
+ let parser = peg.generate([
+ "C = begin:Begin ns:N* end:End { return begin + ns.join('') + end; }",
+ "N = C",
+ " / !Begin !End z:Z { return z; }",
+ "Z = .",
+ "Begin = '(*'",
+ "End = '*)'"
+ ].join("\n"), options);
+
+ expect(parser).to.parse("(**)", "(**)");
+ expect(parser).to.parse("(*abc*)", "(*abc*)");
+ expect(parser).to.parse("(*(**)*)", "(*(**)*)");
+ expect(parser).to.parse(
+ "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)",
+ "(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)"
+ );
+ });
+ });
+ });
});
diff --git a/test/unit/compiler/passes/generate-bytecode.spec.js b/test/unit/compiler/passes/generate-bytecode.spec.js
index e9d8f3c..5c1db08 100644
--- a/test/unit/compiler/passes/generate-bytecode.spec.js
+++ b/test/unit/compiler/passes/generate-bytecode.spec.js
@@ -9,648 +9,648 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |generateBytecode|", function() {
- function bytecodeDetails(bytecode) {
- return {
- rules: [{ bytecode: bytecode }]
- };
- }
-
- function constsDetails(consts) { return { consts: consts }; }
-
- describe("for grammar", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST([
- "a = 'a'",
- "b = 'b'",
- "c = 'c'"
- ].join("\n"), {
- rules: [
- { bytecode: [18, 0, 2, 2, 22, 0, 23, 1] },
- { bytecode: [18, 2, 2, 2, 22, 2, 23, 3] },
- { bytecode: [18, 4, 2, 2, 22, 4, 23, 5] }
- ]
- });
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST([
- "a = 'a'",
- "b = 'b'",
- "c = 'c'"
- ].join("\n"), constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)",
- "\"b\"",
- "peg$literalExpectation(\"b\", false)",
- "\"c\"",
- "peg$literalExpectation(\"c\", false)"
- ]));
- });
- });
-
- describe("for rule", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST("start = 'a'", bytecodeDetails([
- 18, 0, 2, 2, 22, 0, 23, 1 //
- ]));
- });
- });
-
- describe("for named", function() {
- let grammar = "start 'start' = 'a'";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 28, // SILENT_FAILS_ON
- 18, 1, 2, 2, 22, 1, 23, 2, //
- 29, // SILENT_FAILS_OFF
- 14, 2, 0, // IF_ERROR
- 23, 0 // * FAIL
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "peg$otherExpectation(\"start\")",
- "\"a\"",
- "peg$literalExpectation(\"a\", false)"
- ]));
- });
- });
-
- describe("for choice", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST("start = 'a' / 'b' / 'c'", bytecodeDetails([
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 14, 21, 0, // IF_ERROR
- 6, // * POP
- 18, 2, 2, 2, 22, 2, 23, 3, //
- 14, 9, 0, // IF_ERROR
- 6, // * POP
- 18, 4, 2, 2, 22, 4, 23, 5 //
- ]));
- });
- });
-
- describe("for action", function() {
- describe("without labels", function() {
- let grammar = "start = 'a' { code }";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 6, 0, // IF_NOT_ERROR
- 24, 1, // * LOAD_SAVED_POS
- 26, 2, 1, 0, // CALL
- 9 // NIP
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)",
- "function() { code }"
- ]));
- });
- });
-
- describe("with one label", function() {
- let grammar = "start = a:'a' { code }";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 7, 0, // IF_NOT_ERROR
- 24, 1, // * LOAD_SAVED_POS
- 26, 2, 1, 1, 0, // CALL
- 9 // NIP
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)",
- "function(a) { code }"
- ]));
- });
- });
-
- describe("with multiple labels", function() {
- let grammar = "start = a:'a' b:'b' c:'c' { code }";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 40, 3, // IF_NOT_ERROR
- 18, 2, 2, 2, 22, 2, 23, 3, // *
- 15, 25, 4, // IF_NOT_ERROR
- 18, 4, 2, 2, 22, 4, 23, 5, // *
- 15, 10, 4, // IF_NOT_ERROR
- 24, 3, // * LOAD_SAVED_POS
- 26, 6, 3, 3, 2, 1, 0, // CALL
- 9, // NIP
- 8, 3, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 8, 2, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 6, // * POP
- 7, // POP_CURR_POS
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)",
- "\"b\"",
- "peg$literalExpectation(\"b\", false)",
- "\"c\"",
- "peg$literalExpectation(\"c\", false)",
- "function(a, b, c) { code }"
- ]));
- });
- });
- });
-
- describe("for sequence", function() {
- let grammar = "start = 'a' 'b' 'c'";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 33, 3, // IF_NOT_ERROR
- 18, 2, 2, 2, 22, 2, 23, 3, // *
- 15, 18, 4, // IF_NOT_ERROR
- 18, 4, 2, 2, 22, 4, 23, 5, // *
- 15, 3, 4, // IF_NOT_ERROR
- 11, 3, // * WRAP
- 9, // NIP
- 8, 3, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 8, 2, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 6, // * POP
- 7, // POP_CURR_POS
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)",
- "\"b\"",
- "peg$literalExpectation(\"b\", false)",
- "\"c\"",
- "peg$literalExpectation(\"c\", false)"
- ]));
- });
- });
-
- describe("for labeled", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST("start = a:'a'", bytecodeDetails([
- 18, 0, 2, 2, 22, 0, 23, 1 //
- ]));
- });
- });
-
- describe("for text", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST("start = $'a'", bytecodeDetails([
- 5, // PUSH_CURR_POS
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 2, 1, // IF_NOT_ERROR
- 6, // * POP
- 12, // TEXT
- 9 // * NIP
- ]));
- });
- });
-
- describe("for simple_and", function() {
- let grammar = "start = &'a'";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 28, // SILENT_FAILS_ON
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 29, // SILENT_FAILS_OFF
- 15, 3, 3, // IF_NOT_ERROR
- 6, // * POP
- 7, // POP_CURR_POS
- 1, // PUSH_UNDEFINED
- 6, // * POP
- 6, // POP
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)"
- ]));
- });
- });
-
- describe("for simple_not", function() {
- let grammar = "start = !'a'";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 28, // SILENT_FAILS_ON
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 29, // SILENT_FAILS_OFF
- 14, 3, 3, // IF_ERROR
- 6, // * POP
- 6, // POP
- 1, // PUSH_UNDEFINED
- 6, // * POP
- 7, // POP_CURR_POS
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)"
- ]));
- });
- });
-
- describe("for optional", function() {
- let grammar = "start = 'a'?";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 14, 2, 0, // IF_ERROR
- 6, // * POP
- 2 // PUSH_NULL
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)"
- ]));
- });
- });
-
- describe("for zero_or_more", function() {
- let grammar = "start = 'a'*";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 4, // PUSH_EMPTY_ARRAY
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 16, 9, // WHILE_NOT_ERROR
- 10, // * APPEND
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 6 // POP
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)"
- ]));
- });
- });
-
- describe("for one_or_more", function() {
- let grammar = "start = 'a'+";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 4, // PUSH_EMPTY_ARRAY
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 12, 3, // IF_NOT_ERROR
- 16, 9, // * WHILE_NOT_ERROR
- 10, // * APPEND
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 6, // POP
- 6, // * POP
- 6, // POP
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)"
- ]));
- });
- });
-
- describe("for group", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST("start = ('a')", bytecodeDetails([
- 18, 0, 2, 2, 22, 0, 23, 1 //
- ]));
- });
- });
-
- describe("for semantic_and", function() {
- describe("without labels", function() {
- let grammar = "start = &{ code }";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 25, // UPDATE_SAVED_POS
- 26, 0, 0, 0, // CALL
- 13, 2, 2, // IF
- 6, // * POP
- 1, // PUSH_UNDEFINED
- 6, // * POP
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(
- grammar,
- constsDetails(["function() { code }"])
- );
- });
- });
-
- describe("with labels", function() {
- let grammar = "start = a:'a' b:'b' c:'c' &{ code }";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 55, 3, // IF_NOT_ERROR
- 18, 2, 2, 2, 22, 2, 23, 3, // *
- 15, 40, 4, // IF_NOT_ERROR
- 18, 4, 2, 2, 22, 4, 23, 5, // *
- 15, 25, 4, // IF_NOT_ERROR
- 25, // * UPDATE_SAVED_POS
- 26, 6, 0, 3, 2, 1, 0, // CALL
- 13, 2, 2, // IF
- 6, // * POP
- 1, // PUSH_UNDEFINED
- 6, // * POP
- 3, // PUSH_FAILED
- 15, 3, 4, // IF_NOT_ERROR
- 11, 4, // * WRAP
- 9, // NIP
- 8, 4, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 8, 3, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 8, 2, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 6, // * POP
- 7, // POP_CURR_POS
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)",
- "\"b\"",
- "peg$literalExpectation(\"b\", false)",
- "\"c\"",
- "peg$literalExpectation(\"c\", false)",
- "function(a, b, c) { code }"
- ]));
- });
- });
- });
-
- describe("for semantic_not", function() {
- describe("without labels", function() {
- let grammar = "start = !{ code }";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 25, // UPDATE_SAVED_POS
- 26, 0, 0, 0, // CALL
- 13, 2, 2, // IF
- 6, // * POP
- 3, // PUSH_FAILED
- 6, // * POP
- 1 // PUSH_UNDEFINED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(
- grammar,
- constsDetails(["function() { code }"])
- );
- });
- });
-
- describe("with labels", function() {
- let grammar = "start = a:'a' b:'b' c:'c' !{ code }";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 5, // PUSH_CURR_POS
- 18, 0, 2, 2, 22, 0, 23, 1, //
- 15, 55, 3, // IF_NOT_ERROR
- 18, 2, 2, 2, 22, 2, 23, 3, // *
- 15, 40, 4, // IF_NOT_ERROR
- 18, 4, 2, 2, 22, 4, 23, 5, // *
- 15, 25, 4, // IF_NOT_ERROR
- 25, // * UPDATE_SAVED_POS
- 26, 6, 0, 3, 2, 1, 0, // CALL
- 13, 2, 2, // IF
- 6, // * POP
- 3, // PUSH_FAILED
- 6, // * POP
- 1, // PUSH_UNDEFINED
- 15, 3, 4, // IF_NOT_ERROR
- 11, 4, // * WRAP
- 9, // NIP
- 8, 4, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 8, 3, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 8, 2, // * POP_N
- 7, // POP_CURR_POS
- 3, // PUSH_FAILED
- 6, // * POP
- 7, // POP_CURR_POS
- 3 // PUSH_FAILED
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)",
- "\"b\"",
- "peg$literalExpectation(\"b\", false)",
- "\"c\"",
- "peg$literalExpectation(\"c\", false)",
- "function(a, b, c) { code }"
- ]));
- });
- });
- });
-
- describe("for rule_ref", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST([
- "start = other",
- "other = 'other'"
- ].join("\n"), {
- rules: [
- {
- bytecode: [27, 1] // RULE
- },
- { }
- ]
- });
- });
- });
-
- describe("for literal", function() {
- describe("empty", function() {
- let grammar = "start = ''";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 0, 0 // PUSH
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails(["\"\""]));
- });
- });
-
- describe("non-empty case-sensitive", function() {
- let grammar = "start = 'a'";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 18, 0, 2, 2, // MATCH_STRING
- 22, 0, // * ACCEPT_STRING
- 23, 1 // * FAIL
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"a\", false)"
- ]));
- });
- });
-
- describe("non-empty case-insensitive", function() {
- let grammar = "start = 'A'i";
-
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 19, 0, 2, 2, // MATCH_STRING_IC
- 21, 1, // * ACCEPT_N
- 23, 1 // * FAIL
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(grammar, constsDetails([
- "\"a\"",
- "peg$literalExpectation(\"A\", true)"
- ]));
- });
- });
- });
-
- describe("for class", function() {
- it("generates correct bytecode", function() {
- expect(pass).to.changeAST("start = [a]", bytecodeDetails([
- 20, 0, 2, 2, // MATCH_REGEXP
- 21, 1, // * ACCEPT_N
- 23, 1 // * FAIL
- ]));
- });
-
- describe("non-inverted case-sensitive", function() {
- it("defines correct constants", function() {
- expect(pass).to.changeAST("start = [a]", constsDetails([
- "/^[a]/",
- "peg$classExpectation([\"a\"], false, false)"
- ]));
- });
- });
-
- describe("inverted case-sensitive", function() {
- it("defines correct constants", function() {
- expect(pass).to.changeAST("start = [^a]", constsDetails([
- "/^[^a]/",
- "peg$classExpectation([\"a\"], true, false)"
- ]));
- });
- });
-
- describe("non-inverted case-insensitive", function() {
- it("defines correct constants", function() {
- expect(pass).to.changeAST("start = [a]i", constsDetails([
- "/^[a]/i",
- "peg$classExpectation([\"a\"], false, true)"
- ]));
- });
- });
-
- describe("complex", function() {
- it("defines correct constants", function() {
- expect(pass).to.changeAST("start = [ab-def-hij-l]", constsDetails([
- "/^[ab-def-hij-l]/",
- "peg$classExpectation([\"a\", [\"b\", \"d\"], \"e\", [\"f\", \"h\"], \"i\", [\"j\", \"l\"]], false, false)"
- ]));
- });
- });
- });
-
- describe("for any", function() {
- let grammar = "start = .";
-
- it("generates bytecode", function() {
- expect(pass).to.changeAST(grammar, bytecodeDetails([
- 17, 2, 2, // MATCH_ANY
- 21, 1, // * ACCEPT_N
- 23, 0 // * FAIL
- ]));
- });
-
- it("defines correct constants", function() {
- expect(pass).to.changeAST(
- grammar,
- constsDetails(["peg$anyExpectation()"])
- );
- });
- });
+ function bytecodeDetails(bytecode) {
+ return {
+ rules: [{ bytecode: bytecode }]
+ };
+ }
+
+ function constsDetails(consts) { return { consts: consts }; }
+
+ describe("for grammar", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST([
+ "a = 'a'",
+ "b = 'b'",
+ "c = 'c'"
+ ].join("\n"), {
+ rules: [
+ { bytecode: [18, 0, 2, 2, 22, 0, 23, 1] },
+ { bytecode: [18, 2, 2, 2, 22, 2, 23, 3] },
+ { bytecode: [18, 4, 2, 2, 22, 4, 23, 5] }
+ ]
+ });
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST([
+ "a = 'a'",
+ "b = 'b'",
+ "c = 'c'"
+ ].join("\n"), constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)",
+ "\"b\"",
+ "peg$literalExpectation(\"b\", false)",
+ "\"c\"",
+ "peg$literalExpectation(\"c\", false)"
+ ]));
+ });
+ });
+
+ describe("for rule", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST("start = 'a'", bytecodeDetails([
+ 18, 0, 2, 2, 22, 0, 23, 1 //
+ ]));
+ });
+ });
+
+ describe("for named", function() {
+ let grammar = "start 'start' = 'a'";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 28, // SILENT_FAILS_ON
+ 18, 1, 2, 2, 22, 1, 23, 2, //
+ 29, // SILENT_FAILS_OFF
+ 14, 2, 0, // IF_ERROR
+ 23, 0 // * FAIL
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "peg$otherExpectation(\"start\")",
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)"
+ ]));
+ });
+ });
+
+ describe("for choice", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST("start = 'a' / 'b' / 'c'", bytecodeDetails([
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 14, 21, 0, // IF_ERROR
+ 6, // * POP
+ 18, 2, 2, 2, 22, 2, 23, 3, //
+ 14, 9, 0, // IF_ERROR
+ 6, // * POP
+ 18, 4, 2, 2, 22, 4, 23, 5 //
+ ]));
+ });
+ });
+
+ describe("for action", function() {
+ describe("without labels", function() {
+ let grammar = "start = 'a' { code }";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 6, 0, // IF_NOT_ERROR
+ 24, 1, // * LOAD_SAVED_POS
+ 26, 2, 1, 0, // CALL
+ 9 // NIP
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)",
+ "function() { code }"
+ ]));
+ });
+ });
+
+ describe("with one label", function() {
+ let grammar = "start = a:'a' { code }";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 7, 0, // IF_NOT_ERROR
+ 24, 1, // * LOAD_SAVED_POS
+ 26, 2, 1, 1, 0, // CALL
+ 9 // NIP
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)",
+ "function(a) { code }"
+ ]));
+ });
+ });
+
+ describe("with multiple labels", function() {
+ let grammar = "start = a:'a' b:'b' c:'c' { code }";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 40, 3, // IF_NOT_ERROR
+ 18, 2, 2, 2, 22, 2, 23, 3, // *
+ 15, 25, 4, // IF_NOT_ERROR
+ 18, 4, 2, 2, 22, 4, 23, 5, // *
+ 15, 10, 4, // IF_NOT_ERROR
+ 24, 3, // * LOAD_SAVED_POS
+ 26, 6, 3, 3, 2, 1, 0, // CALL
+ 9, // NIP
+ 8, 3, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 8, 2, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 6, // * POP
+ 7, // POP_CURR_POS
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)",
+ "\"b\"",
+ "peg$literalExpectation(\"b\", false)",
+ "\"c\"",
+ "peg$literalExpectation(\"c\", false)",
+ "function(a, b, c) { code }"
+ ]));
+ });
+ });
+ });
+
+ describe("for sequence", function() {
+ let grammar = "start = 'a' 'b' 'c'";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 33, 3, // IF_NOT_ERROR
+ 18, 2, 2, 2, 22, 2, 23, 3, // *
+ 15, 18, 4, // IF_NOT_ERROR
+ 18, 4, 2, 2, 22, 4, 23, 5, // *
+ 15, 3, 4, // IF_NOT_ERROR
+ 11, 3, // * WRAP
+ 9, // NIP
+ 8, 3, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 8, 2, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 6, // * POP
+ 7, // POP_CURR_POS
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)",
+ "\"b\"",
+ "peg$literalExpectation(\"b\", false)",
+ "\"c\"",
+ "peg$literalExpectation(\"c\", false)"
+ ]));
+ });
+ });
+
+ describe("for labeled", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST("start = a:'a'", bytecodeDetails([
+ 18, 0, 2, 2, 22, 0, 23, 1 //
+ ]));
+ });
+ });
+
+ describe("for text", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST("start = $'a'", bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 2, 1, // IF_NOT_ERROR
+ 6, // * POP
+ 12, // TEXT
+ 9 // * NIP
+ ]));
+ });
+ });
+
+ describe("for simple_and", function() {
+ let grammar = "start = &'a'";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 28, // SILENT_FAILS_ON
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 29, // SILENT_FAILS_OFF
+ 15, 3, 3, // IF_NOT_ERROR
+ 6, // * POP
+ 7, // POP_CURR_POS
+ 1, // PUSH_UNDEFINED
+ 6, // * POP
+ 6, // POP
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)"
+ ]));
+ });
+ });
+
+ describe("for simple_not", function() {
+ let grammar = "start = !'a'";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 28, // SILENT_FAILS_ON
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 29, // SILENT_FAILS_OFF
+ 14, 3, 3, // IF_ERROR
+ 6, // * POP
+ 6, // POP
+ 1, // PUSH_UNDEFINED
+ 6, // * POP
+ 7, // POP_CURR_POS
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)"
+ ]));
+ });
+ });
+
+ describe("for optional", function() {
+ let grammar = "start = 'a'?";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 14, 2, 0, // IF_ERROR
+ 6, // * POP
+ 2 // PUSH_NULL
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)"
+ ]));
+ });
+ });
+
+ describe("for zero_or_more", function() {
+ let grammar = "start = 'a'*";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 4, // PUSH_EMPTY_ARRAY
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 16, 9, // WHILE_NOT_ERROR
+ 10, // * APPEND
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 6 // POP
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)"
+ ]));
+ });
+ });
+
+ describe("for one_or_more", function() {
+ let grammar = "start = 'a'+";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 4, // PUSH_EMPTY_ARRAY
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 12, 3, // IF_NOT_ERROR
+ 16, 9, // * WHILE_NOT_ERROR
+ 10, // * APPEND
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 6, // POP
+ 6, // * POP
+ 6, // POP
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)"
+ ]));
+ });
+ });
+
+ describe("for group", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST("start = ('a')", bytecodeDetails([
+ 18, 0, 2, 2, 22, 0, 23, 1 //
+ ]));
+ });
+ });
+
+ describe("for semantic_and", function() {
+ describe("without labels", function() {
+ let grammar = "start = &{ code }";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 25, // UPDATE_SAVED_POS
+ 26, 0, 0, 0, // CALL
+ 13, 2, 2, // IF
+ 6, // * POP
+ 1, // PUSH_UNDEFINED
+ 6, // * POP
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(
+ grammar,
+ constsDetails(["function() { code }"])
+ );
+ });
+ });
+
+ describe("with labels", function() {
+ let grammar = "start = a:'a' b:'b' c:'c' &{ code }";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 55, 3, // IF_NOT_ERROR
+ 18, 2, 2, 2, 22, 2, 23, 3, // *
+ 15, 40, 4, // IF_NOT_ERROR
+ 18, 4, 2, 2, 22, 4, 23, 5, // *
+ 15, 25, 4, // IF_NOT_ERROR
+ 25, // * UPDATE_SAVED_POS
+ 26, 6, 0, 3, 2, 1, 0, // CALL
+ 13, 2, 2, // IF
+ 6, // * POP
+ 1, // PUSH_UNDEFINED
+ 6, // * POP
+ 3, // PUSH_FAILED
+ 15, 3, 4, // IF_NOT_ERROR
+ 11, 4, // * WRAP
+ 9, // NIP
+ 8, 4, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 8, 3, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 8, 2, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 6, // * POP
+ 7, // POP_CURR_POS
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)",
+ "\"b\"",
+ "peg$literalExpectation(\"b\", false)",
+ "\"c\"",
+ "peg$literalExpectation(\"c\", false)",
+ "function(a, b, c) { code }"
+ ]));
+ });
+ });
+ });
+
+ describe("for semantic_not", function() {
+ describe("without labels", function() {
+ let grammar = "start = !{ code }";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 25, // UPDATE_SAVED_POS
+ 26, 0, 0, 0, // CALL
+ 13, 2, 2, // IF
+ 6, // * POP
+ 3, // PUSH_FAILED
+ 6, // * POP
+ 1 // PUSH_UNDEFINED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(
+ grammar,
+ constsDetails(["function() { code }"])
+ );
+ });
+ });
+
+ describe("with labels", function() {
+ let grammar = "start = a:'a' b:'b' c:'c' !{ code }";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 5, // PUSH_CURR_POS
+ 18, 0, 2, 2, 22, 0, 23, 1, //
+ 15, 55, 3, // IF_NOT_ERROR
+ 18, 2, 2, 2, 22, 2, 23, 3, // *
+ 15, 40, 4, // IF_NOT_ERROR
+ 18, 4, 2, 2, 22, 4, 23, 5, // *
+ 15, 25, 4, // IF_NOT_ERROR
+ 25, // * UPDATE_SAVED_POS
+ 26, 6, 0, 3, 2, 1, 0, // CALL
+ 13, 2, 2, // IF
+ 6, // * POP
+ 3, // PUSH_FAILED
+ 6, // * POP
+ 1, // PUSH_UNDEFINED
+ 15, 3, 4, // IF_NOT_ERROR
+ 11, 4, // * WRAP
+ 9, // NIP
+ 8, 4, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 8, 3, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 8, 2, // * POP_N
+ 7, // POP_CURR_POS
+ 3, // PUSH_FAILED
+ 6, // * POP
+ 7, // POP_CURR_POS
+ 3 // PUSH_FAILED
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)",
+ "\"b\"",
+ "peg$literalExpectation(\"b\", false)",
+ "\"c\"",
+ "peg$literalExpectation(\"c\", false)",
+ "function(a, b, c) { code }"
+ ]));
+ });
+ });
+ });
+
+ describe("for rule_ref", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST([
+ "start = other",
+ "other = 'other'"
+ ].join("\n"), {
+ rules: [
+ {
+ bytecode: [27, 1] // RULE
+ },
+ { }
+ ]
+ });
+ });
+ });
+
+ describe("for literal", function() {
+ describe("empty", function() {
+ let grammar = "start = ''";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 0, 0 // PUSH
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails(["\"\""]));
+ });
+ });
+
+ describe("non-empty case-sensitive", function() {
+ let grammar = "start = 'a'";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 18, 0, 2, 2, // MATCH_STRING
+ 22, 0, // * ACCEPT_STRING
+ 23, 1 // * FAIL
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"a\", false)"
+ ]));
+ });
+ });
+
+ describe("non-empty case-insensitive", function() {
+ let grammar = "start = 'A'i";
+
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 19, 0, 2, 2, // MATCH_STRING_IC
+ 21, 1, // * ACCEPT_N
+ 23, 1 // * FAIL
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(grammar, constsDetails([
+ "\"a\"",
+ "peg$literalExpectation(\"A\", true)"
+ ]));
+ });
+ });
+ });
+
+ describe("for class", function() {
+ it("generates correct bytecode", function() {
+ expect(pass).to.changeAST("start = [a]", bytecodeDetails([
+ 20, 0, 2, 2, // MATCH_REGEXP
+ 21, 1, // * ACCEPT_N
+ 23, 1 // * FAIL
+ ]));
+ });
+
+ describe("non-inverted case-sensitive", function() {
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST("start = [a]", constsDetails([
+ "/^[a]/",
+ "peg$classExpectation([\"a\"], false, false)"
+ ]));
+ });
+ });
+
+ describe("inverted case-sensitive", function() {
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST("start = [^a]", constsDetails([
+ "/^[^a]/",
+ "peg$classExpectation([\"a\"], true, false)"
+ ]));
+ });
+ });
+
+ describe("non-inverted case-insensitive", function() {
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST("start = [a]i", constsDetails([
+ "/^[a]/i",
+ "peg$classExpectation([\"a\"], false, true)"
+ ]));
+ });
+ });
+
+ describe("complex", function() {
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST("start = [ab-def-hij-l]", constsDetails([
+ "/^[ab-def-hij-l]/",
+ "peg$classExpectation([\"a\", [\"b\", \"d\"], \"e\", [\"f\", \"h\"], \"i\", [\"j\", \"l\"]], false, false)"
+ ]));
+ });
+ });
+ });
+
+ describe("for any", function() {
+ let grammar = "start = .";
+
+ it("generates bytecode", function() {
+ expect(pass).to.changeAST(grammar, bytecodeDetails([
+ 17, 2, 2, // MATCH_ANY
+ 21, 1, // * ACCEPT_N
+ 23, 0 // * FAIL
+ ]));
+ });
+
+ it("defines correct constants", function() {
+ expect(pass).to.changeAST(
+ grammar,
+ constsDetails(["peg$anyExpectation()"])
+ );
+ });
+ });
});
diff --git a/test/unit/compiler/passes/helpers.js b/test/unit/compiler/passes/helpers.js
index 1eed9aa..18322ba 100644
--- a/test/unit/compiler/passes/helpers.js
+++ b/test/unit/compiler/passes/helpers.js
@@ -3,86 +3,86 @@
let parser = require("../../../../lib/parser");
module.exports = function(chai, utils) {
- let Assertion = chai.Assertion;
-
- Assertion.addMethod("changeAST", function(grammar, props, options) {
- options = options !== undefined ? options : {};
-
- function matchProps(value, props) {
- function isArray(value) {
- return Object.prototype.toString.apply(value) === "[object Array]";
- }
-
- function isObject(value) {
- return value !== null && typeof value === "object";
- }
-
- if (isArray(props)) {
- if (!isArray(value)) { return false; }
-
- if (value.length !== props.length) { return false; }
- for (let i = 0; i < props.length; i++) {
- if (!matchProps(value[i], props[i])) { return false; }
- }
-
- return true;
- } else if (isObject(props)) {
- if (!isObject(value)) { return false; }
-
- let keys = Object.keys(props);
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
-
- if (!(key in value)) { return false; }
-
- if (!matchProps(value[key], props[key])) { return false; }
- }
-
- return true;
- } else {
- return value === props;
- }
- }
-
- let ast = parser.parse(grammar);
-
- utils.flag(this, "object")(ast, options);
-
- this.assert(
- matchProps(ast, props),
- "expected #{this} to change the AST to match #{exp}",
- "expected #{this} to not change the AST to match #{exp}",
- props,
- ast
- );
- });
-
- Assertion.addMethod("reportError", function(grammar, props) {
- let ast = parser.parse(grammar);
-
- let passed, result;
-
- try {
- utils.flag(this, "object")(ast);
- passed = true;
- } catch (e) {
- result = e;
- passed = false;
- }
-
- this.assert(
- !passed,
- "expected #{this} to report an error but it didn't",
- "expected #{this} to not report an error but #{act} was reported",
- null,
- result
- );
-
- if (!passed && props !== undefined) {
- Object.keys(props).forEach(key => {
- new Assertion(result).to.have.property(key)
- .that.is.deep.equal(props[key]);
- });
- }
- });
+ let Assertion = chai.Assertion;
+
+ Assertion.addMethod("changeAST", function(grammar, props, options) {
+ options = options !== undefined ? options : {};
+
+ function matchProps(value, props) {
+ function isArray(value) {
+ return Object.prototype.toString.apply(value) === "[object Array]";
+ }
+
+ function isObject(value) {
+ return value !== null && typeof value === "object";
+ }
+
+ if (isArray(props)) {
+ if (!isArray(value)) { return false; }
+
+ if (value.length !== props.length) { return false; }
+ for (let i = 0; i < props.length; i++) {
+ if (!matchProps(value[i], props[i])) { return false; }
+ }
+
+ return true;
+ } else if (isObject(props)) {
+ if (!isObject(value)) { return false; }
+
+ let keys = Object.keys(props);
+ for (let i = 0; i < keys.length; i++) {
+ let key = keys[i];
+
+ if (!(key in value)) { return false; }
+
+ if (!matchProps(value[key], props[key])) { return false; }
+ }
+
+ return true;
+ } else {
+ return value === props;
+ }
+ }
+
+ let ast = parser.parse(grammar);
+
+ utils.flag(this, "object")(ast, options);
+
+ this.assert(
+ matchProps(ast, props),
+ "expected #{this} to change the AST to match #{exp}",
+ "expected #{this} to not change the AST to match #{exp}",
+ props,
+ ast
+ );
+ });
+
+ Assertion.addMethod("reportError", function(grammar, props) {
+ let ast = parser.parse(grammar);
+
+ let passed, result;
+
+ try {
+ utils.flag(this, "object")(ast);
+ passed = true;
+ } catch (e) {
+ result = e;
+ passed = false;
+ }
+
+ this.assert(
+ !passed,
+ "expected #{this} to report an error but it didn't",
+ "expected #{this} to not report an error but #{act} was reported",
+ null,
+ result
+ );
+
+ if (!passed && props !== undefined) {
+ Object.keys(props).forEach(key => {
+ new Assertion(result).to.have.property(key)
+ .that.is.deep.equal(props[key]);
+ });
+ }
+ });
};
diff --git a/test/unit/compiler/passes/remove-proxy-rules.spec.js b/test/unit/compiler/passes/remove-proxy-rules.spec.js
index e7afc23..54803aa 100644
--- a/test/unit/compiler/passes/remove-proxy-rules.spec.js
+++ b/test/unit/compiler/passes/remove-proxy-rules.spec.js
@@ -9,51 +9,51 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |removeProxyRules|", function() {
- describe("when a proxy rule isn't listed in |allowedStartRules|", function() {
- it("updates references and removes it", function() {
- expect(pass).to.changeAST(
- [
- "start = proxy",
- "proxy = proxied",
- "proxied = 'a'"
- ].join("\n"),
- {
- rules: [
- {
- name: "start",
- expression: { type: "rule_ref", name: "proxied" }
- },
- { name: "proxied" }
- ]
- },
- { allowedStartRules: ["start"] }
- );
- });
- });
+ describe("when a proxy rule isn't listed in |allowedStartRules|", function() {
+ it("updates references and removes it", function() {
+ expect(pass).to.changeAST(
+ [
+ "start = proxy",
+ "proxy = proxied",
+ "proxied = 'a'"
+ ].join("\n"),
+ {
+ rules: [
+ {
+ name: "start",
+ expression: { type: "rule_ref", name: "proxied" }
+ },
+ { name: "proxied" }
+ ]
+ },
+ { allowedStartRules: ["start"] }
+ );
+ });
+ });
- describe("when a proxy rule is listed in |allowedStartRules|", function() {
- it("updates references but doesn't remove it", function() {
- expect(pass).to.changeAST(
- [
- "start = proxy",
- "proxy = proxied",
- "proxied = 'a'"
- ].join("\n"),
- {
- rules: [
- {
- name: "start",
- expression: { type: "rule_ref", name: "proxied" }
- },
- {
- name: "proxy",
- expression: { type: "rule_ref", name: "proxied" }
- },
- { name: "proxied" }
- ]
- },
- { allowedStartRules: ["start", "proxy"] }
- );
- });
- });
+ describe("when a proxy rule is listed in |allowedStartRules|", function() {
+ it("updates references but doesn't remove it", function() {
+ expect(pass).to.changeAST(
+ [
+ "start = proxy",
+ "proxy = proxied",
+ "proxied = 'a'"
+ ].join("\n"),
+ {
+ rules: [
+ {
+ name: "start",
+ expression: { type: "rule_ref", name: "proxied" }
+ },
+ {
+ name: "proxy",
+ expression: { type: "rule_ref", name: "proxied" }
+ },
+ { name: "proxied" }
+ ]
+ },
+ { allowedStartRules: ["start", "proxy"] }
+ );
+ });
+ });
});
diff --git a/test/unit/compiler/passes/report-duplicate-labels.spec.js b/test/unit/compiler/passes/report-duplicate-labels.spec.js
index a775e89..8e0672b 100644
--- a/test/unit/compiler/passes/report-duplicate-labels.spec.js
+++ b/test/unit/compiler/passes/report-duplicate-labels.spec.js
@@ -9,55 +9,55 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportDuplicateLabels|", function() {
- describe("in a sequence", function() {
- it("reports labels duplicate with labels of preceding elements", function() {
- expect(pass).to.reportError("start = a:'a' a:'a'", {
- message: "Label \"a\" is already defined at line 1, column 9.",
- location: {
- start: { offset: 14, line: 1, column: 15 },
- end: { offset: 19, line: 1, column: 20 }
- }
- });
- });
-
- it("doesn't report labels duplicate with labels in subexpressions", function() {
- expect(pass).to.not.reportError("start = ('a' / a:'a' / 'a') a:'a'");
- expect(pass).to.not.reportError("start = (a:'a' { }) a:'a'");
- expect(pass).to.not.reportError("start = ('a' a:'a' 'a') a:'a'");
- expect(pass).to.not.reportError("start = b:(a:'a') a:'a'");
- expect(pass).to.not.reportError("start = $(a:'a') a:'a'");
- expect(pass).to.not.reportError("start = &(a:'a') a:'a'");
- expect(pass).to.not.reportError("start = !(a:'a') a:'a'");
- expect(pass).to.not.reportError("start = (a:'a')? a:'a'");
- expect(pass).to.not.reportError("start = (a:'a')* a:'a'");
- expect(pass).to.not.reportError("start = (a:'a')+ a:'a'");
- expect(pass).to.not.reportError("start = (a:'a') a:'a'");
- });
- });
-
- describe("in a choice", function() {
- it("doesn't report labels duplicate with labels of preceding alternatives", function() {
- expect(pass).to.not.reportError("start = a:'a' / a:'a'");
- });
- });
-
- describe("in outer sequence", function() {
- it("reports labels duplicate with labels of preceding elements", function() {
- expect(pass).to.reportError("start = a:'a' (a:'a')", {
- message: "Label \"a\" is already defined at line 1, column 9.",
- location: {
- start: { offset: 15, line: 1, column: 16 },
- end: { offset: 20, line: 1, column: 21 }
- }
- });
- });
-
- it("doesn't report labels duplicate with the label of the current element", function() {
- expect(pass).to.not.reportError("start = a:(a:'a')");
- });
-
- it("doesn't report labels duplicate with labels of following elements", function() {
- expect(pass).to.not.reportError("start = (a:'a') a:'a'");
- });
- });
+ describe("in a sequence", function() {
+ it("reports labels duplicate with labels of preceding elements", function() {
+ expect(pass).to.reportError("start = a:'a' a:'a'", {
+ message: "Label \"a\" is already defined at line 1, column 9.",
+ location: {
+ start: { offset: 14, line: 1, column: 15 },
+ end: { offset: 19, line: 1, column: 20 }
+ }
+ });
+ });
+
+ it("doesn't report labels duplicate with labels in subexpressions", function() {
+ expect(pass).to.not.reportError("start = ('a' / a:'a' / 'a') a:'a'");
+ expect(pass).to.not.reportError("start = (a:'a' { }) a:'a'");
+ expect(pass).to.not.reportError("start = ('a' a:'a' 'a') a:'a'");
+ expect(pass).to.not.reportError("start = b:(a:'a') a:'a'");
+ expect(pass).to.not.reportError("start = $(a:'a') a:'a'");
+ expect(pass).to.not.reportError("start = &(a:'a') a:'a'");
+ expect(pass).to.not.reportError("start = !(a:'a') a:'a'");
+ expect(pass).to.not.reportError("start = (a:'a')? a:'a'");
+ expect(pass).to.not.reportError("start = (a:'a')* a:'a'");
+ expect(pass).to.not.reportError("start = (a:'a')+ a:'a'");
+ expect(pass).to.not.reportError("start = (a:'a') a:'a'");
+ });
+ });
+
+ describe("in a choice", function() {
+ it("doesn't report labels duplicate with labels of preceding alternatives", function() {
+ expect(pass).to.not.reportError("start = a:'a' / a:'a'");
+ });
+ });
+
+ describe("in outer sequence", function() {
+ it("reports labels duplicate with labels of preceding elements", function() {
+ expect(pass).to.reportError("start = a:'a' (a:'a')", {
+ message: "Label \"a\" is already defined at line 1, column 9.",
+ location: {
+ start: { offset: 15, line: 1, column: 16 },
+ end: { offset: 20, line: 1, column: 21 }
+ }
+ });
+ });
+
+ it("doesn't report labels duplicate with the label of the current element", function() {
+ expect(pass).to.not.reportError("start = a:(a:'a')");
+ });
+
+ it("doesn't report labels duplicate with labels of following elements", function() {
+ expect(pass).to.not.reportError("start = (a:'a') a:'a'");
+ });
+ });
});
diff --git a/test/unit/compiler/passes/report-duplicate-rules.spec.js b/test/unit/compiler/passes/report-duplicate-rules.spec.js
index 6c9283c..9cfb47d 100644
--- a/test/unit/compiler/passes/report-duplicate-rules.spec.js
+++ b/test/unit/compiler/passes/report-duplicate-rules.spec.js
@@ -9,16 +9,16 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportDuplicateRules|", function() {
- it("reports duplicate rules", function() {
- expect(pass).to.reportError([
- "start = 'a'",
- "start = 'b'"
- ].join("\n"), {
- message: "Rule \"start\" is already defined at line 1, column 1.",
- location: {
- start: { offset: 12, line: 2, column: 1 },
- end: { offset: 23, line: 2, column: 12 }
- }
- });
- });
+ it("reports duplicate rules", function() {
+ expect(pass).to.reportError([
+ "start = 'a'",
+ "start = 'b'"
+ ].join("\n"), {
+ message: "Rule \"start\" is already defined at line 1, column 1.",
+ location: {
+ start: { offset: 12, line: 2, column: 1 },
+ end: { offset: 23, line: 2, column: 12 }
+ }
+ });
+ });
});
diff --git a/test/unit/compiler/passes/report-infinite-recursion.spec.js b/test/unit/compiler/passes/report-infinite-recursion.spec.js
index 28b46f3..46ee7a0 100644
--- a/test/unit/compiler/passes/report-infinite-recursion.spec.js
+++ b/test/unit/compiler/passes/report-infinite-recursion.spec.js
@@ -9,111 +9,111 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportInfiniteRecursion|", function() {
- it("reports direct left recursion", function() {
- expect(pass).to.reportError("start = start", {
- message: "Possible infinite loop when parsing (left recursion: start -> start).",
- location: {
- start: { offset: 8, line: 1, column: 9 },
- end: { offset: 13, line: 1, column: 14 }
- }
- });
- });
-
- it("reports indirect left recursion", function() {
- expect(pass).to.reportError([
- "start = stop",
- "stop = start"
- ].join("\n"), {
- message: "Possible infinite loop when parsing (left recursion: start -> stop -> start).",
- location: {
- start: { offset: 20, line: 2, column: 8 },
- end: { offset: 25, line: 2, column: 13 }
- }
- });
- });
-
- describe("in sequences", function() {
- it("reports left recursion if all preceding elements match empty string", function() {
- expect(pass).to.reportError("start = '' '' '' start");
- });
-
- it("doesn't report left recursion if some preceding element doesn't match empty string", function() {
- expect(pass).to.not.reportError("start = 'a' '' '' start");
- expect(pass).to.not.reportError("start = '' 'a' '' start");
- expect(pass).to.not.reportError("start = '' '' 'a' start");
- });
-
- // Regression test for #359.
- it("reports left recursion when rule reference is wrapped in an expression", function() {
- expect(pass).to.reportError("start = '' start?");
- });
-
- it("computes expressions that always consume input on success correctly", function() {
- expect(pass).to.reportError([
- "start = a start",
- "a 'a' = ''"
- ].join("\n"));
- expect(pass).to.not.reportError([
- "start = a start",
- "a 'a' = 'a'"
- ].join("\n"));
-
- expect(pass).to.reportError("start = ('' / 'a' / 'b') start");
- expect(pass).to.reportError("start = ('a' / '' / 'b') start");
- expect(pass).to.reportError("start = ('a' / 'b' / '') start");
- expect(pass).to.not.reportError("start = ('a' / 'b' / 'c') start");
-
- expect(pass).to.reportError("start = ('' { }) start");
- expect(pass).to.not.reportError("start = ('a' { }) start");
-
- expect(pass).to.reportError("start = ('' '' '') start");
- expect(pass).to.not.reportError("start = ('a' '' '') start");
- expect(pass).to.not.reportError("start = ('' 'a' '') start");
- expect(pass).to.not.reportError("start = ('' '' 'a') start");
-
- expect(pass).to.reportError("start = a:'' start");
- expect(pass).to.not.reportError("start = a:'a' start");
-
- expect(pass).to.reportError("start = $'' start");
- expect(pass).to.not.reportError("start = $'a' start");
-
- expect(pass).to.reportError("start = &'' start");
- expect(pass).to.reportError("start = &'a' start");
-
- expect(pass).to.reportError("start = !'' start");
- expect(pass).to.reportError("start = !'a' start");
-
- expect(pass).to.reportError("start = ''? start");
- expect(pass).to.reportError("start = 'a'? start");
-
- expect(pass).to.reportError("start = ''* start");
- expect(pass).to.reportError("start = 'a'* start");
-
- expect(pass).to.reportError("start = ''+ start");
- expect(pass).to.not.reportError("start = 'a'+ start");
-
- expect(pass).to.reportError("start = ('') start");
- expect(pass).to.not.reportError("start = ('a') start");
-
- expect(pass).to.reportError("start = &{ } start");
-
- expect(pass).to.reportError("start = !{ } start");
-
- expect(pass).to.reportError([
- "start = a start",
- "a = ''"
- ].join("\n"));
- expect(pass).to.not.reportError([
- "start = a start",
- "a = 'a'"
- ].join("\n"));
-
- expect(pass).to.reportError("start = '' start");
- expect(pass).to.not.reportError("start = 'a' start");
-
- expect(pass).to.not.reportError("start = [a-d] start");
-
- expect(pass).to.not.reportError("start = . start");
- });
- });
+ it("reports direct left recursion", function() {
+ expect(pass).to.reportError("start = start", {
+ message: "Possible infinite loop when parsing (left recursion: start -> start).",
+ location: {
+ start: { offset: 8, line: 1, column: 9 },
+ end: { offset: 13, line: 1, column: 14 }
+ }
+ });
+ });
+
+ it("reports indirect left recursion", function() {
+ expect(pass).to.reportError([
+ "start = stop",
+ "stop = start"
+ ].join("\n"), {
+ message: "Possible infinite loop when parsing (left recursion: start -> stop -> start).",
+ location: {
+ start: { offset: 20, line: 2, column: 8 },
+ end: { offset: 25, line: 2, column: 13 }
+ }
+ });
+ });
+
+ describe("in sequences", function() {
+ it("reports left recursion if all preceding elements match empty string", function() {
+ expect(pass).to.reportError("start = '' '' '' start");
+ });
+
+ it("doesn't report left recursion if some preceding element doesn't match empty string", function() {
+ expect(pass).to.not.reportError("start = 'a' '' '' start");
+ expect(pass).to.not.reportError("start = '' 'a' '' start");
+ expect(pass).to.not.reportError("start = '' '' 'a' start");
+ });
+
+ // Regression test for #359.
+ it("reports left recursion when rule reference is wrapped in an expression", function() {
+ expect(pass).to.reportError("start = '' start?");
+ });
+
+ it("computes expressions that always consume input on success correctly", function() {
+ expect(pass).to.reportError([
+ "start = a start",
+ "a 'a' = ''"
+ ].join("\n"));
+ expect(pass).to.not.reportError([
+ "start = a start",
+ "a 'a' = 'a'"
+ ].join("\n"));
+
+ expect(pass).to.reportError("start = ('' / 'a' / 'b') start");
+ expect(pass).to.reportError("start = ('a' / '' / 'b') start");
+ expect(pass).to.reportError("start = ('a' / 'b' / '') start");
+ expect(pass).to.not.reportError("start = ('a' / 'b' / 'c') start");
+
+ expect(pass).to.reportError("start = ('' { }) start");
+ expect(pass).to.not.reportError("start = ('a' { }) start");
+
+ expect(pass).to.reportError("start = ('' '' '') start");
+ expect(pass).to.not.reportError("start = ('a' '' '') start");
+ expect(pass).to.not.reportError("start = ('' 'a' '') start");
+ expect(pass).to.not.reportError("start = ('' '' 'a') start");
+
+ expect(pass).to.reportError("start = a:'' start");
+ expect(pass).to.not.reportError("start = a:'a' start");
+
+ expect(pass).to.reportError("start = $'' start");
+ expect(pass).to.not.reportError("start = $'a' start");
+
+ expect(pass).to.reportError("start = &'' start");
+ expect(pass).to.reportError("start = &'a' start");
+
+ expect(pass).to.reportError("start = !'' start");
+ expect(pass).to.reportError("start = !'a' start");
+
+ expect(pass).to.reportError("start = ''? start");
+ expect(pass).to.reportError("start = 'a'? start");
+
+ expect(pass).to.reportError("start = ''* start");
+ expect(pass).to.reportError("start = 'a'* start");
+
+ expect(pass).to.reportError("start = ''+ start");
+ expect(pass).to.not.reportError("start = 'a'+ start");
+
+ expect(pass).to.reportError("start = ('') start");
+ expect(pass).to.not.reportError("start = ('a') start");
+
+ expect(pass).to.reportError("start = &{ } start");
+
+ expect(pass).to.reportError("start = !{ } start");
+
+ expect(pass).to.reportError([
+ "start = a start",
+ "a = ''"
+ ].join("\n"));
+ expect(pass).to.not.reportError([
+ "start = a start",
+ "a = 'a'"
+ ].join("\n"));
+
+ expect(pass).to.reportError("start = '' start");
+ expect(pass).to.not.reportError("start = 'a' start");
+
+ expect(pass).to.not.reportError("start = [a-d] start");
+
+ expect(pass).to.not.reportError("start = . start");
+ });
+ });
});
diff --git a/test/unit/compiler/passes/report-infinite-repetition.spec.js b/test/unit/compiler/passes/report-infinite-repetition.spec.js
index ff3dea9..9b3ca5d 100644
--- a/test/unit/compiler/passes/report-infinite-repetition.spec.js
+++ b/test/unit/compiler/passes/report-infinite-repetition.spec.js
@@ -9,91 +9,91 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportInfiniteRepetition|", function() {
- it("reports infinite loops for zero_or_more", function() {
- expect(pass).to.reportError("start = ('')*", {
- message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
- location: {
- start: { offset: 8, line: 1, column: 9 },
- end: { offset: 13, line: 1, column: 14 }
- }
- });
- });
-
- it("reports infinite loops for one_or_more", function() {
- expect(pass).to.reportError("start = ('')+", {
- message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
- location: {
- start: { offset: 8, line: 1, column: 9 },
- end: { offset: 13, line: 1, column: 14 }
- }
- });
- });
-
- it("computes expressions that always consume input on success correctly", function() {
- expect(pass).to.reportError([
- "start = a*",
- "a 'a' = ''"
- ].join("\n"));
- expect(pass).to.not.reportError([
- "start = a*",
- "a 'a' = 'a'"
- ].join("\n"));
-
- expect(pass).to.reportError("start = ('' / 'a' / 'b')*");
- expect(pass).to.reportError("start = ('a' / '' / 'b')*");
- expect(pass).to.reportError("start = ('a' / 'b' / '')*");
- expect(pass).to.not.reportError("start = ('a' / 'b' / 'c')*");
-
- expect(pass).to.reportError("start = ('' { })*");
- expect(pass).to.not.reportError("start = ('a' { })*");
-
- expect(pass).to.reportError("start = ('' '' '')*");
- expect(pass).to.not.reportError("start = ('a' '' '')*");
- expect(pass).to.not.reportError("start = ('' 'a' '')*");
- expect(pass).to.not.reportError("start = ('' '' 'a')*");
-
- expect(pass).to.reportError("start = (a:'')*");
- expect(pass).to.not.reportError("start = (a:'a')*");
-
- expect(pass).to.reportError("start = ($'')*");
- expect(pass).to.not.reportError("start = ($'a')*");
-
- expect(pass).to.reportError("start = (&'')*");
- expect(pass).to.reportError("start = (&'a')*");
-
- expect(pass).to.reportError("start = (!'')*");
- expect(pass).to.reportError("start = (!'a')*");
-
- expect(pass).to.reportError("start = (''?)*");
- expect(pass).to.reportError("start = ('a'?)*");
-
- expect(pass).to.reportError("start = (''*)*");
- expect(pass).to.reportError("start = ('a'*)*");
-
- expect(pass).to.reportError("start = (''+)*");
- expect(pass).to.not.reportError("start = ('a'+)*");
-
- expect(pass).to.reportError("start = ('')*");
- expect(pass).to.not.reportError("start = ('a')*");
-
- expect(pass).to.reportError("start = (&{ })*");
-
- expect(pass).to.reportError("start = (!{ })*");
-
- expect(pass).to.reportError([
- "start = a*",
- "a = ''"
- ].join("\n"));
- expect(pass).to.not.reportError([
- "start = a*",
- "a = 'a'"
- ].join("\n"));
-
- expect(pass).to.reportError("start = ''*");
- expect(pass).to.not.reportError("start = 'a'*");
-
- expect(pass).to.not.reportError("start = [a-d]*");
-
- expect(pass).to.not.reportError("start = .*");
- });
+ it("reports infinite loops for zero_or_more", function() {
+ expect(pass).to.reportError("start = ('')*", {
+ message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
+ location: {
+ start: { offset: 8, line: 1, column: 9 },
+ end: { offset: 13, line: 1, column: 14 }
+ }
+ });
+ });
+
+ it("reports infinite loops for one_or_more", function() {
+ expect(pass).to.reportError("start = ('')+", {
+ message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
+ location: {
+ start: { offset: 8, line: 1, column: 9 },
+ end: { offset: 13, line: 1, column: 14 }
+ }
+ });
+ });
+
+ it("computes expressions that always consume input on success correctly", function() {
+ expect(pass).to.reportError([
+ "start = a*",
+ "a 'a' = ''"
+ ].join("\n"));
+ expect(pass).to.not.reportError([
+ "start = a*",
+ "a 'a' = 'a'"
+ ].join("\n"));
+
+ expect(pass).to.reportError("start = ('' / 'a' / 'b')*");
+ expect(pass).to.reportError("start = ('a' / '' / 'b')*");
+ expect(pass).to.reportError("start = ('a' / 'b' / '')*");
+ expect(pass).to.not.reportError("start = ('a' / 'b' / 'c')*");
+
+ expect(pass).to.reportError("start = ('' { })*");
+ expect(pass).to.not.reportError("start = ('a' { })*");
+
+ expect(pass).to.reportError("start = ('' '' '')*");
+ expect(pass).to.not.reportError("start = ('a' '' '')*");
+ expect(pass).to.not.reportError("start = ('' 'a' '')*");
+ expect(pass).to.not.reportError("start = ('' '' 'a')*");
+
+ expect(pass).to.reportError("start = (a:'')*");
+ expect(pass).to.not.reportError("start = (a:'a')*");
+
+ expect(pass).to.reportError("start = ($'')*");
+ expect(pass).to.not.reportError("start = ($'a')*");
+
+ expect(pass).to.reportError("start = (&'')*");
+ expect(pass).to.reportError("start = (&'a')*");
+
+ expect(pass).to.reportError("start = (!'')*");
+ expect(pass).to.reportError("start = (!'a')*");
+
+ expect(pass).to.reportError("start = (''?)*");
+ expect(pass).to.reportError("start = ('a'?)*");
+
+ expect(pass).to.reportError("start = (''*)*");
+ expect(pass).to.reportError("start = ('a'*)*");
+
+ expect(pass).to.reportError("start = (''+)*");
+ expect(pass).to.not.reportError("start = ('a'+)*");
+
+ expect(pass).to.reportError("start = ('')*");
+ expect(pass).to.not.reportError("start = ('a')*");
+
+ expect(pass).to.reportError("start = (&{ })*");
+
+ expect(pass).to.reportError("start = (!{ })*");
+
+ expect(pass).to.reportError([
+ "start = a*",
+ "a = ''"
+ ].join("\n"));
+ expect(pass).to.not.reportError([
+ "start = a*",
+ "a = 'a'"
+ ].join("\n"));
+
+ expect(pass).to.reportError("start = ''*");
+ expect(pass).to.not.reportError("start = 'a'*");
+
+ expect(pass).to.not.reportError("start = [a-d]*");
+
+ expect(pass).to.not.reportError("start = .*");
+ });
});
diff --git a/test/unit/compiler/passes/report-undefined-rules.spec.js b/test/unit/compiler/passes/report-undefined-rules.spec.js
index 5fc37f5..78385bb 100644
--- a/test/unit/compiler/passes/report-undefined-rules.spec.js
+++ b/test/unit/compiler/passes/report-undefined-rules.spec.js
@@ -9,13 +9,13 @@ chai.use(helpers);
let expect = chai.expect;
describe("compiler pass |reportUndefinedRules|", function() {
- it("reports undefined rules", function() {
- expect(pass).to.reportError("start = undefined", {
- message: "Rule \"undefined\" is not defined.",
- location: {
- start: { offset: 8, line: 1, column: 9 },
- end: { offset: 17, line: 1, column: 18 }
- }
- });
- });
+ it("reports undefined rules", function() {
+ expect(pass).to.reportError("start = undefined", {
+ message: "Rule \"undefined\" is not defined.",
+ location: {
+ start: { offset: 8, line: 1, column: 9 },
+ end: { offset: 17, line: 1, column: 18 }
+ }
+ });
+ });
});
diff --git a/test/unit/parser.spec.js b/test/unit/parser.spec.js
index 16ff566..55fb90e 100644
--- a/test/unit/parser.spec.js
+++ b/test/unit/parser.spec.js
@@ -6,655 +6,655 @@ let parser = require("../../lib/parser");
let expect = chai.expect;
describe("PEG.js grammar parser", function() {
- let literalAbcd = { type: "literal", value: "abcd", ignoreCase: false };
- let literalEfgh = { type: "literal", value: "efgh", ignoreCase: false };
- let literalIjkl = { type: "literal", value: "ijkl", ignoreCase: false };
- let literalMnop = { type: "literal", value: "mnop", ignoreCase: false };
- let semanticAnd = { type: "semantic_and", code: " code " };
- let semanticNot = { type: "semantic_not", code: " code " };
- let optional = { type: "optional", expression: literalAbcd };
- let zeroOrMore = { type: "zero_or_more", expression: literalAbcd };
- let oneOrMore = { type: "one_or_more", expression: literalAbcd };
- let textOptional = { type: "text", expression: optional };
- let simpleNotAbcd = { type: "simple_not", expression: literalAbcd };
- let simpleAndOptional = { type: "simple_and", expression: optional };
- let simpleNotOptional = { type: "simple_not", expression: optional };
- let labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd };
- let labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh };
- let labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl };
- let labeledMnop = { type: "labeled", label: "d", expression: literalMnop };
- let labeledSimpleNot = { type: "labeled", label: "a", expression: simpleNotAbcd };
- let sequence = {
- type: "sequence",
- elements: [literalAbcd, literalEfgh, literalIjkl]
- };
- let sequence2 = {
- type: "sequence",
- elements: [labeledAbcd, labeledEfgh]
- };
- let sequence4 = {
- type: "sequence",
- elements: [labeledAbcd, labeledEfgh, labeledIjkl, labeledMnop]
- };
- let groupLabeled = { type: "group", expression: labeledAbcd };
- let groupSequence = { type: "group", expression: sequence };
- let actionAbcd = { type: "action", expression: literalAbcd, code: " code " };
- let actionEfgh = { type: "action", expression: literalEfgh, code: " code " };
- let actionIjkl = { type: "action", expression: literalIjkl, code: " code " };
- let actionMnop = { type: "action", expression: literalMnop, code: " code " };
- let actionSequence = { type: "action", expression: sequence, code: " code " };
- let choice = {
- type: "choice",
- alternatives: [literalAbcd, literalEfgh, literalIjkl]
- };
- let choice2 = {
- type: "choice",
- alternatives: [actionAbcd, actionEfgh]
- };
- let choice4 = {
- type: "choice",
- alternatives: [actionAbcd, actionEfgh, actionIjkl, actionMnop]
- };
- let named = { type: "named", name: "start rule", expression: literalAbcd };
- let ruleA = { type: "rule", name: "a", expression: literalAbcd };
- let ruleB = { type: "rule", name: "b", expression: literalEfgh };
- let ruleC = { type: "rule", name: "c", expression: literalIjkl };
- let ruleStart = { type: "rule", name: "start", expression: literalAbcd };
- let initializer = { type: "initializer", code: " code " };
-
- function oneRuleGrammar(expression) {
- return {
- type: "grammar",
- initializer: null,
- rules: [{ type: "rule", name: "start", expression: expression }]
- };
- }
-
- function actionGrammar(code) {
- return oneRuleGrammar(
- { type: "action", expression: literalAbcd, code: code }
- );
- }
-
- function literalGrammar(value, ignoreCase) {
- return oneRuleGrammar(
- { type: "literal", value: value, ignoreCase: ignoreCase }
- );
- }
-
- function classGrammar(parts, inverted, ignoreCase) {
- return oneRuleGrammar({
- type: "class",
- parts: parts,
- inverted: inverted,
- ignoreCase: ignoreCase
- });
- }
-
- function anyGrammar() {
- return oneRuleGrammar({ type: "any" });
- }
-
- function ruleRefGrammar(name) {
- return oneRuleGrammar({ type: "rule_ref", name: name });
- }
-
- let trivialGrammar = literalGrammar("abcd", false);
- let twoRuleGrammar = {
- type: "grammar",
- initializer: null,
- rules: [ruleA, ruleB]
- };
-
- let stripLocation = (function() {
- function buildVisitor(functions) {
- return function(node) {
- return functions[node.type].apply(null, arguments);
- };
- }
-
- function stripLeaf(node) {
- delete node.location;
- }
-
- function stripExpression(node) {
- delete node.location;
-
- strip(node.expression);
- }
-
- function stripChildren(property) {
- return function(node) {
- delete node.location;
-
- node[property].forEach(strip);
- };
- }
-
- let strip = buildVisitor({
- grammar(node) {
- delete node.location;
-
- if (node.initializer) {
- strip(node.initializer);
- }
- node.rules.forEach(strip);
- },
-
- initializer: stripLeaf,
- rule: stripExpression,
- named: stripExpression,
- choice: stripChildren("alternatives"),
- action: stripExpression,
- sequence: stripChildren("elements"),
- labeled: stripExpression,
- text: stripExpression,
- simple_and: stripExpression,
- simple_not: stripExpression,
- optional: stripExpression,
- zero_or_more: stripExpression,
- one_or_more: stripExpression,
- group: stripExpression,
- semantic_and: stripLeaf,
- semantic_not: stripLeaf,
- rule_ref: stripLeaf,
- literal: stripLeaf,
- class: stripLeaf,
- any: stripLeaf
- });
-
- return strip;
- })();
-
- function helpers(chai, utils) {
- let Assertion = chai.Assertion;
-
- Assertion.addMethod("parseAs", function(expected) {
- let result = parser.parse(utils.flag(this, "object"));
-
- stripLocation(result);
-
- this.assert(
- utils.eql(result, expected),
- "expected #{this} to parse as #{exp} but got #{act}",
- "expected #{this} to not parse as #{exp}",
- expected,
- result,
- !utils.flag(this, "negate")
- );
- });
-
- Assertion.addMethod("failToParse", function(props) {
- let passed, result;
-
- try {
- result = parser.parse(utils.flag(this, "object"));
- passed = true;
- } catch (e) {
- result = e;
- passed = false;
- }
-
- if (passed) {
- stripLocation(result);
- }
-
- this.assert(
- !passed,
- "expected #{this} to fail to parse but got #{act}",
- "expected #{this} to not fail to parse but it failed with #{act}",
- null,
- result
- );
-
- if (!passed && props !== undefined) {
- Object.keys(props).forEach(key => {
- new Assertion(result).to.have.property(key)
- .that.is.deep.equal(props[key]);
- });
- }
- });
- }
-
- // Helper activation needs to put inside a |beforeEach| block because the
- // helpers conflict with the ones in
- // test/behavior/generated-parser-behavior.spec.js.
- beforeEach(function() {
- chai.use(helpers);
- });
-
- // Canonical Grammar is "a = 'abcd'; b = 'efgh'; c = 'ijkl';".
- it("parses Grammar", function() {
- expect("\na = 'abcd';\n").to.parseAs(
- { type: "grammar", initializer: null, rules: [ruleA] }
- );
- expect("\na = 'abcd';\nb = 'efgh';\nc = 'ijkl';\n").to.parseAs(
- { type: "grammar", initializer: null, rules: [ruleA, ruleB, ruleC] }
- );
- expect("\n{ code };\na = 'abcd';\n").to.parseAs(
- { type: "grammar", initializer: initializer, rules: [ruleA] }
- );
- });
-
- // Canonical Initializer is "{ code }".
- it("parses Initializer", function() {
- expect("{ code };start = 'abcd'").to.parseAs(
- { type: "grammar", initializer: initializer, rules: [ruleStart] }
- );
- });
-
- // Canonical Rule is "a = 'abcd';".
- it("parses Rule", function() {
- expect("start\n=\n'abcd';").to.parseAs(
- oneRuleGrammar(literalAbcd)
- );
- expect("start\n'start rule'\n=\n'abcd';").to.parseAs(
- oneRuleGrammar(named)
- );
- });
-
- // Canonical Expression is "'abcd'".
- it("parses Expression", function() {
- expect("start = 'abcd' / 'efgh' / 'ijkl'").to.parseAs(
- oneRuleGrammar(choice)
- );
- });
-
- // Canonical ChoiceExpression is "'abcd' / 'efgh' / 'ijkl'".
- it("parses ChoiceExpression", function() {
- expect("start = 'abcd' { code }").to.parseAs(
- oneRuleGrammar(actionAbcd)
- );
- expect("start = 'abcd' { code }\n/\n'efgh' { code }").to.parseAs(
- oneRuleGrammar(choice2)
- );
- expect(
- "start = 'abcd' { code }\n/\n'efgh' { code }\n/\n'ijkl' { code }\n/\n'mnop' { code }"
- ).to.parseAs(
- oneRuleGrammar(choice4)
- );
- });
-
- // Canonical ActionExpression is "'abcd' { code }".
- it("parses ActionExpression", function() {
- expect("start = 'abcd' 'efgh' 'ijkl'").to.parseAs(
- oneRuleGrammar(sequence)
- );
- expect("start = 'abcd' 'efgh' 'ijkl'\n{ code }").to.parseAs(
- oneRuleGrammar(actionSequence)
- );
- });
-
- // Canonical SequenceExpression is "'abcd' 'efgh' 'ijkl'".
- it("parses SequenceExpression", function() {
- expect("start = a:'abcd'").to.parseAs(
- oneRuleGrammar(labeledAbcd)
- );
- expect("start = a:'abcd'\nb:'efgh'").to.parseAs(
- oneRuleGrammar(sequence2)
- );
- expect("start = a:'abcd'\nb:'efgh'\nc:'ijkl'\nd:'mnop'").to.parseAs(
- oneRuleGrammar(sequence4)
- );
- });
-
- // Canonical LabeledExpression is "a:'abcd'".
- it("parses LabeledExpression", function() {
- expect("start = a\n:\n!'abcd'").to.parseAs(oneRuleGrammar(labeledSimpleNot));
- expect("start = !'abcd'").to.parseAs(oneRuleGrammar(simpleNotAbcd));
- });
-
- // Canonical PrefixedExpression is "!'abcd'".
- it("parses PrefixedExpression", function() {
- expect("start = !\n'abcd'?").to.parseAs(oneRuleGrammar(simpleNotOptional));
- expect("start = 'abcd'?").to.parseAs(oneRuleGrammar(optional));
- });
-
- // Canonical PrefixedOperator is "!".
- it("parses PrefixedOperator", function() {
- expect("start = $'abcd'?").to.parseAs(oneRuleGrammar(textOptional));
- expect("start = &'abcd'?").to.parseAs(oneRuleGrammar(simpleAndOptional));
- expect("start = !'abcd'?").to.parseAs(oneRuleGrammar(simpleNotOptional));
- });
-
- // Canonical SuffixedExpression is "'abcd'?".
- it("parses SuffixedExpression", function() {
- expect("start = 'abcd'\n?").to.parseAs(oneRuleGrammar(optional));
- expect("start = 'abcd'").to.parseAs(oneRuleGrammar(literalAbcd));
- });
-
- // Canonical SuffixedOperator is "?".
- it("parses SuffixedOperator", function() {
- expect("start = 'abcd'?").to.parseAs(oneRuleGrammar(optional));
- expect("start = 'abcd'*").to.parseAs(oneRuleGrammar(zeroOrMore));
- expect("start = 'abcd'+").to.parseAs(oneRuleGrammar(oneOrMore));
- });
-
- // Canonical PrimaryExpression is "'abcd'".
- it("parses PrimaryExpression", function() {
- expect("start = 'abcd'").to.parseAs(trivialGrammar);
- expect("start = [a-d]").to.parseAs(classGrammar([["a", "d"]], false, false));
- expect("start = .").to.parseAs(anyGrammar());
- expect("start = a").to.parseAs(ruleRefGrammar("a"));
- expect("start = &{ code }").to.parseAs(oneRuleGrammar(semanticAnd));
-
- expect("start = (\na:'abcd'\n)").to.parseAs(oneRuleGrammar(groupLabeled));
- expect("start = (\n'abcd' 'efgh' 'ijkl'\n)").to.parseAs(oneRuleGrammar(groupSequence));
- expect("start = (\n'abcd'\n)").to.parseAs(trivialGrammar);
- });
-
- // Canonical RuleReferenceExpression is "a".
- it("parses RuleReferenceExpression", function() {
- expect("start = a").to.parseAs(ruleRefGrammar("a"));
-
- expect("start = a\n=").to.failToParse();
- expect("start = a\n'abcd'\n=").to.failToParse();
- });
-
- // Canonical SemanticPredicateExpression is "!{ code }".
- it("parses SemanticPredicateExpression", function() {
- expect("start = !\n{ code }").to.parseAs(oneRuleGrammar(semanticNot));
- });
-
- // Canonical SemanticPredicateOperator is "!".
- it("parses SemanticPredicateOperator", function() {
- expect("start = &{ code }").to.parseAs(oneRuleGrammar(semanticAnd));
- expect("start = !{ code }").to.parseAs(oneRuleGrammar(semanticNot));
- });
-
- // The SourceCharacter rule is not tested.
-
- // Canonical WhiteSpace is " ".
- it("parses WhiteSpace", function() {
- expect("start =\t'abcd'").to.parseAs(trivialGrammar);
- expect("start =\v'abcd'").to.parseAs(trivialGrammar);
- expect("start =\f'abcd'").to.parseAs(trivialGrammar);
- expect("start = 'abcd'").to.parseAs(trivialGrammar);
- expect("start =\u00A0'abcd'").to.parseAs(trivialGrammar);
- expect("start =\uFEFF'abcd'").to.parseAs(trivialGrammar);
- expect("start =\u1680'abcd'").to.parseAs(trivialGrammar);
- });
-
- // Canonical LineTerminator is "\n".
- it("parses LineTerminator", function() {
- expect("start = '\n'").to.failToParse();
- expect("start = '\r'").to.failToParse();
- expect("start = '\u2028'").to.failToParse();
- expect("start = '\u2029'").to.failToParse();
- });
-
- // Canonical LineTerminatorSequence is "\r\n".
- it("parses LineTerminatorSequence", function() {
- expect("start =\n'abcd'").to.parseAs(trivialGrammar);
- expect("start =\r\n'abcd'").to.parseAs(trivialGrammar);
- expect("start =\r'abcd'").to.parseAs(trivialGrammar);
- expect("start =\u2028'abcd'").to.parseAs(trivialGrammar);
- expect("start =\u2029'abcd'").to.parseAs(trivialGrammar);
- });
-
- // Canonical Comment is "/* comment */".
- it("parses Comment", function() {
- expect("start =// comment\n'abcd'").to.parseAs(trivialGrammar);
- expect("start =/* comment */'abcd'").to.parseAs(trivialGrammar);
- });
-
- // Canonical MultiLineComment is "/* comment */".
- it("parses MultiLineComment", function() {
- expect("start =/**/'abcd'").to.parseAs(trivialGrammar);
- expect("start =/*a*/'abcd'").to.parseAs(trivialGrammar);
- expect("start =/*abc*/'abcd'").to.parseAs(trivialGrammar);
-
- expect("start =/**/*/'abcd'").to.failToParse();
- });
-
- // Canonical MultiLineCommentNoLineTerminator is "/* comment */".
- it("parses MultiLineCommentNoLineTerminator", function() {
- expect("a = 'abcd'/**/\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd'/*a*/\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd'/*abc*/\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
-
- expect("a = 'abcd'/**/*/\r\nb = 'efgh'").to.failToParse();
- expect("a = 'abcd'/*\n*/\r\nb = 'efgh'").to.failToParse();
- });
-
- // Canonical SingleLineComment is "// comment".
- it("parses SingleLineComment", function() {
- expect("start =//\n'abcd'").to.parseAs(trivialGrammar);
- expect("start =//a\n'abcd'").to.parseAs(trivialGrammar);
- expect("start =//abc\n'abcd'").to.parseAs(trivialGrammar);
-
- expect("start =//\n@\n'abcd'").to.failToParse();
- });
-
- // Canonical Identifier is "a".
- it("parses Identifier", function() {
- expect("start = a:'abcd'").to.parseAs(oneRuleGrammar(labeledAbcd));
- });
-
- // Canonical IdentifierName is "a".
- it("parses IdentifierName", function() {
- expect("start = a").to.parseAs(ruleRefGrammar("a"));
- expect("start = ab").to.parseAs(ruleRefGrammar("ab"));
- expect("start = abcd").to.parseAs(ruleRefGrammar("abcd"));
- });
-
- // Canonical IdentifierStart is "a".
- it("parses IdentifierStart", function() {
- expect("start = a").to.parseAs(ruleRefGrammar("a"));
- expect("start = $").to.parseAs(ruleRefGrammar("$"));
- expect("start = _").to.parseAs(ruleRefGrammar("_"));
- expect("start = \\u0061").to.parseAs(ruleRefGrammar("a"));
- });
-
- // Canonical IdentifierPart is "a".
- it("parses IdentifierPart", function() {
- expect("start = aa").to.parseAs(ruleRefGrammar("aa"));
- expect("start = a\u0300").to.parseAs(ruleRefGrammar("a\u0300"));
- expect("start = a0").to.parseAs(ruleRefGrammar("a0"));
- expect("start = a\u203F").to.parseAs(ruleRefGrammar("a\u203F"));
- expect("start = a\u200C").to.parseAs(ruleRefGrammar("a\u200C"));
- expect("start = a\u200D").to.parseAs(ruleRefGrammar("a\u200D"));
- });
-
- // Unicode rules and reserved word rules are not tested.
-
- // Canonical LiteralMatcher is "'abcd'".
- it("parses LiteralMatcher", function() {
- expect("start = 'abcd'").to.parseAs(literalGrammar("abcd", false));
- expect("start = 'abcd'i").to.parseAs(literalGrammar("abcd", true));
- });
-
- // Canonical StringLiteral is "'abcd'".
- it("parses StringLiteral", function() {
- expect("start = \"\"").to.parseAs(literalGrammar("", false));
- expect("start = \"a\"").to.parseAs(literalGrammar("a", false));
- expect("start = \"abc\"").to.parseAs(literalGrammar("abc", false));
-
- expect("start = ''").to.parseAs(literalGrammar("", false));
- expect("start = 'a'").to.parseAs(literalGrammar("a", false));
- expect("start = 'abc'").to.parseAs(literalGrammar("abc", false));
- });
-
- // Canonical DoubleStringCharacter is "a".
- it("parses DoubleStringCharacter", function() {
- expect("start = \"a\"").to.parseAs(literalGrammar("a", false));
- expect("start = \"\\n\"").to.parseAs(literalGrammar("\n", false));
- expect("start = \"\\\n\"").to.parseAs(literalGrammar("", false));
-
- expect("start = \"\"\"").to.failToParse();
- expect("start = \"\\\"").to.failToParse();
- expect("start = \"\n\"").to.failToParse();
- });
-
- // Canonical SingleStringCharacter is "a".
- it("parses SingleStringCharacter", function() {
- expect("start = 'a'").to.parseAs(literalGrammar("a", false));
- expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
- expect("start = '\\\n'").to.parseAs(literalGrammar("", false));
-
- expect("start = '''").to.failToParse();
- expect("start = '\\'").to.failToParse();
- expect("start = '\n'").to.failToParse();
- });
-
- // Canonical CharacterClassMatcher is "[a-d]".
- it("parses CharacterClassMatcher", function() {
- expect("start = []").to.parseAs(
- classGrammar([], false, false)
- );
- expect("start = [a-d]").to.parseAs(
- classGrammar([["a", "d"]], false, false)
- );
- expect("start = [a]").to.parseAs(
- classGrammar(["a"], false, false)
- );
- expect("start = [a-de-hi-l]").to.parseAs(
- classGrammar(
- [["a", "d"], ["e", "h"], ["i", "l"]],
- false,
- false
- )
- );
- expect("start = [^a-d]").to.parseAs(
- classGrammar([["a", "d"]], true, false)
- );
- expect("start = [a-d]i").to.parseAs(
- classGrammar([["a", "d"]], false, true)
- );
-
- expect("start = [\\\n]").to.parseAs(
- classGrammar([], false, false)
- );
- });
-
- // Canonical ClassCharacterRange is "a-d".
- it("parses ClassCharacterRange", function() {
- expect("start = [a-d]").to.parseAs(classGrammar([["a", "d"]], false, false));
-
- expect("start = [a-a]").to.parseAs(classGrammar([["a", "a"]], false, false));
- expect("start = [b-a]").to.failToParse({
- message: "Invalid character range: b-a."
- });
- });
-
- // Canonical ClassCharacter is "a".
- it("parses ClassCharacter", function() {
- expect("start = [a]").to.parseAs(classGrammar(["a"], false, false));
- expect("start = [\\n]").to.parseAs(classGrammar(["\n"], false, false));
- expect("start = [\\\n]").to.parseAs(classGrammar([], false, false));
-
- expect("start = []]").to.failToParse();
- expect("start = [\\]").to.failToParse();
- expect("start = [\n]").to.failToParse();
- });
-
- // Canonical LineContinuation is "\\\n".
- it("parses LineContinuation", function() {
- expect("start = '\\\r\n'").to.parseAs(literalGrammar("", false));
- });
-
- // Canonical EscapeSequence is "n".
- it("parses EscapeSequence", function() {
- expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
- expect("start = '\\0'").to.parseAs(literalGrammar("\x00", false));
- expect("start = '\\xFF'").to.parseAs(literalGrammar("\xFF", false));
- expect("start = '\\uFFFF'").to.parseAs(literalGrammar("\uFFFF", false));
-
- expect("start = '\\09'").to.failToParse();
- });
-
- // Canonical CharacterEscapeSequence is "n".
- it("parses CharacterEscapeSequence", function() {
- expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
- expect("start = '\\a'").to.parseAs(literalGrammar("a", false));
- });
-
- // Canonical SingleEscapeCharacter is "n".
- it("parses SingleEscapeCharacter", function() {
- expect("start = '\\''").to.parseAs(literalGrammar("'", false));
- expect("start = '\\\"'").to.parseAs(literalGrammar("\"", false));
- expect("start = '\\\\'").to.parseAs(literalGrammar("\\", false));
- expect("start = '\\b'").to.parseAs(literalGrammar("\b", false));
- expect("start = '\\f'").to.parseAs(literalGrammar("\f", false));
- expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
- expect("start = '\\r'").to.parseAs(literalGrammar("\r", false));
- expect("start = '\\t'").to.parseAs(literalGrammar("\t", false));
- expect("start = '\\v'").to.parseAs(literalGrammar("\v", false));
- });
-
- // Canonical NonEscapeCharacter is "a".
- it("parses NonEscapeCharacter", function() {
- expect("start = '\\a'").to.parseAs(literalGrammar("a", false));
-
- // The negative predicate is impossible to test with PEG.js grammar
- // structure.
- });
-
- // The EscapeCharacter rule is impossible to test with PEG.js grammar
- // structure.
-
- // Canonical HexEscapeSequence is "xFF".
- it("parses HexEscapeSequence", function() {
- expect("start = '\\xFF'").to.parseAs(literalGrammar("\xFF", false));
- });
-
- // Canonical UnicodeEscapeSequence is "uFFFF".
- it("parses UnicodeEscapeSequence", function() {
- expect("start = '\\uFFFF'").to.parseAs(literalGrammar("\uFFFF", false));
- });
-
- // Digit rules are not tested.
-
- // Canonical AnyMatcher is ".".
- it("parses AnyMatcher", function() {
- expect("start = .").to.parseAs(anyGrammar());
- });
-
- // Canonical CodeBlock is "{ code }".
- it("parses CodeBlock", function() {
- expect("start = 'abcd' { code }").to.parseAs(actionGrammar(" code "));
- });
-
- // Canonical Code is " code ".
- it("parses Code", function() {
- expect("start = 'abcd' {a}").to.parseAs(actionGrammar("a"));
- expect("start = 'abcd' {abc}").to.parseAs(actionGrammar("abc"));
- expect("start = 'abcd' {{a}}").to.parseAs(actionGrammar("{a}"));
- expect("start = 'abcd' {{a}{b}{c}}").to.parseAs(actionGrammar("{a}{b}{c}"));
-
- expect("start = 'abcd' {{}").to.failToParse();
- expect("start = 'abcd' {}}").to.failToParse();
- });
-
- // Unicode character category rules and token rules are not tested.
-
- // Canonical __ is "\n".
- it("parses __", function() {
- expect("start ='abcd'").to.parseAs(trivialGrammar);
- expect("start = 'abcd'").to.parseAs(trivialGrammar);
- expect("start =\r\n'abcd'").to.parseAs(trivialGrammar);
- expect("start =/* comment */'abcd'").to.parseAs(trivialGrammar);
- expect("start = 'abcd'").to.parseAs(trivialGrammar);
- });
-
- // Canonical _ is " ".
- it("parses _", function() {
- expect("a = 'abcd'\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd' \r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd'/* comment */\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd' \r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- });
-
- // Canonical EOS is ";".
- it("parses EOS", function() {
- expect("a = 'abcd'\n;b = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd' \r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd' // comment\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- expect("a = 'abcd'\nb = 'efgh'").to.parseAs(twoRuleGrammar);
- });
-
- // Canonical EOF is the end of input.
- it("parses EOF", function() {
- expect("start = 'abcd'\n").to.parseAs(trivialGrammar);
- });
+ let literalAbcd = { type: "literal", value: "abcd", ignoreCase: false };
+ let literalEfgh = { type: "literal", value: "efgh", ignoreCase: false };
+ let literalIjkl = { type: "literal", value: "ijkl", ignoreCase: false };
+ let literalMnop = { type: "literal", value: "mnop", ignoreCase: false };
+ let semanticAnd = { type: "semantic_and", code: " code " };
+ let semanticNot = { type: "semantic_not", code: " code " };
+ let optional = { type: "optional", expression: literalAbcd };
+ let zeroOrMore = { type: "zero_or_more", expression: literalAbcd };
+ let oneOrMore = { type: "one_or_more", expression: literalAbcd };
+ let textOptional = { type: "text", expression: optional };
+ let simpleNotAbcd = { type: "simple_not", expression: literalAbcd };
+ let simpleAndOptional = { type: "simple_and", expression: optional };
+ let simpleNotOptional = { type: "simple_not", expression: optional };
+ let labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd };
+ let labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh };
+ let labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl };
+ let labeledMnop = { type: "labeled", label: "d", expression: literalMnop };
+ let labeledSimpleNot = { type: "labeled", label: "a", expression: simpleNotAbcd };
+ let sequence = {
+ type: "sequence",
+ elements: [literalAbcd, literalEfgh, literalIjkl]
+ };
+ let sequence2 = {
+ type: "sequence",
+ elements: [labeledAbcd, labeledEfgh]
+ };
+ let sequence4 = {
+ type: "sequence",
+ elements: [labeledAbcd, labeledEfgh, labeledIjkl, labeledMnop]
+ };
+ let groupLabeled = { type: "group", expression: labeledAbcd };
+ let groupSequence = { type: "group", expression: sequence };
+ let actionAbcd = { type: "action", expression: literalAbcd, code: " code " };
+ let actionEfgh = { type: "action", expression: literalEfgh, code: " code " };
+ let actionIjkl = { type: "action", expression: literalIjkl, code: " code " };
+ let actionMnop = { type: "action", expression: literalMnop, code: " code " };
+ let actionSequence = { type: "action", expression: sequence, code: " code " };
+ let choice = {
+ type: "choice",
+ alternatives: [literalAbcd, literalEfgh, literalIjkl]
+ };
+ let choice2 = {
+ type: "choice",
+ alternatives: [actionAbcd, actionEfgh]
+ };
+ let choice4 = {
+ type: "choice",
+ alternatives: [actionAbcd, actionEfgh, actionIjkl, actionMnop]
+ };
+ let named = { type: "named", name: "start rule", expression: literalAbcd };
+ let ruleA = { type: "rule", name: "a", expression: literalAbcd };
+ let ruleB = { type: "rule", name: "b", expression: literalEfgh };
+ let ruleC = { type: "rule", name: "c", expression: literalIjkl };
+ let ruleStart = { type: "rule", name: "start", expression: literalAbcd };
+ let initializer = { type: "initializer", code: " code " };
+
+ function oneRuleGrammar(expression) {
+ return {
+ type: "grammar",
+ initializer: null,
+ rules: [{ type: "rule", name: "start", expression: expression }]
+ };
+ }
+
+ function actionGrammar(code) {
+ return oneRuleGrammar(
+ { type: "action", expression: literalAbcd, code: code }
+ );
+ }
+
+ function literalGrammar(value, ignoreCase) {
+ return oneRuleGrammar(
+ { type: "literal", value: value, ignoreCase: ignoreCase }
+ );
+ }
+
+ function classGrammar(parts, inverted, ignoreCase) {
+ return oneRuleGrammar({
+ type: "class",
+ parts: parts,
+ inverted: inverted,
+ ignoreCase: ignoreCase
+ });
+ }
+
+ function anyGrammar() {
+ return oneRuleGrammar({ type: "any" });
+ }
+
+ function ruleRefGrammar(name) {
+ return oneRuleGrammar({ type: "rule_ref", name: name });
+ }
+
+ let trivialGrammar = literalGrammar("abcd", false);
+ let twoRuleGrammar = {
+ type: "grammar",
+ initializer: null,
+ rules: [ruleA, ruleB]
+ };
+
+ let stripLocation = (function() {
+ function buildVisitor(functions) {
+ return function(node) {
+ return functions[node.type].apply(null, arguments);
+ };
+ }
+
+ function stripLeaf(node) {
+ delete node.location;
+ }
+
+ function stripExpression(node) {
+ delete node.location;
+
+ strip(node.expression);
+ }
+
+ function stripChildren(property) {
+ return function(node) {
+ delete node.location;
+
+ node[property].forEach(strip);
+ };
+ }
+
+ let strip = buildVisitor({
+ grammar(node) {
+ delete node.location;
+
+ if (node.initializer) {
+ strip(node.initializer);
+ }
+ node.rules.forEach(strip);
+ },
+
+ initializer: stripLeaf,
+ rule: stripExpression,
+ named: stripExpression,
+ choice: stripChildren("alternatives"),
+ action: stripExpression,
+ sequence: stripChildren("elements"),
+ labeled: stripExpression,
+ text: stripExpression,
+ simple_and: stripExpression,
+ simple_not: stripExpression,
+ optional: stripExpression,
+ zero_or_more: stripExpression,
+ one_or_more: stripExpression,
+ group: stripExpression,
+ semantic_and: stripLeaf,
+ semantic_not: stripLeaf,
+ rule_ref: stripLeaf,
+ literal: stripLeaf,
+ class: stripLeaf,
+ any: stripLeaf
+ });
+
+ return strip;
+ })();
+
+ function helpers(chai, utils) {
+ let Assertion = chai.Assertion;
+
+ Assertion.addMethod("parseAs", function(expected) {
+ let result = parser.parse(utils.flag(this, "object"));
+
+ stripLocation(result);
+
+ this.assert(
+ utils.eql(result, expected),
+ "expected #{this} to parse as #{exp} but got #{act}",
+ "expected #{this} to not parse as #{exp}",
+ expected,
+ result,
+ !utils.flag(this, "negate")
+ );
+ });
+
+ Assertion.addMethod("failToParse", function(props) {
+ let passed, result;
+
+ try {
+ result = parser.parse(utils.flag(this, "object"));
+ passed = true;
+ } catch (e) {
+ result = e;
+ passed = false;
+ }
+
+ if (passed) {
+ stripLocation(result);
+ }
+
+ this.assert(
+ !passed,
+ "expected #{this} to fail to parse but got #{act}",
+ "expected #{this} to not fail to parse but it failed with #{act}",
+ null,
+ result
+ );
+
+ if (!passed && props !== undefined) {
+ Object.keys(props).forEach(key => {
+ new Assertion(result).to.have.property(key)
+ .that.is.deep.equal(props[key]);
+ });
+ }
+ });
+ }
+
+ // Helper activation needs to put inside a |beforeEach| block because the
+ // helpers conflict with the ones in
+ // test/behavior/generated-parser-behavior.spec.js.
+ beforeEach(function() {
+ chai.use(helpers);
+ });
+
+ // Canonical Grammar is "a = 'abcd'; b = 'efgh'; c = 'ijkl';".
+ it("parses Grammar", function() {
+ expect("\na = 'abcd';\n").to.parseAs(
+ { type: "grammar", initializer: null, rules: [ruleA] }
+ );
+ expect("\na = 'abcd';\nb = 'efgh';\nc = 'ijkl';\n").to.parseAs(
+ { type: "grammar", initializer: null, rules: [ruleA, ruleB, ruleC] }
+ );
+ expect("\n{ code };\na = 'abcd';\n").to.parseAs(
+ { type: "grammar", initializer: initializer, rules: [ruleA] }
+ );
+ });
+
+ // Canonical Initializer is "{ code }".
+ it("parses Initializer", function() {
+ expect("{ code };start = 'abcd'").to.parseAs(
+ { type: "grammar", initializer: initializer, rules: [ruleStart] }
+ );
+ });
+
+ // Canonical Rule is "a = 'abcd';".
+ it("parses Rule", function() {
+ expect("start\n=\n'abcd';").to.parseAs(
+ oneRuleGrammar(literalAbcd)
+ );
+ expect("start\n'start rule'\n=\n'abcd';").to.parseAs(
+ oneRuleGrammar(named)
+ );
+ });
+
+ // Canonical Expression is "'abcd'".
+ it("parses Expression", function() {
+ expect("start = 'abcd' / 'efgh' / 'ijkl'").to.parseAs(
+ oneRuleGrammar(choice)
+ );
+ });
+
+ // Canonical ChoiceExpression is "'abcd' / 'efgh' / 'ijkl'".
+ it("parses ChoiceExpression", function() {
+ expect("start = 'abcd' { code }").to.parseAs(
+ oneRuleGrammar(actionAbcd)
+ );
+ expect("start = 'abcd' { code }\n/\n'efgh' { code }").to.parseAs(
+ oneRuleGrammar(choice2)
+ );
+ expect(
+ "start = 'abcd' { code }\n/\n'efgh' { code }\n/\n'ijkl' { code }\n/\n'mnop' { code }"
+ ).to.parseAs(
+ oneRuleGrammar(choice4)
+ );
+ });
+
+ // Canonical ActionExpression is "'abcd' { code }".
+ it("parses ActionExpression", function() {
+ expect("start = 'abcd' 'efgh' 'ijkl'").to.parseAs(
+ oneRuleGrammar(sequence)
+ );
+ expect("start = 'abcd' 'efgh' 'ijkl'\n{ code }").to.parseAs(
+ oneRuleGrammar(actionSequence)
+ );
+ });
+
+ // Canonical SequenceExpression is "'abcd' 'efgh' 'ijkl'".
+ it("parses SequenceExpression", function() {
+ expect("start = a:'abcd'").to.parseAs(
+ oneRuleGrammar(labeledAbcd)
+ );
+ expect("start = a:'abcd'\nb:'efgh'").to.parseAs(
+ oneRuleGrammar(sequence2)
+ );
+ expect("start = a:'abcd'\nb:'efgh'\nc:'ijkl'\nd:'mnop'").to.parseAs(
+ oneRuleGrammar(sequence4)
+ );
+ });
+
+ // Canonical LabeledExpression is "a:'abcd'".
+ it("parses LabeledExpression", function() {
+ expect("start = a\n:\n!'abcd'").to.parseAs(oneRuleGrammar(labeledSimpleNot));
+ expect("start = !'abcd'").to.parseAs(oneRuleGrammar(simpleNotAbcd));
+ });
+
+ // Canonical PrefixedExpression is "!'abcd'".
+ it("parses PrefixedExpression", function() {
+ expect("start = !\n'abcd'?").to.parseAs(oneRuleGrammar(simpleNotOptional));
+ expect("start = 'abcd'?").to.parseAs(oneRuleGrammar(optional));
+ });
+
+ // Canonical PrefixedOperator is "!".
+ it("parses PrefixedOperator", function() {
+ expect("start = $'abcd'?").to.parseAs(oneRuleGrammar(textOptional));
+ expect("start = &'abcd'?").to.parseAs(oneRuleGrammar(simpleAndOptional));
+ expect("start = !'abcd'?").to.parseAs(oneRuleGrammar(simpleNotOptional));
+ });
+
+ // Canonical SuffixedExpression is "'abcd'?".
+ it("parses SuffixedExpression", function() {
+ expect("start = 'abcd'\n?").to.parseAs(oneRuleGrammar(optional));
+ expect("start = 'abcd'").to.parseAs(oneRuleGrammar(literalAbcd));
+ });
+
+ // Canonical SuffixedOperator is "?".
+ it("parses SuffixedOperator", function() {
+ expect("start = 'abcd'?").to.parseAs(oneRuleGrammar(optional));
+ expect("start = 'abcd'*").to.parseAs(oneRuleGrammar(zeroOrMore));
+ expect("start = 'abcd'+").to.parseAs(oneRuleGrammar(oneOrMore));
+ });
+
+ // Canonical PrimaryExpression is "'abcd'".
+ it("parses PrimaryExpression", function() {
+ expect("start = 'abcd'").to.parseAs(trivialGrammar);
+ expect("start = [a-d]").to.parseAs(classGrammar([["a", "d"]], false, false));
+ expect("start = .").to.parseAs(anyGrammar());
+ expect("start = a").to.parseAs(ruleRefGrammar("a"));
+ expect("start = &{ code }").to.parseAs(oneRuleGrammar(semanticAnd));
+
+ expect("start = (\na:'abcd'\n)").to.parseAs(oneRuleGrammar(groupLabeled));
+ expect("start = (\n'abcd' 'efgh' 'ijkl'\n)").to.parseAs(oneRuleGrammar(groupSequence));
+ expect("start = (\n'abcd'\n)").to.parseAs(trivialGrammar);
+ });
+
+ // Canonical RuleReferenceExpression is "a".
+ it("parses RuleReferenceExpression", function() {
+ expect("start = a").to.parseAs(ruleRefGrammar("a"));
+
+ expect("start = a\n=").to.failToParse();
+ expect("start = a\n'abcd'\n=").to.failToParse();
+ });
+
+ // Canonical SemanticPredicateExpression is "!{ code }".
+ it("parses SemanticPredicateExpression", function() {
+ expect("start = !\n{ code }").to.parseAs(oneRuleGrammar(semanticNot));
+ });
+
+ // Canonical SemanticPredicateOperator is "!".
+ it("parses SemanticPredicateOperator", function() {
+ expect("start = &{ code }").to.parseAs(oneRuleGrammar(semanticAnd));
+ expect("start = !{ code }").to.parseAs(oneRuleGrammar(semanticNot));
+ });
+
+ // The SourceCharacter rule is not tested.
+
+ // Canonical WhiteSpace is " ".
+ it("parses WhiteSpace", function() {
+ expect("start =\t'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\v'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\f'abcd'").to.parseAs(trivialGrammar);
+ expect("start = 'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\u00A0'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\uFEFF'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\u1680'abcd'").to.parseAs(trivialGrammar);
+ });
+
+ // Canonical LineTerminator is "\n".
+ it("parses LineTerminator", function() {
+ expect("start = '\n'").to.failToParse();
+ expect("start = '\r'").to.failToParse();
+ expect("start = '\u2028'").to.failToParse();
+ expect("start = '\u2029'").to.failToParse();
+ });
+
+ // Canonical LineTerminatorSequence is "\r\n".
+ it("parses LineTerminatorSequence", function() {
+ expect("start =\n'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\r\n'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\r'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\u2028'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\u2029'abcd'").to.parseAs(trivialGrammar);
+ });
+
+ // Canonical Comment is "/* comment */".
+ it("parses Comment", function() {
+ expect("start =// comment\n'abcd'").to.parseAs(trivialGrammar);
+ expect("start =/* comment */'abcd'").to.parseAs(trivialGrammar);
+ });
+
+ // Canonical MultiLineComment is "/* comment */".
+ it("parses MultiLineComment", function() {
+ expect("start =/**/'abcd'").to.parseAs(trivialGrammar);
+ expect("start =/*a*/'abcd'").to.parseAs(trivialGrammar);
+ expect("start =/*abc*/'abcd'").to.parseAs(trivialGrammar);
+
+ expect("start =/**/*/'abcd'").to.failToParse();
+ });
+
+ // Canonical MultiLineCommentNoLineTerminator is "/* comment */".
+ it("parses MultiLineCommentNoLineTerminator", function() {
+ expect("a = 'abcd'/**/\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd'/*a*/\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd'/*abc*/\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+
+ expect("a = 'abcd'/**/*/\r\nb = 'efgh'").to.failToParse();
+ expect("a = 'abcd'/*\n*/\r\nb = 'efgh'").to.failToParse();
+ });
+
+ // Canonical SingleLineComment is "// comment".
+ it("parses SingleLineComment", function() {
+ expect("start =//\n'abcd'").to.parseAs(trivialGrammar);
+ expect("start =//a\n'abcd'").to.parseAs(trivialGrammar);
+ expect("start =//abc\n'abcd'").to.parseAs(trivialGrammar);
+
+ expect("start =//\n@\n'abcd'").to.failToParse();
+ });
+
+ // Canonical Identifier is "a".
+ it("parses Identifier", function() {
+ expect("start = a:'abcd'").to.parseAs(oneRuleGrammar(labeledAbcd));
+ });
+
+ // Canonical IdentifierName is "a".
+ it("parses IdentifierName", function() {
+ expect("start = a").to.parseAs(ruleRefGrammar("a"));
+ expect("start = ab").to.parseAs(ruleRefGrammar("ab"));
+ expect("start = abcd").to.parseAs(ruleRefGrammar("abcd"));
+ });
+
+ // Canonical IdentifierStart is "a".
+ it("parses IdentifierStart", function() {
+ expect("start = a").to.parseAs(ruleRefGrammar("a"));
+ expect("start = $").to.parseAs(ruleRefGrammar("$"));
+ expect("start = _").to.parseAs(ruleRefGrammar("_"));
+ expect("start = \\u0061").to.parseAs(ruleRefGrammar("a"));
+ });
+
+ // Canonical IdentifierPart is "a".
+ it("parses IdentifierPart", function() {
+ expect("start = aa").to.parseAs(ruleRefGrammar("aa"));
+ expect("start = a\u0300").to.parseAs(ruleRefGrammar("a\u0300"));
+ expect("start = a0").to.parseAs(ruleRefGrammar("a0"));
+ expect("start = a\u203F").to.parseAs(ruleRefGrammar("a\u203F"));
+ expect("start = a\u200C").to.parseAs(ruleRefGrammar("a\u200C"));
+ expect("start = a\u200D").to.parseAs(ruleRefGrammar("a\u200D"));
+ });
+
+ // Unicode rules and reserved word rules are not tested.
+
+ // Canonical LiteralMatcher is "'abcd'".
+ it("parses LiteralMatcher", function() {
+ expect("start = 'abcd'").to.parseAs(literalGrammar("abcd", false));
+ expect("start = 'abcd'i").to.parseAs(literalGrammar("abcd", true));
+ });
+
+ // Canonical StringLiteral is "'abcd'".
+ it("parses StringLiteral", function() {
+ expect("start = \"\"").to.parseAs(literalGrammar("", false));
+ expect("start = \"a\"").to.parseAs(literalGrammar("a", false));
+ expect("start = \"abc\"").to.parseAs(literalGrammar("abc", false));
+
+ expect("start = ''").to.parseAs(literalGrammar("", false));
+ expect("start = 'a'").to.parseAs(literalGrammar("a", false));
+ expect("start = 'abc'").to.parseAs(literalGrammar("abc", false));
+ });
+
+ // Canonical DoubleStringCharacter is "a".
+ it("parses DoubleStringCharacter", function() {
+ expect("start = \"a\"").to.parseAs(literalGrammar("a", false));
+ expect("start = \"\\n\"").to.parseAs(literalGrammar("\n", false));
+ expect("start = \"\\\n\"").to.parseAs(literalGrammar("", false));
+
+ expect("start = \"\"\"").to.failToParse();
+ expect("start = \"\\\"").to.failToParse();
+ expect("start = \"\n\"").to.failToParse();
+ });
+
+ // Canonical SingleStringCharacter is "a".
+ it("parses SingleStringCharacter", function() {
+ expect("start = 'a'").to.parseAs(literalGrammar("a", false));
+ expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
+ expect("start = '\\\n'").to.parseAs(literalGrammar("", false));
+
+ expect("start = '''").to.failToParse();
+ expect("start = '\\'").to.failToParse();
+ expect("start = '\n'").to.failToParse();
+ });
+
+ // Canonical CharacterClassMatcher is "[a-d]".
+ it("parses CharacterClassMatcher", function() {
+ expect("start = []").to.parseAs(
+ classGrammar([], false, false)
+ );
+ expect("start = [a-d]").to.parseAs(
+ classGrammar([["a", "d"]], false, false)
+ );
+ expect("start = [a]").to.parseAs(
+ classGrammar(["a"], false, false)
+ );
+ expect("start = [a-de-hi-l]").to.parseAs(
+ classGrammar(
+ [["a", "d"], ["e", "h"], ["i", "l"]],
+ false,
+ false
+ )
+ );
+ expect("start = [^a-d]").to.parseAs(
+ classGrammar([["a", "d"]], true, false)
+ );
+ expect("start = [a-d]i").to.parseAs(
+ classGrammar([["a", "d"]], false, true)
+ );
+
+ expect("start = [\\\n]").to.parseAs(
+ classGrammar([], false, false)
+ );
+ });
+
+ // Canonical ClassCharacterRange is "a-d".
+ it("parses ClassCharacterRange", function() {
+ expect("start = [a-d]").to.parseAs(classGrammar([["a", "d"]], false, false));
+
+ expect("start = [a-a]").to.parseAs(classGrammar([["a", "a"]], false, false));
+ expect("start = [b-a]").to.failToParse({
+ message: "Invalid character range: b-a."
+ });
+ });
+
+ // Canonical ClassCharacter is "a".
+ it("parses ClassCharacter", function() {
+ expect("start = [a]").to.parseAs(classGrammar(["a"], false, false));
+ expect("start = [\\n]").to.parseAs(classGrammar(["\n"], false, false));
+ expect("start = [\\\n]").to.parseAs(classGrammar([], false, false));
+
+ expect("start = []]").to.failToParse();
+ expect("start = [\\]").to.failToParse();
+ expect("start = [\n]").to.failToParse();
+ });
+
+ // Canonical LineContinuation is "\\\n".
+ it("parses LineContinuation", function() {
+ expect("start = '\\\r\n'").to.parseAs(literalGrammar("", false));
+ });
+
+ // Canonical EscapeSequence is "n".
+ it("parses EscapeSequence", function() {
+ expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
+ expect("start = '\\0'").to.parseAs(literalGrammar("\x00", false));
+ expect("start = '\\xFF'").to.parseAs(literalGrammar("\xFF", false));
+ expect("start = '\\uFFFF'").to.parseAs(literalGrammar("\uFFFF", false));
+
+ expect("start = '\\09'").to.failToParse();
+ });
+
+ // Canonical CharacterEscapeSequence is "n".
+ it("parses CharacterEscapeSequence", function() {
+ expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
+ expect("start = '\\a'").to.parseAs(literalGrammar("a", false));
+ });
+
+ // Canonical SingleEscapeCharacter is "n".
+ it("parses SingleEscapeCharacter", function() {
+ expect("start = '\\''").to.parseAs(literalGrammar("'", false));
+ expect("start = '\\\"'").to.parseAs(literalGrammar("\"", false));
+ expect("start = '\\\\'").to.parseAs(literalGrammar("\\", false));
+ expect("start = '\\b'").to.parseAs(literalGrammar("\b", false));
+ expect("start = '\\f'").to.parseAs(literalGrammar("\f", false));
+ expect("start = '\\n'").to.parseAs(literalGrammar("\n", false));
+ expect("start = '\\r'").to.parseAs(literalGrammar("\r", false));
+ expect("start = '\\t'").to.parseAs(literalGrammar("\t", false));
+ expect("start = '\\v'").to.parseAs(literalGrammar("\v", false));
+ });
+
+ // Canonical NonEscapeCharacter is "a".
+ it("parses NonEscapeCharacter", function() {
+ expect("start = '\\a'").to.parseAs(literalGrammar("a", false));
+
+ // The negative predicate is impossible to test with PEG.js grammar
+ // structure.
+ });
+
+ // The EscapeCharacter rule is impossible to test with PEG.js grammar
+ // structure.
+
+ // Canonical HexEscapeSequence is "xFF".
+ it("parses HexEscapeSequence", function() {
+ expect("start = '\\xFF'").to.parseAs(literalGrammar("\xFF", false));
+ });
+
+ // Canonical UnicodeEscapeSequence is "uFFFF".
+ it("parses UnicodeEscapeSequence", function() {
+ expect("start = '\\uFFFF'").to.parseAs(literalGrammar("\uFFFF", false));
+ });
+
+ // Digit rules are not tested.
+
+ // Canonical AnyMatcher is ".".
+ it("parses AnyMatcher", function() {
+ expect("start = .").to.parseAs(anyGrammar());
+ });
+
+ // Canonical CodeBlock is "{ code }".
+ it("parses CodeBlock", function() {
+ expect("start = 'abcd' { code }").to.parseAs(actionGrammar(" code "));
+ });
+
+ // Canonical Code is " code ".
+ it("parses Code", function() {
+ expect("start = 'abcd' {a}").to.parseAs(actionGrammar("a"));
+ expect("start = 'abcd' {abc}").to.parseAs(actionGrammar("abc"));
+ expect("start = 'abcd' {{a}}").to.parseAs(actionGrammar("{a}"));
+ expect("start = 'abcd' {{a}{b}{c}}").to.parseAs(actionGrammar("{a}{b}{c}"));
+
+ expect("start = 'abcd' {{}").to.failToParse();
+ expect("start = 'abcd' {}}").to.failToParse();
+ });
+
+ // Unicode character category rules and token rules are not tested.
+
+ // Canonical __ is "\n".
+ it("parses __", function() {
+ expect("start ='abcd'").to.parseAs(trivialGrammar);
+ expect("start = 'abcd'").to.parseAs(trivialGrammar);
+ expect("start =\r\n'abcd'").to.parseAs(trivialGrammar);
+ expect("start =/* comment */'abcd'").to.parseAs(trivialGrammar);
+ expect("start = 'abcd'").to.parseAs(trivialGrammar);
+ });
+
+ // Canonical _ is " ".
+ it("parses _", function() {
+ expect("a = 'abcd'\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd' \r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd'/* comment */\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd' \r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ });
+
+ // Canonical EOS is ";".
+ it("parses EOS", function() {
+ expect("a = 'abcd'\n;b = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd' \r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd' // comment\r\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ expect("a = 'abcd'\nb = 'efgh'").to.parseAs(twoRuleGrammar);
+ });
+
+ // Canonical EOF is the end of input.
+ it("parses EOF", function() {
+ expect("start = 'abcd'\n").to.parseAs(trivialGrammar);
+ });
});