diff --git a/.eslintrc.js b/.eslintrc.js
index 9cab2f8..7de629b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,6 +3,11 @@
module.exports = {
"extends": "futagozaryuu/node-v4",
- "root": true
+ "root": true,
+ "rules": {
+
+ "prefer-rest-params": 0,
+
+ },
};
diff --git a/bin/options.js b/bin/options.js
index 5fe7460..506c8a5 100644
--- a/bin/options.js
+++ b/bin/options.js
@@ -1,255 +1,304 @@
"use strict";
-let fs = require("fs");
-let path = require("path");
-let peg = require("../");
+const fs = require( "fs" );
+const path = require( "path" );
+const peg = require( "../" );
// Options
let inputFile = null;
let outputFile = null;
-let options = {
- "--": [],
- "cache": false,
- "dependencies": {},
- "exportVar": null,
- "format": "commonjs",
- "optimize": "speed",
- "output": "source",
- "plugins": [],
- "trace": false
+const options = {
+ "--": [],
+ "cache": false,
+ "dependencies": {},
+ "exportVar": null,
+ "format": "commonjs",
+ "optimize": "speed",
+ "output": "source",
+ "plugins": [],
+ "trace": false
};
-const EXPORT_VAR_FORMATS = ["globals", "umd"];
-const DEPENDENCY_FORMATS = ["amd", "commonjs", "es", "umd"];
-const MODULE_FORMATS = ["amd", "bare", "commonjs", "es", "globals", "umd"];
-const OPTIMIZATION_GOALS = ["size", "speed"];
+const EXPORT_VAR_FORMATS = [ "globals", "umd" ];
+const DEPENDENCY_FORMATS = [ "amd", "commonjs", "es", "umd" ];
+const MODULE_FORMATS = [ "amd", "bare", "commonjs", "es", "globals", "umd" ];
+const OPTIMIZATION_GOALS = [ "size", "speed" ];
// Helpers
-function abort(message) {
- console.error(message);
- process.exit(1);
+function abort( message ) {
+
+ console.error( message );
+ process.exit( 1 );
+
}
-function addExtraOptions(json) {
- let extraOptions;
-
- try {
- extraOptions = JSON.parse(json);
- } catch (e) {
- if (!(e instanceof SyntaxError)) { throw e; }
-
- abort("Error parsing JSON: " + e.message);
- }
- if (typeof extraOptions !== "object") {
- abort("The JSON with extra options has to represent an object.");
- }
-
- Object
- .keys(extraOptions)
- .forEach(key => {
- options[key] = extraOptions[key];
- });
+function addExtraOptions( json ) {
+
+ let extraOptions;
+
+ try {
+
+ extraOptions = JSON.parse( json );
+
+ } catch ( e ) {
+
+ if ( ! ( e instanceof SyntaxError ) ) throw e;
+ abort( "Error parsing JSON: " + e.message );
+
+ }
+ if ( typeof extraOptions !== "object" ) {
+
+ abort( "The JSON with extra options has to represent an object." );
+
+ }
+
+ Object
+ .keys( extraOptions )
+ .forEach( key => {
+
+ options[ key ] = extraOptions[ key ];
+
+ } );
+
}
-function formatChoicesList(list) {
- list = list.map(entry => `"${entry}"`);
- let lastOption = list.pop();
+function formatChoicesList( list ) {
+
+ list = list.map( entry => `"${ entry }"` );
+ const lastOption = list.pop();
+
+ return list.length === 0
+ ? lastOption
+ : list.join( ", " ) + " or " + lastOption;
- return list.length === 0
- ? lastOption
- : list.join(", ") + " or " + lastOption;
}
-function updateList(list, string) {
- string
- .split(",")
- .forEach(entry => {
- entry = entry.trim();
- if (list.indexOf(entry) === -1) {
- list.push(entry);
- }
- });
+function updateList( list, string ) {
+
+ string
+ .split( "," )
+ .forEach( entry => {
+
+ entry = entry.trim();
+ if ( list.indexOf( entry ) === -1 ) {
+
+ list.push( entry );
+
+ }
+
+ } );
+
}
// Arguments
-let args = process.argv.slice(2);
+let args = process.argv.slice( 2 );
+
+function nextArg( option ) {
+
+ if ( args.length === 0 ) {
-function nextArg(option) {
- if (args.length === 0) {
- abort(`Missing parameter of the ${option} option.`);
- }
+ abort( `Missing parameter of the ${ option } option.` );
+
+ }
+ return args.shift();
- return args.shift();
}
// Parse Arguments
-while (args.length > 0) {
- let json, mod;
- let argument = args.shift();
-
- if (argument.indexOf("-") === 0 && argument.indexOf("=") > 1) {
- argument = argument.split("=");
- args.unshift(argument.length > 2 ? argument.slice(1) : argument[1]);
- argument = argument[0];
- }
-
- switch (argument) {
-
- case "--":
- options["--"] = args;
- args = [];
- break;
-
- case "-a":
- case "--allowed-start-rules":
- if (!options.allowedStartRules) {
- options.allowedStartRules = [];
- }
- updateList(options.allowedStartRules, nextArg("--allowed-start-rules"));
- break;
-
- case "--cache":
- options.cache = true;
- break;
-
- case "--no-cache":
- options.cache = false;
- break;
-
- case "-d":
- case "--dependency":
- argument = nextArg("-d/--dependency");
- if (argument.indexOf(":") === -1) {
- mod = [argument, argument];
- } else {
- mod = argument.split(":");
- if (mod.length > 2) {
- mod[1] = mod.slice(1);
- }
- }
- options.dependencies[mod[0]] = mod[1];
- break;
-
- case "-e":
- case "--export-var":
- options.exportVar = nextArg("-e/--export-var");
- break;
-
- case "--extra-options":
- addExtraOptions(nextArg("--extra-options"));
- break;
-
- case "-c":
- case "--config":
- case "--extra-options-file":
- argument = nextArg("-c/--config/--extra-options-file");
- try {
- json = fs.readFileSync(argument, "utf8");
- } catch (e) {
- abort(`Can't read from file "${argument}".`);
- }
- addExtraOptions(json);
- break;
-
- case "-f":
- case "--format":
- argument = nextArg("-f/--format");
- if (MODULE_FORMATS.indexOf(argument) === -1) {
- abort(`Module format must be either ${formatChoicesList(MODULE_FORMATS)}.`);
- }
- options.format = argument;
- break;
-
- case "-h":
- case "--help":
- console.log(fs.readFileSync(path.join(__dirname, "usage.txt"), "utf8").trim());
- process.exit();
- break;
-
- case "-O":
- case "--optimize":
- argument = nextArg("-O/--optimize");
- if (OPTIMIZATION_GOALS.indexOf(argument) === -1) {
- abort(`Optimization goal must be either ${formatChoicesList(OPTIMIZATION_GOALS)}.`);
- }
- options.optimize = argument;
- break;
-
- case "-o":
- case "--output":
- outputFile = nextArg("-o/--output");
- break;
-
- case "-p":
- case "--plugin":
- argument = nextArg("-p/--plugin");
- try {
- mod = require(argument);
- } catch (ex1) {
- if (ex1.code !== "MODULE_NOT_FOUND") { throw ex1; }
-
- try {
- mod = require(path.resolve(argument));
- } catch (ex2) {
- if (ex2.code !== "MODULE_NOT_FOUND") { throw ex2; }
-
- abort(`Can't load module "${argument}".`);
- }
- }
- options.plugins.push(mod);
- break;
-
- case "--trace":
- options.trace = true;
- break;
-
- case "--no-trace":
- options.trace = false;
- break;
-
- case "-v":
- case "--version":
- console.log("PEG.js v" + peg.VERSION);
- process.exit();
- break;
-
- default:
- if (inputFile !== null) {
- abort(`Unknown option: "${argument}".`);
- }
- inputFile = argument;
- }
+while ( args.length > 0 ) {
+
+ let json, mod;
+ let argument = args.shift();
+
+ if ( argument.indexOf( "-" ) === 0 && argument.indexOf( "=" ) > 1 ) {
+
+ argument = argument.split( "=" );
+ args.unshift( argument.length > 2 ? argument.slice( 1 ) : argument[ 1 ] );
+ argument = argument[ 0 ];
+
+ }
+
+ switch ( argument ) {
+
+ case "--":
+ options[ "--" ] = args;
+ args = [];
+ break;
+
+ case "-a":
+ case "--allowed-start-rules":
+ if ( ! options.allowedStartRules ) options.allowedStartRules = [];
+ updateList( options.allowedStartRules, nextArg( "--allowed-start-rules" ) );
+ break;
+
+ case "--cache":
+ options.cache = true;
+ break;
+
+ case "--no-cache":
+ options.cache = false;
+ break;
+
+ case "-d":
+ case "--dependency":
+ argument = nextArg( "-d/--dependency" );
+ mod = argument.split( ":" );
+
+ if ( mod.length === 1 ) mod = [ argument, argument ];
+ else if ( mod.length > 2 ) mod[ 1 ] = mod.slice( 1 );
+
+ options.dependencies[ mod[ 0 ] ] = mod[ 1 ];
+ break;
+
+ case "-e":
+ case "--export-var":
+ options.exportVar = nextArg( "-e/--export-var" );
+ break;
+
+ case "--extra-options":
+ addExtraOptions( nextArg( "--extra-options" ) );
+ break;
+
+ case "-c":
+ case "--config":
+ case "--extra-options-file":
+ argument = nextArg( "-c/--config/--extra-options-file" );
+ try {
+
+ json = fs.readFileSync( argument, "utf8" );
+
+ } catch ( e ) {
+
+ abort( `Can't read from file "${ argument }".` );
+
+ }
+ addExtraOptions( json );
+ break;
+
+ case "-f":
+ case "--format":
+ argument = nextArg( "-f/--format" );
+ if ( MODULE_FORMATS.indexOf( argument ) === -1 ) {
+
+ abort( `Module format must be either ${ formatChoicesList( MODULE_FORMATS ) }.` );
+
+ }
+ options.format = argument;
+ break;
+
+ case "-h":
+ case "--help":
+ console.log( fs.readFileSync( path.join( __dirname, "usage.txt" ), "utf8" ).trim() );
+ process.exit();
+ break;
+
+ case "-O":
+ case "--optimize":
+ argument = nextArg( "-O/--optimize" );
+ if ( OPTIMIZATION_GOALS.indexOf( argument ) === -1 ) {
+
+ abort( `Optimization goal must be either ${ formatChoicesList( OPTIMIZATION_GOALS ) }.` );
+
+ }
+ options.optimize = argument;
+ break;
+
+ case "-o":
+ case "--output":
+ outputFile = nextArg( "-o/--output" );
+ break;
+
+ case "-p":
+ case "--plugin":
+ argument = nextArg( "-p/--plugin" );
+ try {
+
+ mod = require( argument );
+
+ } catch ( ex1 ) {
+
+ if ( ex1.code !== "MODULE_NOT_FOUND" ) throw ex1;
+ try {
+
+ mod = require( path.resolve( argument ) );
+
+ } catch ( ex2 ) {
+
+ if ( ex2.code !== "MODULE_NOT_FOUND" ) throw ex2;
+ abort( `Can't load module "${ argument }".` );
+
+ }
+
+ }
+ options.plugins.push( mod );
+ break;
+
+ case "--trace":
+ options.trace = true;
+ break;
+
+ case "--no-trace":
+ options.trace = false;
+ break;
+
+ case "-v":
+ case "--version":
+ console.log( "PEG.js v" + peg.VERSION );
+ process.exit();
+ break;
+
+ default:
+ if ( inputFile !== null ) {
+
+ abort( `Unknown option: "${ argument }".` );
+
+ }
+ inputFile = argument;
+
+ }
+
}
// Validation and defaults
-if (Object.keys(options.dependencies).length > 0) {
- if (DEPENDENCY_FORMATS.indexOf(options.format) === -1) {
- abort(`Can't use the -d/--dependency option with the "${options.format}" module format.`);
- }
-}
+if ( Object.keys( options.dependencies ).length > 0 ) {
+
+ if ( DEPENDENCY_FORMATS.indexOf( options.format ) === -1 ) {
+
+ abort( `Can't use the -d/--dependency option with the "${ options.format }" module format.` );
+
+ }
-if (options.exportVar !== null) {
- if (EXPORT_VAR_FORMATS.indexOf(options.format) === -1) {
- abort(`Can't use the -e/--export-var option with the "${options.format}" module format.`);
- }
}
-if (inputFile === null) {
- inputFile = "-";
+if ( options.exportVar !== null ) {
+
+ if ( EXPORT_VAR_FORMATS.indexOf( options.format ) === -1 ) {
+
+ abort( `Can't use the -e/--export-var option with the "${ options.format }" module format.` );
+
+ }
+
}
-if (outputFile === null) {
- if (inputFile === "-") {
- outputFile = "-";
- } else if (inputFile) {
- outputFile = inputFile.substr(0, inputFile.length - path.extname(inputFile).length) + ".js";
- }
+if ( inputFile === null ) inputFile = "-";
+
+if ( outputFile === null ) {
+
+ if ( inputFile === "-" ) outputFile = "-";
+ else if ( inputFile ) {
+
+ outputFile = inputFile
+ .substr( 0, inputFile.length - path.extname( inputFile ).length )
+ + ".js";
+
+ }
+
}
// Export
diff --git a/bin/peg.js b/bin/peg.js
index 29ef696..976ef9a 100644
--- a/bin/peg.js
+++ b/bin/peg.js
@@ -2,63 +2,100 @@
"use strict";
-let fs = require("fs");
-let peg = require("../lib/peg");
-let options = require("./options");
+const fs = require( "fs" );
+const peg = require( "../lib/peg" );
+const options = require( "./options" );
// Helpers
-function readStream(inputStream, callback) {
- let input = "";
- inputStream.on("data", data => { input += data; });
- inputStream.on("end", () => { callback(input); });
+function readStream( inputStream, callback ) {
+
+ let input = "";
+ inputStream.on( "data", data => {
+
+ input += data;
+
+ } );
+ inputStream.on( "end", () => {
+
+ callback( input );
+
+ } );
+
}
-function abort(message) {
- console.error(message);
- process.exit(1);
+function abort( message ) {
+
+ console.error( message );
+ process.exit( 1 );
+
}
// Main
let inputStream, outputStream;
-if (options.inputFile === "-") {
- process.stdin.resume();
- inputStream = process.stdin;
- inputStream.on("error", () => {
- abort(`Can't read from file "${options.inputFile}".`);
- });
+if ( options.inputFile === "-" ) {
+
+ process.stdin.resume();
+ inputStream = process.stdin;
+ inputStream.on( "error", () => {
+
+ abort( `Can't read from file "${ options.inputFile }".` );
+
+ } );
+
} else {
- inputStream = fs.createReadStream(options.inputFile);
+
+ inputStream = fs.createReadStream( options.inputFile );
+
}
-if (options.outputFile === "-") {
- outputStream = process.stdout;
+if ( options.outputFile === "-" ) {
+
+ outputStream = process.stdout;
+
} else {
- outputStream = fs.createWriteStream(options.outputFile);
- outputStream.on("error", () => {
- abort(`Can't write to file "${options.outputFile}".`);
- });
+
+ outputStream = fs.createWriteStream( options.outputFile );
+ outputStream.on( "error", () => {
+
+ abort( `Can't write to file "${ options.outputFile }".` );
+
+ } );
+
}
-readStream(inputStream, input => {
- let location, source;
-
- try {
- source = peg.generate(input, options);
- } catch (e) {
- if (e.location !== undefined) {
- location = e.location.start;
- abort(location.line + ":" + location.column + ": " + e.message);
- } else {
- abort(e.message);
+readStream( inputStream, input => {
+
+ let location, source;
+
+ try {
+
+ source = peg.generate( input, options );
+
+ } catch ( e ) {
+
+ if ( typeof e.location === "object" ) {
+
+ location = e.location.start;
+ if ( typeof location === "object" ) {
+
+ return abort( location.line + ":" + location.column + ": " + e.message );
+
+ }
+
+ }
+
+ return abort( e.message );
+
}
- }
- outputStream.write(source);
- if (outputStream !== process.stdout) {
- outputStream.end();
- }
-});
+ outputStream.write( source );
+ if ( outputStream !== process.stdout ) {
+
+ outputStream.end();
+
+ }
+} );
diff --git a/gulpfile.js b/gulpfile.js
index 277ff9f..7dbe078 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -1,89 +1,96 @@
"use strict";
-let babelify = require("babelify");
-let browserify = require("browserify");
-let buffer = require("vinyl-buffer");
-let del = require("del");
-let eslint = require("gulp-eslint");
-let gulp = require("gulp");
-let header = require("gulp-header");
-let mocha = require("gulp-mocha");
-let rename = require("gulp-rename");
-let runSequence = require("run-sequence");
-let source = require("vinyl-source-stream");
-let spawn = require("child_process").spawn;
-let uglify = require("gulp-uglify");
-
-function execFile(args) {
- return spawn("node", args.split(" "), { stdio: "inherit" });
+const version = require( "./package" ).version;
+const spawn = require( "child_process" ).spawn;
+const gulp = require( "gulp" );
+const task = gulp.task.bind( gulp );
+const eslint = require( "gulp-eslint" );
+const mocha = require( "gulp-mocha" );
+const dedent = require( "dedent" );
+const browserify = require( "browserify" );
+const babelify = require( "babelify" );
+const source = require( "vinyl-source-stream" );
+const rename = require( "gulp-rename" );
+const buffer = require( "vinyl-buffer" );
+const uglify = require( "gulp-uglify" );
+const header = require( "gulp-header" );
+const del = require( "del" );
+const runSequence = require( "run-sequence" );
+
+function node( args ) {
+
+ return spawn( "node", args.split( " " ), { stdio: "inherit" } );
+
}
// Run ESLint on all JavaScript files.
-gulp.task("lint", () =>
- gulp.src([
- "lib/**/*.js",
- "!lib/parser.js",
- "test/benchmark/**/*.js",
- "test/benchmark/run",
- "test/impact",
- "test/spec/**/*.js",
- "test/server/run",
- "bin/*.js",
- "gulpfile.js"
- ])
- .pipe(eslint())
- .pipe(eslint.format())
- .pipe(eslint.failAfterError())
+task( "lint", () => gulp
+ .src( [
+ "**/.*rc.js",
+ "lib/**/*.js",
+ "!lib/parser.js",
+ "test/benchmark/**/*.js",
+ "test/benchmark/run",
+ "test/impact",
+ "test/spec/**/*.js",
+ "test/server/run",
+ "bin/*.js",
+ "gulpfile.js"
+ ] )
+ .pipe( eslint( { dotfiles: true } ) )
+ .pipe( eslint.format() )
+ .pipe( eslint.failAfterError() )
);
// Run tests.
-gulp.task("test", () =>
- gulp.src("test/spec/**/*.spec.js", { read: false })
- .pipe(mocha())
+task( "test", () => gulp
+ .src( "test/spec/**/*.spec.js", { read: false } )
+ .pipe( mocha() )
);
// Run benchmarks.
-gulp.task("benchmark", () => execFile("test/benchmark/run"));
+task( "benchmark", () => node( "test/benchmark/run" ) );
// Create the browser build.
-gulp.task("browser:build", () => {
- const HEADER = [
- "//",
- "// PEG.js v" + require("./package").version,
- "// https://pegjs.org/",
- "//",
- "// Copyright (c) 2010-2016 David Majda",
- "// Copyright (c) 2017+ Futago-za Ryuu",
- "//",
- "// Licensed under the MIT License.",
- "//",
- ""
- ]
- .map(line => `${line}\n`)
- .join("");
-
- return browserify("lib/peg.js", { standalone: "peg" })
- .transform(babelify, { presets: "es2015", compact: false })
- .bundle()
- .pipe(source("peg.js"))
- .pipe(header(HEADER))
- .pipe(gulp.dest("browser"))
- .pipe(rename({ suffix: ".min" }))
- .pipe(buffer())
- .pipe(uglify())
- .pipe(header(HEADER))
- .pipe(gulp.dest("browser"));
-});
+task( "browser:build", () => {
+
+ const HEADER = dedent`
+
+ /**
+ * PEG.js v${ version }
+ * https://pegjs.org/
+ *
+ * Copyright (c) 2010-2016 David Majda
+ * Copyright (c) 2017+ Futago-za Ryuu
+ *
+ * Released under the MIT License.
+ */\n\n
+
+ `;
+
+ return browserify( "lib/peg.js", { standalone: "peg" } )
+ .transform( babelify, { presets: "es2015", compact: false } )
+ .bundle()
+ .pipe( source( "peg.js" ) )
+ .pipe( header( HEADER ) )
+ .pipe( gulp.dest( "browser" ) )
+ .pipe( rename( { suffix: ".min" } ) )
+ .pipe( buffer() )
+ .pipe( uglify() )
+ .pipe( header( HEADER ) )
+ .pipe( gulp.dest( "browser" ) );
+
+} );
// Delete the browser build.
-gulp.task("browser:clean", () => del("browser"));
+task( "browser:clean", () => del( "browser" ) );
// Generate the grammar parser.
-gulp.task("parser", () =>
- execFile("bin/peg src/parser.pegjs -o lib/parser.js")
+task( "parser", () =>
+ node( "bin/peg src/parser.pegjs -o lib/parser.js" )
);
// Default task.
-gulp.task("default", cb =>
- runSequence("lint", "test", cb)
+task( "default", cb =>
+ runSequence( "benchmark", "test", cb )
);
diff --git a/lib/.eslintrc.js b/lib/.eslintrc.js
index 5663514..c454440 100644
--- a/lib/.eslintrc.js
+++ b/lib/.eslintrc.js
@@ -4,8 +4,16 @@ module.exports = {
"extends": "futagozaryuu/es2015",
"env": {
- "commonjs": true
+
+ "commonjs": true,
+
+ },
+ "root": true,
+ "rules": {
+
+ "prefer-rest-params": 0,
+ "strict": 0,
+
},
- "root": true
};
diff --git a/lib/compiler/asts.js b/lib/compiler/asts.js
index e7f383a..50c4ff7 100644
--- a/lib/compiler/asts.js
+++ b/lib/compiler/asts.js
@@ -1,76 +1,102 @@
"use strict";
-let visitor = require("./visitor");
+const 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];
- }
- }
+const asts = {
+ findRule( ast, name ) {
- return undefined;
- },
+ for ( let i = 0; i < ast.rules.length; i++ ) {
- indexOfRule(ast, name) {
- for (let i = 0; i < ast.rules.length; i++) {
- if (ast.rules[i].name === name) {
- return i;
- }
- }
+ if ( ast.rules[ i ].name === name ) return ast.rules[ i ];
- return -1;
- },
+ }
- alwaysConsumesOnSuccess(ast, node) {
- function consumesTrue() { return true; }
- function consumesFalse() { return false; }
+ return void 0;
- function consumesExpression(node) {
- return consumes(node.expression);
- }
+ },
+
+ 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 ) {
+
+ let consumes;
+
+ 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);
- }
+ 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..6d2fec6 100644
--- a/lib/compiler/index.js
+++ b/lib/compiler/index.js
@@ -1,91 +1,109 @@
"use strict";
-let generateBytecode = require("./passes/generate-bytecode");
-let generateJS = require("./passes/generate-js");
-let removeProxyRules = require("./passes/remove-proxy-rules");
-let reportDuplicateLabels = require("./passes/report-duplicate-labels");
-let reportDuplicateRules = require("./passes/report-duplicate-rules");
-let reportInfiniteRecursion = require("./passes/report-infinite-recursion");
-let reportInfiniteRepetition = require("./passes/report-infinite-repetition");
-let reportUndefinedRules = require("./passes/report-undefined-rules");
-let visitor = require("./visitor");
-
-function processOptions(options, defaults) {
- let processedOptions = {};
-
- Object.keys(options).forEach(name => {
- processedOptions[name] = options[name];
- });
-
- Object.keys(defaults).forEach(name => {
- if (!Object.prototype.hasOwnProperty.call(processedOptions, name)) {
- processedOptions[name] = defaults[name];
- }
- });
+const generateBytecode = require( "./passes/generate-bytecode" );
+const generateJS = require( "./passes/generate-js" );
+const removeProxyRules = require( "./passes/remove-proxy-rules" );
+const reportDuplicateLabels = require( "./passes/report-duplicate-labels" );
+const reportDuplicateRules = require( "./passes/report-duplicate-rules" );
+const reportInfiniteRecursion = require( "./passes/report-infinite-recursion" );
+const reportInfiniteRepetition = require( "./passes/report-infinite-repetition" );
+const reportUndefinedRules = require( "./passes/report-undefined-rules" );
+const visitor = require( "./visitor" );
+
+function processOptions( options, defaults ) {
+
+ const processedOptions = {};
+
+ Object.keys( options ).forEach( name => {
+
+ processedOptions[ name ] = options[ name ];
+
+ } );
+
+ Object.keys( defaults ).forEach( name => {
+
+ if ( ! Object.prototype.hasOwnProperty.call( processedOptions, name ) ) {
+
+ processedOptions[ name ] = defaults[ name ];
+
+ }
+
+ } );
+
+ return processedOptions;
- return processedOptions;
}
-let compiler = {
- // AST node visitor builder. Useful mainly for plugins which manipulate the
- // AST.
- visitor: visitor,
-
- // Compiler passes.
- //
- // Each pass is a function that is passed the AST. It can perform checks on it
- // or modify it as needed. If the pass encounters a semantic error, it throws
- // |peg.GrammarError|.
- passes: {
- check: {
- reportUndefinedRules: reportUndefinedRules,
- reportDuplicateRules: reportDuplicateRules,
- reportDuplicateLabels: reportDuplicateLabels,
- reportInfiniteRecursion: reportInfiniteRecursion,
- reportInfiniteRepetition: reportInfiniteRepetition
- },
- transform: {
- removeProxyRules: removeProxyRules
+const compiler = {
+ // AST node visitor builder. Useful mainly for plugins which manipulate the
+ // AST.
+ visitor: visitor,
+
+ // Compiler passes.
+ //
+ // Each pass is a function that is passed the AST. It can perform checks on it
+ // or modify it as needed. If the pass encounters a semantic error, it throws
+ // |peg.GrammarError|.
+ passes: {
+ check: {
+ reportUndefinedRules: reportUndefinedRules,
+ reportDuplicateRules: reportDuplicateRules,
+ reportDuplicateLabels: reportDuplicateLabels,
+ reportInfiniteRecursion: reportInfiniteRecursion,
+ reportInfiniteRepetition: reportInfiniteRepetition
+ },
+ transform: {
+ removeProxyRules: removeProxyRules
+ },
+ generate: {
+ generateBytecode: generateBytecode,
+ generateJS: generateJS
+ }
},
- generate: {
- generateBytecode: generateBytecode,
- generateJS: generateJS
- }
- },
-
- // Generates a parser from a specified grammar AST. Throws |peg.GrammarError|
- // if the AST contains a semantic error. Note that not all errors are detected
- // during the generation and some may protrude to the generated parser and
- // cause its malfunction.
- compile(ast, passes, options) {
- options = options !== undefined ? options : {};
-
- options = processOptions(options, {
- allowedStartRules: [ast.rules[0].name],
- cache: false,
- dependencies: {},
- exportVar: null,
- format: "bare",
- optimize: "speed",
- output: "parser",
- trace: false
- });
-
- Object.keys(passes).forEach(stage => {
- passes[stage].forEach(p => { p(ast, options); });
- });
-
- switch (options.output) {
- case "parser":
- return eval(ast.code);
-
- case "source":
- return ast.code;
-
- default:
- throw new Error("Invalid output format: " + options.output + ".");
+
+ // 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 = typeof options !== "undefined" ? options : {};
+
+ options = processOptions( options, {
+ allowedStartRules: [ ast.rules[ 0 ].name ],
+ cache: false,
+ dependencies: {},
+ exportVar: null,
+ format: "bare",
+ optimize: "speed",
+ output: "parser",
+ trace: false
+ } );
+
+ Object.keys( passes ).forEach( stage => {
+
+ passes[ stage ].forEach( pass => {
+
+ pass( ast, options );
+
+ } );
+
+ } );
+
+ switch ( options.output ) {
+
+ case "parser":
+ return eval( ast.code );
+
+ case "source":
+ return ast.code;
+
+ 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..f09251a 100644
--- a/lib/compiler/js.js
+++ b/lib/compiler/js.js
@@ -1,54 +1,62 @@
"use strict";
-function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
+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));
- },
-
- 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));
- }
+const 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 ) );
+
+ },
+
+ 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..e91ed65 100644
--- a/lib/compiler/opcodes.js
+++ b/lib/compiler/opcodes.js
@@ -1,54 +1,56 @@
"use strict";
// Bytecode instruction opcodes.
-let opcodes = {
- // 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
-
- // 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
-
- // 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
-
- // 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
-
- // Rules
-
- RULE: 27, // RULE r
-
- // Failure Reporting
-
- SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
- SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
+const opcodes = {
+
+ // 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
+
+ // 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
+
+ // 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
+
+ // 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
+
+ // Rules
+
+ RULE: 27, // RULE r
+
+ // Failure Reporting
+
+ 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 3f36ce9..c364b75 100644
--- a/lib/compiler/passes/generate-bytecode.js
+++ b/lib/compiler/passes/generate-bytecode.js
@@ -1,9 +1,9 @@
"use strict";
-let asts = require("../asts");
-let js = require("../js");
-let op = require("../opcodes");
-let visitor = require("../visitor");
+const asts = require( "../asts" );
+const js = require( "../js" );
+const op = require( "../opcodes" );
+const visitor = require( "../visitor" );
// Generates bytecode.
//
@@ -187,431 +187,494 @@ let visitor = require("../visitor");
// [29] SILENT_FAILS_OFF
//
// 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) {
+function generateBytecode( ast ) {
+
+ const consts = [];
+ let generate;
+
+ function addConst( value ) {
+
+ const 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 ) {
+
+ const 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 ) {
+
+ const 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(
- generate(alternatives[0], {
- sp: context.sp,
- env: cloneEnv(context.env),
- action: null
- }),
- alternatives.length > 1
- ? buildCondition(
- [op.IF_ERROR],
+ [ 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],
- buildAlternativesCode(alternatives.slice(1), context)
+ [ 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 ]
+ )
+ )
);
- }
-
- 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
- }),
+
+ }
+
+ function buildSemanticPredicate( code, negative, context ) {
+
+ const functionIndex = addFunctionConst( Object.keys( context.env ), code );
+
+ return buildSequence(
+ [ op.UPDATE_SAVED_POS ],
+ buildCall( functionIndex, 0, context.env, context.sp ),
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]
- )
+ [ op.IF ],
+ buildSequence( [ op.POP ], negative ? [ op.PUSH_FAILED ] : [ op.PUSH_UNDEFINED ] ),
+ buildSequence( [ op.POP ], negative ? [ op.PUSH_UNDEFINED ] : [ op.PUSH_FAILED ] )
)
- );
- } else {
- if (context.action) {
- let functionIndex = addFunctionConst(
- Object.keys(context.env),
- context.action.code
+ );
+
+ }
+
+ function buildAppendLoop( expressionCode ) {
+
+ return buildLoop(
+ [ op.WHILE_NOT_ERROR ],
+ buildSequence( [ op.APPEND ], expressionCode )
+ );
+
+ }
+
+ 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 ) {
+
+ const 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 < 2
+ ? []
+ : buildCondition(
+ [ op.IF_ERROR ],
+ buildSequence(
+ [ op.POP ],
+ buildAlternativesCode( alternatives.slice( 1 ), context )
+ ),
+ []
+ )
+ );
+
+ }
+
+ return buildAlternativesCode( node.alternatives, context );
+
+ },
+
+ action( node, context ) {
+
+ const env = cloneEnv( context.env );
+ const emitCall = node.expression.type !== "sequence" || node.expression.elements.length === 0;
+ const expressionCode = generate( node.expression, {
+ sp: context.sp + ( emitCall ? 1 : 0 ),
+ env: env,
+ action: node
+ } );
+ const functionIndex = addFunctionConst( Object.keys( env ), node.code );
+
+ return emitCall === false
+ ? expressionCode
+ : 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 ]
+ );
+
+ },
+
+ sequence( node, context ) {
+
+ function buildElementsCode( elements, context ) {
+
+ if ( elements.length > 0 ) {
+
+ const 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 ) {
+
+ const functionIndex = addFunctionConst(
+ Object.keys( context.env ),
+ context.action.code
+ );
+
+ return buildSequence(
+ [ op.LOAD_SAVED_POS, node.elements.length ],
+ buildCall(
+ functionIndex,
+ node.elements.length + 1,
+ context.env,
+ context.sp
+ )
+ );
+
+ }
+ return buildSequence( [ op.WRAP, node.elements.length ], [ op.NIP ] );
+
+ }
+
return buildSequence(
- [op.LOAD_SAVED_POS, node.elements.length],
- buildCall(
- functionIndex,
- node.elements.length + 1,
- context.env,
- context.sp
- )
+ [ op.PUSH_CURR_POS ],
+ buildElementsCode( node.elements, {
+ sp: context.sp + 1,
+ env: context.env,
+ action: context.action
+ } )
);
- } else {
- return buildSequence([op.WRAP, node.elements.length], [op.NIP]);
- }
+
+ },
+
+ labeled( node, context ) {
+
+ const 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 ) {
+
+ const 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 ) {
+
+ const 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 ) {
+
+ const stringIndex = addConst( `"${ js.stringEscape(
+ node.ignoreCase ? node.value.toLowerCase() : node.value
+ ) }"` );
+ const 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 ]
+ );
+
+ }
+
+ const stringIndex = addConst( "\"\"" );
+ return [ op.PUSH, stringIndex ];
+
+ },
+
+ class( node ) {
+
+ const regexp = "/^["
+ + ( node.inverted ? "^" : "" )
+ + node.parts
+ .map( part =>
+ ( Array.isArray( part )
+ ? js.regexpClassEscape( part[ 0 ] )
+ + "-"
+ + js.regexpClassEscape( part[ 1 ] )
+ : js.regexpClassEscape( part ) )
+ )
+ .join( "" )
+ + "]/"
+ + ( node.ignoreCase ? "i" : "" );
+
+ const parts = "["
+ + node.parts
+ .map( part =>
+ ( Array.isArray( part )
+ ? `["${ js.stringEscape( part[ 0 ] ) }", "${ js.stringEscape( part[ 1 ] ) }"]`
+ : "\"" + js.stringEscape( part ) + "\"" )
+ )
+ .join( ", " )
+ + "]";
+
+ const regexpIndex = addConst( regexp );
+ const expectedIndex = addConst(
+ "peg$classExpectation("
+ + parts + ", "
+ + node.inverted + ", "
+ + node.ignoreCase
+ + ")"
+ );
+
+ return buildCondition(
+ [ op.MATCH_REGEXP, regexpIndex ],
+ [ op.ACCEPT_N, 1 ],
+ [ op.FAIL, expectedIndex ]
+ );
+
+ },
+
+ any() {
+
+ const expectedIndex = addConst( "peg$anyExpectation()" );
+
+ return buildCondition(
+ [ op.MATCH_ANY ],
+ [ op.ACCEPT_N, 1 ],
+ [ op.FAIL, expectedIndex ]
+ );
+
}
- }
-
- 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
- )
- + "\""
- );
- 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 = "/^["
- + (node.inverted ? "^" : "")
- + node.parts.map(part =>
- Array.isArray(part)
- ? js.regexpClassEscape(part[0])
- + "-"
- + js.regexpClassEscape(part[1])
- : js.regexpClassEscape(part)
- ).join("")
- + "]/" + (node.ignoreCase ? "i" : "");
- let parts = "["
- + node.parts.map(part =>
- Array.isArray(part)
- ? "[\"" + js.stringEscape(part[0]) + "\", \"" + js.stringEscape(part[1]) + "\"]"
- : "\"" + js.stringEscape(part) + "\""
- ).join(", ")
- + "]";
- 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 );
- generate(ast);
}
module.exports = generateBytecode;
diff --git a/lib/compiler/passes/generate-js.js b/lib/compiler/passes/generate-js.js
index 8adeaa0..8e9c7ce 100644
--- a/lib/compiler/passes/generate-js.js
+++ b/lib/compiler/passes/generate-js.js
@@ -1,1423 +1,1567 @@
+/* eslint no-mixed-operators: 0, prefer-const: 0 */
+
"use strict";
-let asts = require("../asts");
-let js = require("../js");
-let op = require("../opcodes");
+const asts = require( "../asts" );
+const js = require( "../js" );
+const op = require( "../opcodes" );
// Generates parser JavaScript code.
-function generateJS(ast, options) {
- /* These only indent non-empty lines to avoid trailing whitespace. */
- const lineMatchRE = /^([^`\r\n]+?(?:`[^`]*?`[^\r\n]*?)?)$/gm;
- function indent2(code) { return code.replace(lineMatchRE, " $1"); }
- function indent10(code) { return code.replace(lineMatchRE, " $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(""))
- + "\")"
- ).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"));
+function generateJS( ast, options ) {
+
+ /* These only indent non-empty lines to avoid trailing whitespace. */
+ const lineMatchRE = /^([^`\r\n]+?(?:`[^`]*?`[^\r\n]*?)?)$/gm;
+ function indent2( code ) {
+
+ return code.replace( lineMatchRE, " $1" );
+
}
+ function indent10( code ) {
+
+ return code.replace( lineMatchRE, " $1" );
- 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 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( "" )
+ )
+ }")`
+ )
+ .join( ",\n" )
+ ),
+ "];"
+ ].join( "\n" );
- function generateRuleFooter(ruleNameCode, resultCode) {
- let parts = [];
+ }
- if (options.cache) {
- parts.push([
- "",
- "peg$resultsCache[key] = { nextPos: peg$currPos, result: " + resultCode + " };"
- ].join("\n"));
- }
+ return ast.consts.map( ( c, i ) => "var peg$c" + i + " = " + c + ";" ).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 generateRuleHeader( ruleNameCode, ruleIndexCode ) {
+
+ const 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 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 generateRuleFooter( ruleNameCode, resultCode ) {
+
+ const 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 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");
+ function generateInterpreter() {
+
+ const parts = [];
+
+ function generateCondition( cond, argsLength ) {
+
+ const baseLength = argsLength + 3;
+ const thenLengthCode = "bc[ip + " + ( baseLength - 2 ) + "]";
+ const 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 ) {
+
+ const baseLength = 2;
+ const 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() {
+
+ const baseLength = 4;
+ const 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" );
+
}
- 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"));
+ function generateRuleFunction( rule ) {
+
+ const parts = [];
+ const stackVars = [];
+
+ 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
+
+ const stack = {
+ sp: -1,
+ maxSp: -1,
+
+ push( exprCode ) {
+
+ const code = s( ++this.sp ) + " = " + exprCode + ";";
+ if ( this.sp > this.maxSp ) this.maxSp = this.sp;
+ return code;
+
+ },
+
+ pop( n ) {
+
+ if ( typeof n === "undefined" ) return s( this.sp-- );
+
+ const 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;
+ const end = bc.length;
+ const parts = [];
+ let value;
+
+ function compileCondition( cond, argCount ) {
+
+ const baseLength = argCount + 3;
+ const thenLength = bc[ ip + baseLength - 2 ];
+ const elseLength = bc[ ip + baseLength - 1 ];
+ const 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 ) {
+
+ const baseLength = 2;
+ const bodyLength = bc[ ip + baseLength - 1 ];
+ const 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() {
+
+ const baseLength = 4;
+ const paramsLength = bc[ ip + baseLength - 1 ];
+
+ const value = c( bc[ ip + 1 ] )
+ + "("
+ + bc
+ .slice( ip + baseLength, ip + baseLength + paramsLength )
+ .map( 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, "
+ + eval( ast.consts[ bc[ ip + 1 ] ] ).length
+ + ") === "
+ + c( bc[ ip + 1 ] )
+ : "input.charCodeAt(peg$currPos) === "
+ + eval( ast.consts[ bc[ ip + 1 ] ] ).charCodeAt( 0 )
+ , 1
+ );
+ break;
+
+ 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" );
+
+ }
+
+ const 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" );
+
}
- 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--);
+ function generateToplevel() {
+
+ const 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" ) {
+
+ const startRuleIndices = "{ "
+ + options.allowedStartRules
+ .map( r => r + ": " + asts.indexOfRule( ast, r ) )
+ .join( ", " )
+ + " }";
+ const startRuleIndex = asts.indexOfRule( ast, options.allowedStartRules[ 0 ] );
+
+ parts.push( [
+ " var peg$startRuleIndices = " + startRuleIndices + ";",
+ " var peg$startRuleIndex = " + startRuleIndex + ";"
+ ].join( "\n" ) );
+
} else {
- let values = Array(n);
- for (let i = 0; i < n; i++) {
- values[i] = s(this.sp - n + 1 + i);
- }
+ const startRuleFunctions = "{ "
+ + options.allowedStartRules
+ .map( r => r + ": peg$parse" + r )
+ .join( ", " )
+ + " }";
+ const startRuleFunction = "peg$parse" + options.allowedStartRules[ 0 ];
- this.sp -= n;
+ parts.push( [
+ " var peg$startRuleFunctions = " + startRuleFunctions + ";",
+ " var peg$startRuleFunction = " + startRuleFunction + ";"
+ ].join( "\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( "" );
+
+ 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" ) );
+
}
- 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.");
+
+ if ( options.trace ) {
+
+ if ( options.optimize === "size" ) {
+
+ const ruleNames = "["
+ + ast.rules
+ .map( 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("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(", ")
- + ")";
- 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) === "
- + eval(ast.consts[bc[ip + 1]]).charCodeAt(0),
- 1
- );
- break;
-
- 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] + ".");
+ 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" ) );
+
}
- }
- return parts.join("\n");
- }
+ parts.push( [
+ "",
+ " function text() {",
+ " return input.substring(peg$savedPos, peg$currPos);",
+ " }",
+ "",
+ " function offset() {",
+ " return peg$savedPos;",
+ " }",
+ "",
+ " function range() {",
+ " return [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( "" );
- code = compile(rule.bytecode);
+ } else {
- parts.push("function peg$parse" + rule.name + "() {");
+ ast.rules.forEach( rule => {
- if (options.trace) {
- parts.push(" var startPos = peg$currPos;");
- }
+ parts.push( indent2( generateRuleFunction( rule ) ) );
+ parts.push( "" );
- 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(", ")
- + " }";
- 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(", ")
- + " }";
- let startRuleFunction = "peg$parse" + options.allowedStartRules[0];
-
- parts.push([
- " var peg$startRuleFunctions = " + startRuleFunctions + ";",
- " var peg$startRuleFunction = " + startRuleFunction + ";"
- ].join("\n"));
- }
+ if ( ast.initializer ) {
- 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"));
- }
+ parts.push( indent2( ast.initializer.code ) );
+ parts.push( "" );
- if (options.trace) {
- if (options.optimize === "size") {
- let ruleNames = "["
- + ast.rules.map(
- 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"));
- }
+ if ( options.optimize === "size" ) {
- parts.push([
- "",
- " function text() {",
- " return input.substring(peg$savedPos, peg$currPos);",
- " }",
- "",
- " function offset() {",
- " return peg$savedPos;",
- " }",
- "",
- " function range() {",
- " return [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("");
- });
- }
+ parts.push( " peg$result = peg$parseRule(peg$startRuleIndex);" );
- if (ast.initializer) {
- parts.push(indent2(ast.initializer.code));
- parts.push("");
- }
+ } else {
- if (options.optimize === "size") {
- parts.push(" peg$result = peg$parseRule(peg$startRuleIndex);");
- } else {
- parts.push(" peg$result = peg$startRuleFunction();");
- }
+ 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");
- }
+ 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 generateParserExports() {
- return options.trace
- ? [
- "{",
- " peg$SyntaxError as SyntaxError,",
- " peg$DefaultTracer as DefaultTracer,",
- " peg$parse as parse",
- "}"
- ].join("\n")
- : [
- "{",
- " peg$SyntaxError as SyntaxError,",
- " peg$parse as 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("");
+ function generateWrapper( toplevelCode ) {
+
+ function generateGeneratedByComment() {
+
+ return [
+ "// Generated by PEG.js 0.10.0.",
+ "//",
+ "// https://pegjs.org/"
+ ].join( "\n" );
+
}
- parts.push([
- toplevelCode,
- "",
- "module.exports = " + generateParserObject() + ";",
- ""
- ].join("\n"));
-
- return parts.join("\n");
- },
-
- es() {
- let parts = [];
- let dependencyVars = Object.keys(options.dependencies);
-
- parts.push(
- generateGeneratedByComment(),
- ""
- );
-
- if (dependencyVars.length > 0) {
- dependencyVars.forEach(variable => {
- parts.push("import " + variable
- + " from \""
- + js.stringEscape(options.dependencies[variable])
- + "\";"
- );
- });
- parts.push("");
+ function generateParserObject() {
+
+ return options.trace
+ ? [
+ "{",
+ " SyntaxError: peg$SyntaxError,",
+ " DefaultTracer: peg$DefaultTracer,",
+ " parse: peg$parse",
+ "}"
+ ].join( "\n" )
+ : [
+ "{",
+ " SyntaxError: peg$SyntaxError,",
+ " parse: peg$parse",
+ "}"
+ ].join( "\n" );
+
}
- parts.push(
- toplevelCode,
- "",
- "export " + generateParserExports() + ";",
- "",
- "export default " + generateParserObject() + ";",
- ""
- );
-
- 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(", ")
- + "]";
- 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(", ")
- + "]";
- 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"));
+ function generateParserExports() {
+
+ return options.trace
+ ? [
+ "{",
+ " peg$SyntaxError as SyntaxError,",
+ " peg$DefaultTracer as DefaultTracer,",
+ " peg$parse as parse",
+ "}"
+ ].join( "\n" )
+ : [
+ "{",
+ " peg$SyntaxError as SyntaxError,",
+ " peg$parse as parse",
+ "}"
+ ].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());
+ const generators = {
+ bare() {
+
+ return [
+ generateGeneratedByComment(),
+ "(function() {",
+ " \"use strict\";",
+ "",
+ indent2( toplevelCode ),
+ "",
+ indent2( "return " + generateParserObject() + ";" ),
+ "})()"
+ ].join( "\n" );
+
+ },
+
+ commonjs() {
+
+ const parts = [];
+ const 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" );
+
+ },
+
+ es() {
+
+ const parts = [];
+ const dependencyVars = Object.keys( options.dependencies );
+
+ parts.push(
+ generateGeneratedByComment(),
+ ""
+ );
+
+ if ( dependencyVars.length > 0 ) {
+
+ dependencyVars.forEach( variable => {
+
+ parts.push( "import " + variable
+ + " from \""
+ + js.stringEscape( options.dependencies[ variable ] )
+ + "\";"
+ );
+
+ } );
+ parts.push( "" );
+
+ }
+
+ parts.push(
+ toplevelCode,
+ "",
+ "export " + generateParserExports() + ";",
+ "",
+ "export default " + generateParserObject() + ";",
+ ""
+ );
+
+ return parts.join( "\n" );
+
+ },
+
+ amd() {
+
+ const dependencyVars = Object.keys( options.dependencies );
+ const dependencyIds = dependencyVars.map( v => options.dependencies[ v ] );
+ const dependencies = "["
+ + dependencyIds
+ .map( id => `"${ js.stringEscape( id ) }"` )
+ .join( ", " )
+ + "]";
+ const 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() {
+
+ const parts = [];
+ const dependencyVars = Object.keys( options.dependencies );
+ const dependencyIds = dependencyVars.map( v => options.dependencies[ v ] );
+ const dependencies = "["
+ + dependencyIds
+ .map( id => `"${ js.stringEscape( id ) }"` )
+ .join( ", " )
+ + "]";
+ const requires = dependencyIds
+ .map( id => `require("${ js.stringEscape( id ) }")` )
+ .join( ", " );
+ const 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..a452e0e 100644
--- a/lib/compiler/passes/remove-proxy-rules.js
+++ b/lib/compiler/passes/remove-proxy-rules.js
@@ -1,39 +1,59 @@
"use strict";
-let visitor = require("../visitor");
+const 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;
- }
- }
- });
+function removeProxyRules( ast, options ) {
- replace(ast);
- }
+ function isProxyRule( node ) {
- let indices = [];
+ return node.type === "rule" && node.expression.type === "rule_ref";
- 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();
+ function replaceRuleRefs( ast, from, to ) {
+
+ const replace = visitor.build( {
+ rule_ref( node ) {
+
+ if ( node.name === from ) {
+
+ node.name = to;
+
+ }
+
+ }
+ } );
+
+ replace( ast );
+
+ }
+
+ const 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 );
+
+ } );
- 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..ab13144 100644
--- a/lib/compiler/passes/report-duplicate-labels.js
+++ b/lib/compiler/passes/report-duplicate-labels.js
@@ -1,62 +1,83 @@
"use strict";
-let GrammarError = require("../../grammar-error");
-let visitor = require("../visitor");
+const GrammarError = require( "../../grammar-error" );
+const visitor = require( "../visitor" );
// Checks that each label is defined only once within each scope.
-function reportDuplicateLabels(ast) {
- function cloneEnv(env) {
- let clone = {};
-
- Object.keys(env).forEach(name => {
- clone[name] = env[name];
- });
-
- return clone;
- }
-
- function checkExpressionWithClonedEnv(node, env) {
- check(node.expression, cloneEnv(env));
- }
-
- let check = visitor.build({
- rule(node) {
- check(node.expression, { });
- },
-
- choice(node, env) {
- node.alternatives.forEach(alternative => {
- check(alternative, cloneEnv(env));
- });
- },
-
- action: checkExpressionWithClonedEnv,
-
- 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
- );
- }
-
- check(node.expression, env);
-
- 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
- });
-
- check(ast);
+function reportDuplicateLabels( ast ) {
+
+ let check;
+
+ function cloneEnv( env ) {
+
+ const clone = {};
+
+ Object.keys( env ).forEach( name => {
+
+ clone[ name ] = env[ name ];
+
+ } );
+
+ return clone;
+
+ }
+
+ function checkExpressionWithClonedEnv( node, env ) {
+
+ check( node.expression, cloneEnv( env ) );
+
+ }
+
+ check = visitor.build( {
+ rule( node ) {
+
+ check( node.expression, {} );
+
+ },
+
+ choice( node, env ) {
+
+ node.alternatives.forEach( alternative => {
+
+ check( alternative, cloneEnv( env ) );
+
+ } );
+
+ },
+
+ action: checkExpressionWithClonedEnv,
+
+ labeled( node, env ) {
+
+ const label = node.label;
+
+ if ( Object.prototype.hasOwnProperty.call( env, label ) ) {
+
+ const start = env[ label ].start;
+
+ throw new GrammarError(
+ `Label "${ label }" is already defined at line ${ start.line }, column ${ start.column }.`,
+ node.location
+ );
+
+ }
+
+ check( node.expression, env );
+ env[ label ] = node.location;
+
+ },
+
+ text: checkExpressionWithClonedEnv,
+ simple_and: checkExpressionWithClonedEnv,
+ simple_not: checkExpressionWithClonedEnv,
+ optional: checkExpressionWithClonedEnv,
+ zero_or_more: checkExpressionWithClonedEnv,
+ one_or_more: checkExpressionWithClonedEnv,
+ group: checkExpressionWithClonedEnv
+ } );
+
+ 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..55826a3 100644
--- a/lib/compiler/passes/report-duplicate-rules.js
+++ b/lib/compiler/passes/report-duplicate-rules.js
@@ -1,28 +1,36 @@
"use strict";
-let GrammarError = require("../../grammar-error");
-let visitor = require("../visitor");
+const GrammarError = require( "../../grammar-error" );
+const visitor = require( "../visitor" );
// Checks that each rule is defined only once.
-function reportDuplicateRules(ast) {
- 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 "
- + "at line " + rules[node.name].start.line + ", "
- + "column " + rules[node.name].start.column + ".",
- node.location
- );
- }
-
- rules[node.name] = node.location;
- }
- });
-
- check(ast);
+function reportDuplicateRules( ast ) {
+
+ const rules = {};
+
+ const check = visitor.build( {
+ rule( node ) {
+
+ const name = node.name;
+
+ if ( Object.prototype.hasOwnProperty.call( rules, name ) ) {
+
+ const start = rules[ name ].start;
+
+ throw new GrammarError(
+ `Rule "${ name }" is already defined at line ${ start.line }, column ${ start.column }.`,
+ node.location
+ );
+
+ }
+
+ rules[ node.name ] = node.location;
+
+ }
+ } );
+
+ 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..c7a6840 100644
--- a/lib/compiler/passes/report-infinite-recursion.js
+++ b/lib/compiler/passes/report-infinite-recursion.js
@@ -1,8 +1,8 @@
"use strict";
-let GrammarError = require("../../grammar-error");
-let asts = require("../asts");
-let visitor = require("../visitor");
+const GrammarError = require( "../../grammar-error" );
+const asts = require( "../asts" );
+const visitor = require( "../visitor" );
// Reports left recursion in the grammar, which prevents infinite recursion in
// the generated parser.
@@ -14,41 +14,52 @@ 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 check = visitor.build({
- rule(node) {
- visitedRules.push(node.name);
- check(node.expression);
- visitedRules.pop(node.name);
- },
-
- sequence(node) {
- node.elements.every(element => {
- check(element);
-
- return !asts.alwaysConsumesOnSuccess(ast, element);
- });
- },
-
- rule_ref(node) {
- if (visitedRules.indexOf(node.name) !== -1) {
- visitedRules.push(node.name);
-
- throw new GrammarError(
- "Possible infinite loop when parsing (left recursion: "
- + visitedRules.join(" -> ")
- + ").",
- node.location
- );
- }
-
- check(asts.findRule(ast, node.name));
- }
- });
-
- check(ast);
+function reportInfiniteRecursion( ast ) {
+
+ const visitedRules = [];
+
+ const check = visitor.build( {
+ rule( node ) {
+
+ visitedRules.push( node.name );
+ check( node.expression );
+ visitedRules.pop( node.name );
+
+ },
+
+ sequence( node ) {
+
+ node.elements.every( element => {
+
+ check( element );
+
+ return ! asts.alwaysConsumesOnSuccess( ast, element );
+
+ } );
+
+ },
+
+ rule_ref( node ) {
+
+ if ( visitedRules.indexOf( node.name ) !== -1 ) {
+
+ visitedRules.push( node.name );
+ const rulePath = visitedRules.join( " -> " );
+
+ throw new GrammarError(
+ `Possible infinite loop when parsing (left recursion: ${ rulePath }).`,
+ node.location
+ );
+
+ }
+
+ check( asts.findRule( ast, node.name ) );
+
+ }
+ } );
+
+ 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..4bf756f 100644
--- a/lib/compiler/passes/report-infinite-repetition.js
+++ b/lib/compiler/passes/report-infinite-repetition.js
@@ -1,33 +1,43 @@
"use strict";
-let GrammarError = require("../../grammar-error");
-let asts = require("../asts");
-let visitor = require("../visitor");
+const GrammarError = require( "../../grammar-error" );
+const asts = require( "../asts" );
+const 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
- );
- }
- },
-
- 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);
+function reportInfiniteRepetition( ast ) {
+
+ const 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
+ );
+
+ }
+
+ }
+ } );
+
+ check( ast );
+
}
module.exports = reportInfiniteRepetition;
diff --git a/lib/compiler/passes/report-undefined-rules.js b/lib/compiler/passes/report-undefined-rules.js
index 4108432..798568e 100644
--- a/lib/compiler/passes/report-undefined-rules.js
+++ b/lib/compiler/passes/report-undefined-rules.js
@@ -1,32 +1,43 @@
"use strict";
-let GrammarError = require("../../grammar-error");
-let asts = require("../asts");
-let visitor = require("../visitor");
+const GrammarError = require( "../../grammar-error" );
+const asts = require( "../asts" );
+const visitor = require( "../visitor" );
// Checks that all referenced rules exist.
-function reportUndefinedRules(ast, options) {
- let check = visitor.build({
- rule_ref(node) {
- if (!asts.findRule(ast, node.name)) {
- throw new GrammarError(
- "Rule \"" + node.name + "\" is not defined.",
- node.location
- );
- }
+function reportUndefinedRules( ast, options ) {
+
+ const 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 );
+
+ if ( options.allowedStartRules ) {
+
+ options.allowedStartRules.forEach( rule => {
+
+ if ( ! asts.findRule( ast, rule ) ) {
+
+ throw new GrammarError( `Start rule "${ rule }" is not defined.` );
+
+ }
+
+ } );
+
}
- });
-
- check(ast);
-
- if (options.allowedStartRules) {
- options.allowedStartRules.forEach(rule => {
- if (!asts.findRule(ast, rule)) {
- throw new GrammarError(
- "Start rule \"" + rule + "\" is not defined.");
- }
- });
- }
+
}
module.exports = reportUndefinedRules;
diff --git a/lib/compiler/visitor.js b/lib/compiler/visitor.js
index 8f5d08a..d4b97c7 100644
--- a/lib/compiler/visitor.js
+++ b/lib/compiler/visitor.js
@@ -1,75 +1,97 @@
"use strict";
// Simple AST node visitor builder.
-let visitor = {
- build(functions) {
- function visit(node) {
- return functions[node.type].apply(null, arguments);
- }
+const visitor = {
+ build( functions ) {
- function visitNop() {
- // Do nothing.
- }
+ function visit( node ) {
- function visitExpression(node) {
- let extraArgs = Array.prototype.slice.call(arguments, 1);
+ return functions[ node.type ].apply( null, arguments );
- visit.apply(null, [node.expression].concat(extraArgs));
- }
+ }
- function visitChildren(property) {
- return function(node) {
- let extraArgs = Array.prototype.slice.call(arguments, 1);
+ function visitNop() {
+ // Do nothing.
+ }
- node[property].forEach(child => {
- visit.apply(null, [child].concat(extraArgs));
- });
- };
- }
+ function visitExpression( node ) {
+
+ const extraArgs = Array.prototype.slice.call( arguments, 1 );
- const DEFAULT_FUNCTIONS = {
- grammar(node) {
- let extraArgs = Array.prototype.slice.call(arguments, 1);
+ visit( ...[ node.expression ].concat( extraArgs ) );
- if (node.initializer) {
- visit.apply(null, [node.initializer].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
- };
-
- Object.keys(DEFAULT_FUNCTIONS).forEach(type => {
- if (!Object.prototype.hasOwnProperty.call(functions, type)) {
- functions[type] = DEFAULT_FUNCTIONS[type];
- }
- });
-
- return visit;
- }
+ function visitChildren( property ) {
+
+ return function visitProperty( node ) {
+
+ const extraArgs = Array.prototype.slice.call( arguments, 1 );
+
+ node[ property ].forEach( child => {
+
+ visit( ...[ child ].concat( extraArgs ) );
+
+ } );
+
+ };
+
+ }
+
+ const DEFAULT_FUNCTIONS = {
+ grammar( node ) {
+
+ const extraArgs = Array.prototype.slice.call( arguments, 1 );
+
+ if ( node.initializer ) {
+
+ visit( ...[ node.initializer ].concat( extraArgs ) );
+
+ }
+
+ node.rules.forEach( rule => {
+
+ visit( ...[ 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
+ };
+
+ Object.keys( DEFAULT_FUNCTIONS ).forEach( type => {
+
+ if ( ! Object.prototype.hasOwnProperty.call( functions, type ) ) {
+
+ functions[ type ] = DEFAULT_FUNCTIONS[ type ];
+
+ }
+
+ } );
+
+ return visit;
+
+ }
};
module.exports = visitor;
diff --git a/lib/grammar-error.js b/lib/grammar-error.js
index ce3fb98..68fbaff 100644
--- a/lib/grammar-error.js
+++ b/lib/grammar-error.js
@@ -2,15 +2,21 @@
// Thrown when the grammar contains an error.
class GrammarError {
- constructor(message, location) {
- this.name = "GrammarError";
- this.message = message;
- this.location = location;
- if (typeof Error.captureStackTrace === "function") {
- Error.captureStackTrace(this, GrammarError);
+ constructor( message, location ) {
+
+ this.name = "GrammarError";
+ this.message = message;
+ this.location = location;
+
+ if ( typeof Error.captureStackTrace === "function" ) {
+
+ Error.captureStackTrace( this, GrammarError );
+
+ }
+
}
- }
+
}
module.exports = GrammarError;
diff --git a/lib/peg.js b/lib/peg.js
index a4639fe..66d1191 100644
--- a/lib/peg.js
+++ b/lib/peg.js
@@ -1,54 +1,64 @@
"use strict";
-let GrammarError = require("./grammar-error");
-let compiler = require("./compiler");
-let parser = require("./parser");
-
-let peg = {
- // PEG.js version (uses semantic versioning).
- VERSION: "0.10.0",
-
- GrammarError: GrammarError,
- parser: parser,
- compiler: compiler,
-
- // Generates a parser from a specified grammar and returns it.
- //
- // The grammar must be a string in the format described by the metagramar in
- // the parser.pegjs file.
- //
- // Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
- // |peg.GrammarError| if it contains a semantic error. Note that not all
- // errors are detected during the generation and some may protrude to the
- // generated parser and cause its malfunction.
- generate(grammar, options) {
- options = options !== undefined ? options : {};
-
- function convertPasses(passes) {
- let converted = {};
-
- Object.keys(passes).forEach(stage => {
- converted[stage] = Object.keys(passes[stage])
- .map(name => passes[stage][name]);
- });
-
- return converted;
- }
+const GrammarError = require( "./grammar-error" );
+const compiler = require( "./compiler" );
+const parser = require( "./parser" );
+
+const 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 = typeof options !== "undefined" ? options : {};
+
+ function convertPasses( passes ) {
+
+ const converted = {};
+
+ Object.keys( passes ).forEach( stage => {
- let plugins = "plugins" in options ? options.plugins : [];
- let config = {
- parser: peg.parser,
- passes: convertPasses(peg.compiler.passes)
- };
+ converted[ stage ] = Object.keys( passes[ stage ] )
+ .map( name => passes[ stage ][ name ] );
- plugins.forEach(p => { p.use(config, options); });
+ } );
- return peg.compiler.compile(
- config.parser.parse(grammar),
- config.passes,
- options
- );
- }
+ return converted;
+
+ }
+
+ const plugins = "plugins" in options ? options.plugins : [];
+ const 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/package.json b/package.json
index ce9d644..10f3258 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"devDependencies": {
"babel-preset-es2015": "6.24.1",
"babel-core": "6.26.0",
+ "dedent": "0.7.0",
"babelify": "8.0.0",
"browserify": "14.5.0",
"chai": "4.1.2",
diff --git a/test/.eslintrc.js b/test/.eslintrc.js
index d2cf41d..f3e9e90 100644
--- a/test/.eslintrc.js
+++ b/test/.eslintrc.js
@@ -5,8 +5,10 @@ module.exports = {
"extends": "futagozaryuu/test",
"rules": {
- "node/shebang": 0
+ "node/shebang": 0,
+ "func-names": 0,
+ "no-mixed-operators": 0,
- }
+ },
};
diff --git a/test/benchmark/benchmarks.js b/test/benchmark/benchmarks.js
index 227888b..3dcb899 100644
--- a/test/benchmark/benchmarks.js
+++ b/test/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)" }
- ]
- }
+const 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)" }
+ ]
+ }
];
module.exports = benchmarks;
diff --git a/test/benchmark/index.js b/test/benchmark/index.js
index 57fd4aa..75bdc11 100644
--- a/test/benchmark/index.js
+++ b/test/benchmark/index.js
@@ -2,137 +2,152 @@
/* eslint-env browser, jquery */
-let Runner = require("./runner.js");
-let benchmarks = require("./benchmarks.js");
-
-$("#run").click(() => {
- // Results Table Manipulation
-
- let resultsTable = $("#results-table");
-
- function appendResult(klass, title, url, inputSize, parseTime) {
- const KB = 1024;
- const MS_IN_S = 1000;
-
- resultsTable.append(
- "
"
- + ""
- + (url !== null ? "" : "")
- + title
- + (url !== null ? "" : "")
- + " | "
- + ""
- + ""
- + (inputSize / KB).toFixed(2)
- + ""
- + " kB"
- + " | "
- + ""
- + ""
- + parseTime.toFixed(2)
- + ""
- + " ms"
- + " | "
- + ""
- + ""
- + ((inputSize / KB) / (parseTime / MS_IN_S)).toFixed(2)
- + ""
- + " 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: "benchmark/" + file,
- dataType: "text",
- async: false
- }).responseText;
- },
-
- testStart() {
- // Nothing to do.
- },
-
- testFinish(benchmark, test, inputSize, parseTime) {
- appendResult(
- "individual",
- test.title,
- "benchmark/" + benchmark.id + "/" + test.file,
- inputSize,
- parseTime
- );
- },
-
- benchmarkStart(benchmark) {
- resultsTable.append(
- ""
- + ""
- + 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");
+const Runner = require( "./runner.js" );
+const benchmarks = require( "./benchmarks.js" );
+
+$( "#run" ).click( () => {
+
+ // Results Table Manipulation
+
+ const resultsTable = $( "#results-table" );
+
+ function appendResult( klass, title, url, inputSize, parseTime ) {
+
+ const KB = 1024;
+ const MS_IN_S = 1000;
+
+ resultsTable.append(
+ ""
+ + ""
+ + ( url !== null ? "" : "" )
+ + title
+ + ( url !== null ? "" : "" )
+ + " | "
+ + ""
+ + ""
+ + ( inputSize / KB ).toFixed( 2 )
+ + ""
+ + " kB"
+ + " | "
+ + ""
+ + ""
+ + parseTime.toFixed( 2 )
+ + ""
+ + " ms"
+ + " | "
+ + ""
+ + ""
+ + ( ( inputSize / KB ) / ( parseTime / MS_IN_S ) ).toFixed( 2 )
+ + ""
+ + " 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.
+
+ const runCount = parseInt( $( "#run-count" ).val(), 10 );
+ const options = {
+ cache: $( "#cache" ).is( ":checked" ),
+ optimize: $( "#optimize" ).val()
+ };
+
+ if ( isNaN( runCount ) || runCount <= 0 ) {
+
+ alert( "Number of runs must be a positive integer." );
+
+ return;
+
}
- });
-});
-$(document).ready(() => {
- $("#run").focus();
-});
+ Runner.run( benchmarks, runCount, options, {
+ readFile( file ) {
+
+ return $.ajax( {
+ type: "GET",
+ url: "benchmark/" + file,
+ dataType: "text",
+ async: false
+ } ).responseText;
+
+ },
+
+ testStart() {
+ // Nothing to do.
+ },
+
+ testFinish( benchmark, test, inputSize, parseTime ) {
+
+ appendResult(
+ "individual",
+ test.title,
+ "benchmark/" + benchmark.id + "/" + test.file,
+ inputSize,
+ parseTime
+ );
+
+ },
+
+ benchmarkStart( benchmark ) {
+
+ resultsTable.append(
+ ""
+ + ""
+ + 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() );
diff --git a/test/benchmark/run b/test/benchmark/run
index c4c16a3..278c8ab 100644
--- a/test/benchmark/run
+++ b/test/benchmark/run
@@ -2,195 +2,251 @@
"use strict";
-let Runner = require("./runner.js");
-let benchmarks = require("./benchmarks.js");
-let fs = require("fs");
+const Runner = require( "./runner.js" );
+const benchmarks = require( "./benchmarks.js" );
+const fs = require( "fs" );
+const path = require( "path" );
// Results Table Manipulation
-function dup(text, count) {
- let result = "";
+function dup( text, count ) {
- for (let i = 1; i <= count; i++) {
- result += text;
- }
+ let result = "";
+
+ for ( let i = 1; i <= count; i++ ) result += text;
+
+ return result;
- return result;
}
-function padLeft(text, length) {
- return dup(" ", length - text.length) + text;
+function padLeft( text, length ) {
+
+ return dup( " ", length - text.length ) + text;
+
}
-function padRight(text, length) {
- return text + dup(" ", length - text.length);
+function padRight( text, length ) {
+
+ return text + dup( " ", length - text.length );
+
}
-function center(text, length) {
- let padLength = (length - text.length) / 2;
+function center( text, length ) {
+
+ const padLength = ( length - text.length ) / 2;
+
+ return dup( " ", Math.floor( padLength ) )
+ + text
+ + dup( " ", Math.ceil( padLength ) );
- return dup(" ", Math.floor(padLength))
- + text
- + dup(" ", Math.ceil(padLength));
}
function writeTableHeader() {
- console.log("┌─────────────────────────────────────┬───────────┬────────────┬──────────────┐");
- console.log("│ Test │ Inp. size │ Avg. time │ Avg. speed │");
+
+ console.log( "┌─────────────────────────────────────┬───────────┬────────────┬──────────────┐" );
+ console.log( "│ Test │ Inp. size │ Avg. time │ Avg. speed │" );
+
}
-function writeHeading(heading) {
- console.log("├─────────────────────────────────────┴───────────┴────────────┴──────────────┤");
- console.log("│ " + center(heading, 75) + " │");
- console.log("├─────────────────────────────────────┬───────────┬────────────┬──────────────┤");
+function writeHeading( heading ) {
+
+ console.log( "├─────────────────────────────────────┴───────────┴────────────┴──────────────┤" );
+ console.log( "│ " + center( heading, 75 ) + " │" );
+ console.log( "├─────────────────────────────────────┬───────────┬────────────┬──────────────┤" );
+
}
-function writeResult(title, inputSize, parseTime) {
- const KB = 1024;
- const MS_IN_S = 1000;
+function writeResult( title, inputSize, parseTime ) {
+
+ const KB = 1024;
+ const MS_IN_S = 1000;
+
+ console.log(
+ "│ " +
+ padRight( title, 35 ) +
+ " │ " +
+ padLeft( ( inputSize / KB ).toFixed( 2 ), 6 ) +
+ " kB │ " +
+ padLeft( parseTime.toFixed( 2 ), 7 ) +
+ " ms │ " +
+ padLeft( ( ( inputSize / KB ) / ( parseTime / MS_IN_S ) ).toFixed( 2 ), 7 ) +
+ " kB/s │"
+ );
- console.log("│ "
- + padRight(title, 35)
- + " │ "
- + padLeft((inputSize / KB).toFixed(2), 6)
- + " kB │ "
- + padLeft(parseTime.toFixed(2), 7)
- + " ms │ "
- + padLeft(((inputSize / KB) / (parseTime / MS_IN_S)).toFixed(2), 7)
- + " kB/s │"
- );
}
function writeSeparator() {
- console.log("├─────────────────────────────────────┼───────────┼────────────┼──────────────┤");
+
+ console.log( "├─────────────────────────────────────┼───────────┼────────────┼──────────────┤" );
+
}
function writeTableFooter() {
- console.log("└─────────────────────────────────────┴───────────┴────────────┴──────────────┘");
+
+ console.log( "└─────────────────────────────────────┴───────────┴────────────┴──────────────┘" );
+
}
// Helpers
function printHelp() {
- console.log("Usage: run [options]");
- console.log("");
- console.log("Runs PEG.js benchmark suite.");
- console.log("");
- console.log("Options:");
- console.log(" -n, --run-count number of runs (default: 10)");
- console.log(" --cache make tested parsers cache results");
- console.log(" -o, --optimize select optimization for speed or size (default:");
- console.log(" speed)");
+
+ console.log( "Usage: run [options]" );
+ console.log( "" );
+ console.log( "Runs PEG.js benchmark suite." );
+ console.log( "" );
+ console.log( "Options:" );
+ console.log( " -n, --run-count number of runs (default: 10)" );
+ console.log( " --cache make tested parsers cache results" );
+ console.log( " -o, --optimize select optimization for speed or size (default:" );
+ console.log( " speed)" );
+
}
function exitSuccess() {
- process.exit(0);
+
+ process.exit( 0 );
+
}
function exitFailure() {
- process.exit(1);
+
+ process.exit( 1 );
+
}
-function abort(message) {
- console.error(message);
- exitFailure();
+function abort( message ) {
+
+ console.error( message );
+ exitFailure();
+
}
// Arguments
-let args = process.argv.slice(2); // Trim "node" and the script path.
+const args = process.argv.slice( 2 ); // Trim "node" and the script path.
+
+function isOption( arg ) {
+
+ return ( /^-/ ).test( arg );
-function isOption(arg) {
- return (/^-/).test(arg);
}
function nextArg() {
- args.shift();
+
+ args.shift();
+
}
// Main
let runCount = 10;
-let options = {
- cache: false,
- optimize: "speed"
+const options = {
+ cache: false,
+ optimize: "speed"
};
-while (args.length > 0 && isOption(args[0])) {
- switch (args[0]) {
- case "-n":
- case "--run-count":
- nextArg();
- if (args.length === 0) {
- abort("Missing parameter of the -n/--run-count option.");
- }
- runCount = parseInt(args[0], 10);
- if (isNaN(runCount) || runCount <= 0) {
- abort("Number of runs must be a positive integer.");
- }
- break;
-
- case "--cache":
- options.cache = true;
- break;
-
- case "-o":
- case "--optimize":
- nextArg();
- if (args.length === 0) {
- abort("Missing parameter of the -o/--optimize option.");
- }
- if (args[0] !== "speed" && args[0] !== "size") {
- abort("Optimization goal must be either \"speed\" or \"size\".");
- }
- options.optimize = args[0];
- break;
-
- case "-h":
- case "--help":
- printHelp();
- exitSuccess();
- break;
-
- default:
- abort("Unknown option: " + args[0] + ".");
- }
- nextArg();
-}
-
-if (args.length > 0) {
- abort("No arguments are allowed.");
-}
-
-Runner.run(benchmarks, runCount, options, {
- readFile(file) {
- return fs.readFileSync(__dirname + "/" + file, "utf8");
- },
-
- testStart() {
+while ( args.length > 0 && isOption( args[ 0 ] ) ) {
+
+ switch ( args[ 0 ] ) {
+
+ case "-n":
+ case "--run-count":
+ nextArg();
+ if ( args.length === 0 ) {
+
+ abort( "Missing parameter of the -n/--run-count option." );
+
+ }
+ runCount = parseInt( args[ 0 ], 10 );
+ if ( isNaN( runCount ) || runCount <= 0 ) {
+
+ abort( "Number of runs must be a positive integer." );
+
+ }
+ break;
+
+ case "--cache":
+ options.cache = true;
+ break;
+
+ case "-o":
+ case "--optimize":
+ nextArg();
+ if ( args.length === 0 ) {
+
+ abort( "Missing parameter of the -o/--optimize option." );
+
+ }
+ if ( args[ 0 ] !== "speed" && args[ 0 ] !== "size" ) {
+
+ abort( "Optimization goal must be either \"speed\" or \"size\"." );
+
+ }
+ options.optimize = args[ 0 ];
+ break;
+
+ case "-h":
+ case "--help":
+ printHelp();
+ exitSuccess();
+ break;
+
+ default:
+ abort( "Unknown option: " + args[ 0 ] + "." );
+
+ }
+ nextArg();
+
+}
+
+if ( args.length > 0 ) {
+
+ abort( "No arguments are allowed." );
+
+}
+
+Runner.run( benchmarks, runCount, options, {
+ readFile( file ) {
+
+ return fs.readFileSync( path.join( __dirname, file ), "utf8" );
+
+ },
+
+ testStart() {
// Nothing to do.
- },
-
- testFinish(benchmark, test, inputSize, parseTime) {
- writeResult(test.title, inputSize, parseTime);
- },
-
- benchmarkStart(benchmark) {
- writeHeading(benchmark.title);
- },
-
- benchmarkFinish(benchmark, inputSize, parseTime) {
- writeSeparator();
- writeResult(benchmark.title + " total", inputSize, parseTime);
- },
-
- start() {
- writeTableHeader();
- },
-
- finish(inputSize, parseTime) {
- writeSeparator();
- writeResult("Total", inputSize, parseTime);
- writeTableFooter();
- }
-});
+ },
+
+ testFinish( benchmark, test, inputSize, parseTime ) {
+
+ writeResult( test.title, inputSize, parseTime );
+
+ },
+
+ benchmarkStart( benchmark ) {
+
+ writeHeading( benchmark.title );
+
+ },
+
+ benchmarkFinish( benchmark, inputSize, parseTime ) {
+
+ writeSeparator();
+ writeResult( benchmark.title + " total", inputSize, parseTime );
+
+ },
+
+ start() {
+
+ writeTableHeader();
+
+ },
+
+ finish( inputSize, parseTime ) {
+
+ writeSeparator();
+ writeResult( "Total", inputSize, parseTime );
+ writeTableFooter();
+
+ }
+} );
diff --git a/test/benchmark/runner.js b/test/benchmark/runner.js
index 7b49506..7a6f68c 100644
--- a/test/benchmark/runner.js
+++ b/test/benchmark/runner.js
@@ -1,118 +1,150 @@
"use strict";
-/* global setTimeout */
+const peg = require( "../../lib/peg" );
-let peg = require("../../lib/peg");
+const Runner = {
+ run( benchmarks, runCount, options, callbacks ) {
-let Runner = {
- run(benchmarks, runCount, options, callbacks) {
- // Queue
+ // Queue
- let Q = {
- functions: [],
+ const Q = {
+ functions: [],
- add(f) {
- this.functions.push(f);
- },
+ add( f ) {
- run() {
- if (this.functions.length > 0) {
- this.functions.shift()();
+ 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.
+
+ const state = {};
+
+ function initialize() {
+
+ callbacks.start();
+
+ state.totalInputSize = 0;
+ state.totalParseTime = 0;
- // 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 benchmarkInitializer( benchmark ) {
+
+ return function () {
+
+ callbacks.benchmarkStart( benchmark );
- function testRunner(benchmark, test) {
- return function() {
- callbacks.testStart(benchmark, test);
+ state.parser = peg.generate(
+ callbacks.readFile( "../../examples/" + benchmark.id + ".pegjs" ),
+ options
+ );
+ state.benchmarkInputSize = 0;
+ state.benchmarkParseTime = 0;
- 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);
+ function testRunner( benchmark, test ) {
- state.benchmarkInputSize += input.length;
- state.benchmarkParseTime += averageParseTime;
- };
- }
+ return function () {
- function benchmarkFinalizer(benchmark) {
- return function() {
- callbacks.benchmarkFinish(
- benchmark,
- state.benchmarkInputSize,
- state.benchmarkParseTime
- );
-
- state.totalInputSize += state.benchmarkInputSize;
- state.totalParseTime += state.benchmarkParseTime;
- };
- }
+ callbacks.testStart( benchmark, test );
- function finalize() {
- callbacks.finish(state.totalInputSize, state.totalParseTime);
- }
+ const input = callbacks.readFile( benchmark.id + "/" + test.file );
+
+ let parseTime = 0;
+ for ( let i = 0; i < runCount; i++ ) {
+
+ const t = ( new Date() ).getTime();
+ state.parser.parse( input );
+ parseTime += ( new Date() ).getTime() - t;
- // Main
+ }
+ const averageParseTime = parseTime / runCount;
- 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);
+ callbacks.testFinish( benchmark, test, input.length, averageParseTime );
- Q.run();
- }
+ 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/test/impact b/test/impact
index ebbaa3b..8b59113 100644
--- a/test/impact
+++ b/test/impact
@@ -1,150 +1,179 @@
#!/usr/bin/env node
-/* eslint camelcase:0, max-len:0, one-var:0 */
-
//
// Measures impact of a Git commit (or multiple commits) on generated parsers
// speed and size. Makes sense to use only on PEG.js git repository checkout.
//
+/* eslint prefer-const: 0 */
+
"use strict";
-let child_process = require("child_process");
-let fs = require("fs");
-let os = require("os");
-let path = require("path");
-let glob = require("glob");
+const child_process = require( "child_process" );
+const fs = require( "fs" );
+const os = require( "os" );
+const path = require( "path" );
+const dedent = require( "dedent" );
+const glob = require( "glob" );
// Current Working Directory
-let cwd = path.join(__dirname, "..");
-
-if (process.cwd() !== cwd) {
- process.chdir(cwd);
-}
+const cwd = path.join( __dirname, ".." );
+if ( process.cwd() !== cwd ) process.chdir( cwd );
// Execution Files
let PEGJS_BIN = "bin/peg.js";
let BENCHMARK_BIN = "test/benchmark/run";
-if (!fs.existsSync(PEGJS_BIN)) {
- PEGJS_BIN = "bin/pegjs";
+if ( ! fs.existsSync( PEGJS_BIN ) ) {
+
+ PEGJS_BIN = "bin/pegjs";
+
}
-if (!fs.existsSync(BENCHMARK_BIN)) {
- BENCHMARK_BIN = "benchmark/run";
+if ( ! fs.existsSync( BENCHMARK_BIN ) ) {
+
+ BENCHMARK_BIN = "benchmark/run";
+
}
// Utils
-let print = console.log;
+function echo( message ) {
+
+ process.stdout.write( message );
-function echo(message) {
- process.stdout.write(message);
}
-function exec(command) {
- return child_process.execSync(command, { encoding: "utf8" });
+function exec( command ) {
+
+ return child_process.execSync( command, { encoding: "utf8" } );
+
}
-function prepare(commit) {
- exec(`git checkout --quiet "${commit}"`);
+function prepare( commit ) {
+
+ exec( `git checkout --quiet "${ commit }"` );
+
}
function runBenchmark() {
- return parseFloat(
- exec("node " + BENCHMARK_BIN)
- // Split by table seprator, reverse and return the total bytes per second
- .split("│")
- .reverse()[1]
- // Trim the whitespaces and remove ` kB/s` from the end
- .trim()
- .slice(0, -5)
- );
+
+ return parseFloat(
+ exec( "node " + BENCHMARK_BIN )
+
+ // Split by table seprator, reverse and return the total bytes per second
+ .split( "│" )
+ .reverse()[ 1 ]
+
+ // Trim the whitespaces and remove ` kB/s` from the end
+ .trim()
+ .slice( 0, -5 )
+ );
+
}
function measureSpeed() {
- return (runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() / 5).toFixed(2);
+
+ return ( runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() / 5 ).toFixed( 2 );
+
}
function measureSize() {
- let size = 0;
- glob.sync("examples/*.pegjs")
- .forEach(example => {
- exec(`node ${PEGJS_BIN} ${example}`);
- example = example.slice(0, -5) + "js";
- size += fs.statSync(example).size;
- fs.unlinkSync(example);
- });
+ let size = 0;
+
+ glob.sync( "examples/*.pegjs" )
+ .forEach( example => {
+
+ exec( `node ${ PEGJS_BIN } ${ example }` );
+ example = example.slice( 0, -5 ) + "js";
+ size += fs.statSync( example ).size;
+ fs.unlinkSync( example );
+
+ } );
+
+ return size;
- return size;
}
-function difference($1, $2) {
- return (($2 / $1 - 1) * 100).toFixed(4);
+function difference( $1, $2 ) {
+
+ return ( ( $2 / $1 - 1 ) * 100 ).toFixed( 4 );
+
}
// Prepare
-let argv = process.argv.slice(2);
+const argv = process.argv.slice( 2 );
let commit_before, commit_after;
-if (argv.length === 1) {
- commit_before = argv[0] + "~1";
- commit_after = argv[0];
-} else if (argv.length === 2) {
- commit_before = argv[0];
- commit_after = argv[1];
+if ( argv.length === 1 ) {
+
+ commit_before = argv[ 0 ] + "~1";
+ commit_after = argv[ 0 ];
+
+} else if ( argv.length === 2 ) {
+
+ commit_before = argv[ 0 ];
+ commit_after = argv[ 1 ];
+
} else {
- print("Usage:");
- print("");
- print(" test/impact ");
- print(" test/impact ");
- print("");
- print("Measures impact of a Git commit (or multiple commits) on generated parsers'");
- print("speed and size. Makes sense to use only on PEG.js Git repository checkout.");
- print("");
- process.exit(1);
+
+ console.log( dedent`
+
+ Usage:
+
+ test/impact
+ test/impact
+
+ Measures impact of a Git commit (or multiple commits) on generated parser's
+ speed and size. Makes sense to use only on PEG.js Git repository checkout.
+
+ ` );
+ process.exit( 1 );
+
}
// Measure
-let branch = exec("git rev-parse --abbrev-ref HEAD");
+const branch = exec( "git rev-parse --abbrev-ref HEAD" );
let speed1, size1, speed2, size2;
-echo(`Measuring commit ${commit_before}...`);
-prepare(commit_before);
+echo( `Measuring commit ${ commit_before }...` );
+prepare( commit_before );
speed1 = measureSpeed();
size1 = measureSize();
-echo(" OK" + os.EOL);
+echo( " OK" + os.EOL );
-echo(`Measuring commit ${commit_after}...`);
-prepare(commit_after);
+echo( `Measuring commit ${ commit_after }...` );
+prepare( commit_after );
speed2 = measureSpeed();
size2 = measureSize();
-echo(" OK" + os.EOL);
+echo( " OK" + os.EOL );
// Finish
-prepare(branch);
+prepare( branch );
+
+console.log( dedent`
+
+ test/impact ${ commit_before } ${ commit_after }
-print(`
-test/impact ${commit_before} ${commit_after}
+ Speed impact
+ ------------
+ Before: ${ speed1 } kB/s
+ After: ${ speed2 } kB/s
+ Difference: ${ difference( parseFloat( speed1 ), parseFloat( speed2 ) ) }%
- Speed impact
- ------------
- Before: ${speed1} kB/s
- After: ${speed2} kB/s
- Difference: ${difference(parseFloat(speed1), parseFloat(speed2))}%
+ Size impact
+ -----------
+ Before: ${ size1 } b
+ After: ${ size2 } b
+ Difference: ${ difference( size1, size2 ) }%
- Size impact
- -----------
- Before: ${size1} b
- After: ${size2} b
- Difference: ${difference(size1, size2)}%
+ - Measured by /test/impact with Node.js ${ process.version }
+ - Your system: ${ os.type() } ${ os.release() } ${ os.arch() }.
-- Measured by /test/impact with Node.js ${process.version}
-- Your system: ${os.type()} ${os.release()} ${os.arch()}.
-`);
+` );
diff --git a/test/server/run b/test/server/run
index e2bac59..cc3e0e9 100644
--- a/test/server/run
+++ b/test/server/run
@@ -2,31 +2,35 @@
"use strict";
-let babelify = require("babelify");
-let browserify = require("browserify");
-let express = require("express");
-let glob = require("glob");
-let logger = require("morgan");
-
-let app = express();
-
-app.use(logger("dev"));
-app.use(express.static(__dirname));
-app.use("/benchmark", express.static(`${__dirname}/../benchmark`));
-app.use("/examples", express.static(`${__dirname}/../../examples`));
-
-app.get("/:dir/bundle.js", (req, res) => {
- browserify(glob.sync(
- `${__dirname}/../${req.params.dir}/**/*.js`
- ))
- .transform(babelify, {
- presets: "es2015",
- compact: false
- })
- .bundle()
- .pipe(res);
-});
-
-app.listen(8000, () => {
- console.log("Test server running at: http://localhost:8000/");
-});
+const babelify = require( "babelify" );
+const browserify = require( "browserify" );
+const express = require( "express" );
+const glob = require( "glob" );
+const logger = require( "morgan" );
+
+const app = express();
+
+app.use( logger( "dev" ) );
+app.use( express.static( __dirname ) );
+app.use( "/benchmark", express.static( `${ __dirname }/../benchmark` ) );
+app.use( "/examples", express.static( `${ __dirname }/../../examples` ) );
+
+app.get( "/:dir/bundle.js", ( req, res ) => {
+
+ browserify( glob.sync(
+ `${ __dirname }/../${ req.params.dir }/**/*.js`
+ ) )
+ .transform( babelify, {
+ presets: "es2015",
+ compact: false
+ } )
+ .bundle()
+ .pipe( res );
+
+} );
+
+app.listen( 8000, () => {
+
+ console.log( "Test server running at: http://localhost:8000/" );
+
+} );
diff --git a/test/spec/api/generated-parser-api.spec.js b/test/spec/api/generated-parser-api.spec.js
index 2e7e84e..63b428b 100644
--- a/test/spec/api/generated-parser-api.spec.js
+++ b/test/spec/api/generated-parser-api.spec.js
@@ -1,168 +1,216 @@
"use strict";
-/* global console */
-
-let chai = require("chai");
-let peg = require("../../../lib/peg");
-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 });
- });
- });
-});
+const chai = require( "chai" );
+const peg = require( "../../../lib/peg" );
+const sinon = require( "sinon" );
+
+const expect = chai.expect;
+
+describe( "generated parser API", function () {
+
+ describe( "parse", function () {
+
+ it( "parses input", function () {
+
+ const parser = peg.generate( "start = 'a'" );
+ expect( parser.parse( "a" ) ).to.equal( "a" );
+
+ } );
+
+ it( "throws an exception on syntax error", function () {
+
+ const parser = peg.generate( "start = 'a'" );
+ expect( () => {
+
+ parser.parse( "b" );
+
+ } ).to.throw();
+
+ } );
+
+ describe( "start rule", function () {
+
+ const parser = peg.generate( `
+
+ a = 'x' { return 'a'; }
+ b = 'x' { return 'b'; }
+ c = 'x' { return 'c'; }
+
+ `, { 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 () {
+
+ const parser = peg.generate( `
+
+ start = a / b
+ a = 'a'
+ b = 'b'
+
+ `, { trace: true } );
+
+ describe( "default tracer", function () {
+
+ it( "traces using console.log (if console is defined)", function () {
+
+ const 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 ) => {
+
+ const 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 () {
+
+ const 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 }
+ }
+ }
+ ];
+
+ const tracer = { trace: sinon.spy() };
+ parser.parse( "b", { tracer: tracer } );
+
+ expect( tracer.trace.callCount ).to.equal( events.length );
+ events.forEach( ( event, index ) => {
+
+ const call = tracer.trace.getCall( index );
+ expect( call.calledWithExactly( event ) ).to.equal( true );
+
+ } );
+
+ } );
+
+ } );
+
+ } );
+
+ } );
+
+ it( "accepts custom options", function () {
+
+ const parser = peg.generate( "start = 'a'" );
+ parser.parse( "a", { foo: 42 } );
+
+ } );
+
+ } );
+
+} );
diff --git a/test/spec/api/pegjs-api.spec.js b/test/spec/api/pegjs-api.spec.js
index a0ad3c1..4e8e422 100644
--- a/test/spec/api/pegjs-api.spec.js
+++ b/test/spec/api/pegjs-api.spec.js
@@ -1,206 +1,319 @@
"use strict";
-let chai = require("chai");
-let peg = require("../../../lib/peg");
-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");
-
- it("throws an error on missing rule", function() {
- expect(() => peg.generate(grammar, {
- allowedStartRules: ["missing"]
- })).to.throw();
- });
-
- // 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.
+const chai = require( "chai" );
+const peg = require( "../../../lib/peg" );
+const sinon = require( "sinon" );
- // The |plugins| option is tested in plugin API tests.
+const expect = chai.expect;
- it("accepts custom options", function() {
- peg.generate("start = 'a'", { foo: 42 });
- });
- });
-});
+describe( "PEG.js API", function () {
+
+ describe( "generate", function () {
+
+ it( "generates a parser", function () {
+
+ const 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 () {
+
+ const grammar = `
+
+ a = 'x'
+ b = 'x'
+ c = 'x'
+
+ `;
+
+ it( "throws an error on missing rule", function () {
+
+ expect( () => {
+
+ peg.generate( grammar, { allowedStartRules: [ "missing" ] } );
+
+ } ).to.throw();
+
+ } );
+
+ // 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const grammar = `
+
+ { var n = 0; }
+ start = (a 'b') / (a 'c') { return n; }
+ a = 'a' { n++; }
+
+ `;
+
+ describe( "when |cache| is not set", function () {
+
+ it( "generated parser doesn't cache intermediate parse results", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( grammar, { cache: true } );
+ expect( parser.parse( "ac" ) ).to.equal( 1 );
+
+ } );
+
+ } );
+
+ } );
+
+ describe( "tracing", function () {
+
+ const grammar = "start = 'a'";
+
+ describe( "when |trace| is not set", function () {
+
+ it( "generated parser doesn't trace", function () {
+
+ const parser = peg.generate( grammar );
+ const 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 () {
+
+ const parser = peg.generate( grammar, { trace: false } );
+ const 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 () {
+
+ const parser = peg.generate( grammar, { trace: true } );
+ const 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 () {
+
+ const grammar = "start = 'a'";
+
+ describe( "when |output| is not set", function () {
+
+ it( "returns generated parser object", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 |plugins| option is tested in plugin API tests.
+
+ it( "accepts custom options", function () {
+
+ peg.generate( "start = 'a'", { foo: 42 } );
+
+ } );
+
+ } );
+
+} );
diff --git a/test/spec/api/plugin-api.spec.js b/test/spec/api/plugin-api.spec.js
index 93342df..31eae10 100644
--- a/test/spec/api/plugin-api.spec.js
+++ b/test/spec/api/plugin-api.spec.js
@@ -1,128 +1,185 @@
"use strict";
-let chai = require("chai");
-let peg = require("../../../lib/peg");
-
-let expect = chai.expect;
-
-describe("plugin API", function() {
- describe("use", function() {
- let grammar = "start = 'a'";
-
- it("is called for each plugin", function() {
- let pluginsUsed = [false, false, false];
- let plugins = [
- { use() { pluginsUsed[0] = true; } },
- { use() { pluginsUsed[1] = true; } },
- { use() { pluginsUsed[2] = true; } }
- ];
-
- peg.generate(grammar, { plugins: plugins });
-
- expect(pluginsUsed).to.deep.equal([true, true, true]);
- });
-
- it("receives configuration", function() {
- let plugin = {
- use(config) {
- expect(config).to.be.an("object");
-
- expect(config.parser).to.be.an("object");
- expect(config.parser.parse("start = 'a'")).to.be.an("object");
-
- expect(config.passes).to.be.an("object");
-
- expect(config.passes.check).to.be.an("array");
- config.passes.check.forEach(pass => {
- expect(pass).to.be.a("function");
- });
-
- expect(config.passes.transform).to.be.an("array");
- config.passes.transform.forEach(pass => {
- expect(pass).to.be.a("function");
- });
-
- expect(config.passes.generate).to.be.an("array");
- config.passes.generate.forEach(pass => {
- expect(pass).to.be.a("function");
- });
- }
- };
-
- peg.generate(grammar, { plugins: [plugin] });
- });
-
- it("receives options", function() {
- let plugin = {
- use(config, options) {
- expect(options).to.equal(generateOptions);
- }
- };
- let generateOptions = { plugins: [plugin], foo: 42 };
-
- peg.generate(grammar, generateOptions);
- });
-
- it("can replace parser", function() {
- let plugin = {
- use(config) {
- let parser = peg.generate([
- "start = .* {",
- " return {",
- " type: 'grammar',",
- " rules: [",
- " {",
- " type: 'rule',",
- " name: 'start',",
- " expression: { type: 'literal', value: text(), ignoreCase: false }",
- " }",
- " ]",
- " };",
- "}"
- ].join("\n"));
-
- config.parser = parser;
- }
- };
- let parser = peg.generate("a", { plugins: [plugin] });
-
- expect(parser.parse("a")).to.equal("a");
- });
-
- it("can change compiler passes", function() {
- let plugin = {
- use(config) {
- function pass(ast) {
- ast.code = "({ parse: function() { return 42; } })";
- }
-
- config.passes.generate = [pass];
- }
- };
- let parser = peg.generate(grammar, { plugins: [plugin] });
-
- expect(parser.parse("a")).to.equal(42);
- });
-
- it("can change options", function() {
- let grammar = [
- "a = 'x'",
- "b = 'x'",
- "c = 'x'"
- ].join("\n");
- let plugin = {
- use(config, options) {
- options.allowedStartRules = ["b", "c"];
- }
- };
- let parser = peg.generate(grammar, {
- allowedStartRules: ["a"],
- plugins: [plugin]
- });
-
- expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
- expect(parser.parse("x", { startRule: "b" })).to.equal("x");
- expect(parser.parse("x", { startRule: "c" })).to.equal("x");
- });
- });
-});
+const chai = require( "chai" );
+const peg = require( "../../../lib/peg" );
+
+const expect = chai.expect;
+
+describe( "plugin API", function () {
+
+ describe( "use", function () {
+
+ const grammar = "start = 'a'";
+
+ it( "is called for each plugin", function () {
+
+ const pluginsUsed = [ false, false, false ];
+ const 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 () {
+
+ const 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 () {
+
+ const generateOptions = {
+ plugins: [ {
+ use( config, options ) {
+
+ expect( options ).to.equal( generateOptions );
+
+ }
+ } ],
+ foo: 42
+ };
+
+ peg.generate( grammar, generateOptions );
+
+ } );
+
+ it( "can replace parser", function () {
+
+ const plugin = {
+ use( config ) {
+
+ config.parser = peg.generate( `
+
+ start = .* {
+ return {
+ type: 'grammar',
+ rules: [{
+ type: 'rule',
+ name: 'start',
+ expression: {
+ type: 'literal',
+ value: text(),
+ ignoreCase: false
+ }
+ }]
+ };
+ }
+
+ ` );
+
+ }
+ };
+
+ const parser = peg.generate( "a", { plugins: [ plugin ] } );
+ expect( parser.parse( "a" ) ).to.equal( "a" );
+
+ } );
+
+ it( "can change compiler passes", function () {
+
+ const plugin = {
+ use( config ) {
+
+ function pass( ast ) {
+
+ ast.code = "({ parse: function() { return 42; } })";
+
+ }
+
+ config.passes.generate = [ pass ];
+
+ }
+ };
+
+ const parser = peg.generate( grammar, { plugins: [ plugin ] } );
+ expect( parser.parse( "a" ) ).to.equal( 42 );
+
+ } );
+
+ it( "can change options", function () {
+
+ const grammar = `
+
+ a = 'x'
+ b = 'x'
+ c = 'x'
+
+ `;
+ const plugin = {
+ use( config, options ) {
+
+ options.allowedStartRules = [ "b", "c" ];
+
+ }
+ };
+
+ const 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/spec/behavior/generated-parser-behavior.spec.js b/test/spec/behavior/generated-parser-behavior.spec.js
index aeea59b..472ee1f 100644
--- a/test/spec/behavior/generated-parser-behavior.spec.js
+++ b/test/spec/behavior/generated-parser-behavior.spec.js
@@ -1,1574 +1,2028 @@
"use strict";
-/* global console */
+const chai = require( "chai" );
+const peg = require( "../../../lib/peg" );
+const sinon = require( "sinon" );
-let chai = require("chai");
-let peg = require("../../../lib/peg");
-let sinon = require("sinon");
+const expect = chai.expect;
-let expect = chai.expect;
+describe( "generated parser behavior", function () {
-describe("generated parser behavior", function() {
- function varyOptimizationOptions(block) {
- function clone(object) {
- let result = {};
+ function varyOptimizationOptions( block ) {
- Object.keys(object).forEach(key => {
- result[key] = object[key];
- });
+ function clone( object ) {
- return result;
- }
+ const 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");
- }
+ Object.keys( object ).forEach( key => {
- 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
- );
+ result[ key ] = object[ key ];
- 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
- );
+ return result;
- 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
- );
+ const 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 }
+ ];
- 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
- );
+ optionsVariants.forEach( variant => {
+
+ describe(
+ "with options " + chai.util.inspect( variant ),
+ function () {
- expect(parser).to.parse("ab");
- });
+ block( clone( variant ) );
- 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 }
- });
- });
-
- it("|offset| returns current start offset", function() {
- let parser = peg.generate([
- "start = [0-9]+ val:mark { return val; }",
- "mark = 'xx' { return offset(); }"
- ].join("\n"), options);
-
- expect(parser).to.parse("0123456xx", 7);
- });
-
- it("|range| returns current range", function() {
- let parser = peg.generate([
- "start = [0-9]+ val:mark { return val; }",
- "mark = 'xx' { return range(); }"
- ].join("\n"), options);
-
- expect(parser).to.parse("0123456xx", [7, 9]);
- });
- });
- });
-
- 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
- );
+ function withConsoleStub( block ) {
- expect(parser).to.parse("a");
- });
+ if ( typeof console === "object" ) sinon.stub( console, "log" );
- it("cannot access variable defined by labeled predicate element", function() {
- let parser = peg.generate(
- "start = 'a' b:!{ return b !== undefined; } 'c'",
- options
- );
+ try {
- expect(parser).to.failToParse("ac");
- });
+ return block();
- it("cannot access variables defined by following labeled elements", function() {
- let parser = peg.generate(
- "start = !{ return a !== 'a'; } a:'a'",
- options
- );
+ } finally {
- 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
- );
+ if ( typeof console === "object" ) console.log.restore();
- 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");
- });
+ function helpers( chai, utils ) {
- it("cannot access variables defined by following labeled elements", function() {
- let parser = peg.generate(
- "start = ('a' !{ return b !== 'b'; }) b:'b'",
- options
- );
+ const Assertion = chai.Assertion;
- 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
- );
+ Assertion.addMethod( "parse", function ( input, expected, options ) {
- expect(parser).to.parse("a", { a: 42 }, { a: 42 });
- });
+ options = typeof options !== "undefined" ? options : {};
- it("|text| returns text matched by the expression", function() {
- let parser = peg.generate(
- "start = 'a' { return text(); }",
- options
+ const result = withConsoleStub( () =>
+ utils.flag( this, "object" ).parse( input, 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 }
- }
- });
- });
+ if ( typeof expected !== "undefined" ) {
- it("reports position correctly in the middle of input", function() {
- let parser = peg.generate("start = 'a'", options);
+ 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" )
+ );
- 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);
+ } );
+
+ Assertion.addMethod( "failToParse", function ( input, props, options ) {
+
+ options = typeof options !== "undefined" ? options : {};
+
+ let passed, result;
+
+ try {
+
+ result = withConsoleStub( () =>
+ utils.flag( this, "object" ).parse( input, options )
+ );
+ passed = true;
+
+ } catch ( e ) {
+
+ result = e;
+ passed = false;
- 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 }
+ 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 && typeof props !== "undefined" ) {
+
+ Object.keys( props ).forEach( key => {
+
+ new Assertion( result ).to.have.property( key )
+ .that.is.deep.equal( props[ key ] );
+
+ } );
+
}
- });
- expect(parser).to.failToParse("1\r\nx", { // Windows
- location: {
- start: { offset: 3, line: 2, column: 1 },
- end: { offset: 4, line: 2, column: 2 }
+
+ } );
+
+ }
+
+ // 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 );
+
+ } );
+
}
- });
- });
- });
- });
-
- // 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*)"
- );
- });
- });
- });
-});
+
+ describe( "when the expression matches", function () {
+
+ it( "returns its match result", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = ''", options );
+
+ expect( parser ).to.parse( "" );
+
+ } );
+
+ it( "matches one-character literals", function () {
+
+ const parser = peg.generate( "start = 'a'", options );
+
+ expect( parser ).to.parse( "a" );
+ expect( parser ).to.failToParse( "b" );
+
+ } );
+
+ it( "matches multi-character literals", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = 'a'", options );
+
+ expect( parser ).to.parse( "a", "a" );
+
+ } );
+
+ it( "consumes the matched text", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = []", options );
+
+ expect( parser ).to.failToParse( "a" );
+
+ } );
+
+ it( "matches classes with a character list", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = [a]", options );
+
+ expect( parser ).to.parse( "a", "a" );
+
+ } );
+
+ it( "consumes the matched character", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = .", options );
+
+ expect( parser ).to.parse( "a", "a" );
+
+ } );
+
+ it( "consumes the matched character", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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.
+ const parser = peg.generate( "start = &{ return true; } ''", options );
+
+ expect( parser ).to.parse( "", [ void 0, "" ] );
+
+ } );
+
+ } );
+
+ describe( "when the code returns a falsey value", function () {
+
+ it( "reports match failure", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate(
+ "start = &{ return a === 'a'; } a:'a'",
+ options
+ );
+
+ expect( parser ).to.failToParse( "a" );
+
+ } );
+
+ it( "cannot access variables defined by subexpressions", function () {
+
+ const 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 => {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 }
+ } );
+
+ } );
+
+ it( "|offset| returns current start offset", function () {
+
+ const parser = peg.generate( [
+ "start = [0-9]+ val:mark { return val; }",
+ "mark = 'xx' { return offset(); }"
+ ].join( "\n" ), options );
+
+ expect( parser ).to.parse( "0123456xx", 7 );
+
+ } );
+
+ it( "|range| returns current range", function () {
+
+ const parser = peg.generate( [
+ "start = [0-9]+ val:mark { return val; }",
+ "mark = 'xx' { return range(); }"
+ ].join( "\n" ), options );
+
+ expect( parser ).to.parse( "0123456xx", [ 7, 9 ] );
+
+ } );
+
+ } );
+
+ } );
+
+ 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.
+ const parser = peg.generate( "start = !{ return false; } ''", options );
+
+ expect( parser ).to.parse( "", [ void 0, "" ] );
+
+ } );
+
+ } );
+
+ describe( "when the code returns a truthy value", function () {
+
+ it( "reports match failure", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate(
+ "start = !{ return a !== 'a'; } a:'a'",
+ options
+ );
+
+ expect( parser ).to.failToParse( "a" );
+
+ } );
+
+ it( "cannot access variables defined by subexpressions", function () {
+
+ const 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 => {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = 'a'?", options );
+
+ expect( parser ).to.parse( "a", "a" );
+
+ } );
+
+ } );
+
+ describe( "when the expression doesn't match", function () {
+
+ it( "returns |null|", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = &'a' 'a'", options );
+
+ expect( parser ).to.parse( "a", [ void 0, "a" ] );
+
+ } );
+
+ it( "resets parse position", function () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = &'a'", options );
+
+ expect( parser ).to.failToParse( "b" );
+
+ } );
+
+ it( "discards any expectations recorded when matching the expression", function () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = !'a'", options );
+
+ expect( parser ).to.failToParse( "a" );
+
+ } );
+
+ } );
+
+ describe( "when the expression doesn't match", function () {
+
+ it( "returns |undefined|", function () {
+
+ const parser = peg.generate( "start = !'a' 'b'", options );
+
+ expect( parser ).to.parse( "b", [ void 0, "b" ] );
+
+ } );
+
+ it( "resets parse position", function () {
+
+ const parser = peg.generate( "start = !'a' 'b'", options );
+
+ expect( parser ).to.parse( "b" );
+
+ } );
+
+ it( "discards any expectations recorded when matching the expression", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 => {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate(
+ "start = 'a' { return text(); }",
+ options
+ );
+
+ expect( parser ).to.parse( "a", "a" );
+
+ } );
+
+ it( "|location| returns location info of the expression", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = 'a' { return 42; }", options );
+
+ expect( parser ).to.failToParse( "b" );
+
+ } );
+
+ it( "doesn't execute the code", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = 'a'", options );
+
+ expect( parser ).to.failToParse( "ab", {
+ expected: [ { type: "end" } ]
+ } );
+
+ } );
+
+ it( "reports expectations correctly with one alternative", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = 'a'", options );
+
+ expect( parser ).to.failToParse( "", { found: null } );
+
+ } );
+
+ it( "reports found string correctly in the middle of input", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const parser = peg.generate( "start = 'a'", options );
+
+ expect( parser ).to.failToParse( "b", {
+ message: "Expected \"a\" but \"b\" found."
+ } );
+
+ } );
+
+ it( "removes duplicates from expectations", function () {
+
+ const parser = peg.generate( "start = 'a' / 'a'", options );
+
+ expect( parser ).to.failToParse( "b", {
+ message: "Expected \"a\" but \"b\" found."
+ } );
+
+ } );
+
+ it( "sorts expectations", function () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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
+ const 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
+ const 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
+ const 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/spec/unit/compiler/passes/generate-bytecode.spec.js b/test/spec/unit/compiler/passes/generate-bytecode.spec.js
index ef55023..71f33a0 100644
--- a/test/spec/unit/compiler/passes/generate-bytecode.spec.js
+++ b/test/spec/unit/compiler/passes/generate-bytecode.spec.js
@@ -1,655 +1,829 @@
"use strict";
-let chai = require("chai");
-let helpers = require("./helpers");
-let pass = require("../../../../../lib/compiler/passes/generate-bytecode");
-
-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, 39, 3, // IF_NOT_ERROR
- 18, 2, 2, 2, 22, 2, 23, 3, // *
- 15, 24, 4, // IF_NOT_ERROR
- 18, 4, 2, 2, 22, 4, 23, 5, // *
- 15, 9, 4, // IF_NOT_ERROR
- 24, 3, // * LOAD_SAVED_POS
- 26, 6, 4, 3, 2, 1, 0, // CALL <6>
- 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()"])
- );
- });
- });
-});
+const chai = require( "chai" );
+const helpers = require( "./helpers" );
+const pass = require( "../../../../../lib/compiler/passes/generate-bytecode" );
+
+chai.use( helpers );
+
+const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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, 39, 3, // IF_NOT_ERROR
+ 18, 2, 2, 2, 22, 2, 23, 3, // *
+ 15, 24, 4, // IF_NOT_ERROR
+ 18, 4, 2, 2, 22, 4, 23, 5, // *
+ 15, 9, 4, // IF_NOT_ERROR
+ 24, 3, // * LOAD_SAVED_POS
+ 26, 6, 4, 3, 2, 1, 0, // CALL <6>
+ 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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 () {
+
+ const 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/spec/unit/compiler/passes/helpers.js b/test/spec/unit/compiler/passes/helpers.js
index 0d16573..86f37e1 100644
--- a/test/spec/unit/compiler/passes/helpers.js
+++ b/test/spec/unit/compiler/passes/helpers.js
@@ -1,90 +1,112 @@
"use strict";
-let parser = require("../../../../../lib/parser");
+const parser = require( "../../../../../lib/parser" );
-module.exports = function(chai, utils) {
- let Assertion = chai.Assertion;
+module.exports = function ( chai, utils ) {
- Assertion.addMethod("changeAST", function(grammar, props, options) {
- options = options !== undefined ? options : {};
+ const Assertion = chai.Assertion;
- function matchProps(value, props) {
- function isArray(value) {
- return Object.prototype.toString.apply(value) === "[object Array]";
- }
+ Assertion.addMethod( "changeAST", function ( grammar, props, options ) {
- function isObject(value) {
- return value !== null && typeof value === "object";
- }
+ options = typeof options !== "undefined" ? options : {};
- if (isArray(props)) {
- if (!isArray(value)) { return false; }
+ function matchProps( value, props ) {
+
+ function isObject( value ) {
+
+ return value !== null && typeof value === "object";
+
+ }
+
+ if ( Array.isArray( props ) ) {
+
+ if ( ! Array.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;
+
+ const keys = Object.keys( props );
+ for ( let i = 0; i < keys.length; i++ ) {
+
+ const key = keys[ i ];
+
+ if ( ! ( key in value ) ) return false;
+ if ( ! matchProps( value[ key ], props[ key ] ) ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ return value === props;
- 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; }
+ const ast = parser.parse( grammar );
- let keys = Object.keys(props);
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
+ utils.flag( this, "object" )( ast, options );
- if (!(key in value)) { return false; }
+ 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, options ) {
+
+ options = typeof options !== "undefined" ? options : {};
+
+ const ast = parser.parse( grammar );
+
+ let passed, result;
+
+ try {
+
+ utils.flag( this, "object" )( ast, options );
+ passed = true;
+
+ } catch ( e ) {
+
+ result = e;
+ passed = 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, options) {
- options = options !== undefined ? options : {};
-
- let ast = parser.parse(grammar);
-
- let passed, result;
-
- try {
- utils.flag(this, "object")(ast, options);
- 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]);
- });
- }
- });
+ 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 && typeof props !== "undefined" ) {
+
+ Object.keys( props ).forEach( key => {
+
+ new Assertion( result )
+ .to.have.property( key )
+ .that.is.deep.equal( props[ key ] );
+
+ } );
+
+ }
+
+ } );
+
};
diff --git a/test/spec/unit/compiler/passes/remove-proxy-rules.spec.js b/test/spec/unit/compiler/passes/remove-proxy-rules.spec.js
index 46d6108..3579a7f 100644
--- a/test/spec/unit/compiler/passes/remove-proxy-rules.spec.js
+++ b/test/spec/unit/compiler/passes/remove-proxy-rules.spec.js
@@ -1,59 +1,69 @@
"use strict";
-let chai = require("chai");
-let helpers = require("./helpers");
-let pass = require("../../../../../lib/compiler/passes/remove-proxy-rules");
-
-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 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"] }
- );
- });
- });
-});
+const chai = require( "chai" );
+const helpers = require( "./helpers" );
+const pass = require( "../../../../../lib/compiler/passes/remove-proxy-rules" );
+
+chai.use( helpers );
+
+const 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 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/spec/unit/compiler/passes/report-duplicate-labels.spec.js b/test/spec/unit/compiler/passes/report-duplicate-labels.spec.js
index a69f914..a96d9bd 100644
--- a/test/spec/unit/compiler/passes/report-duplicate-labels.spec.js
+++ b/test/spec/unit/compiler/passes/report-duplicate-labels.spec.js
@@ -1,63 +1,83 @@
"use strict";
-let chai = require("chai");
-let helpers = require("./helpers");
-let pass = require("../../../../../lib/compiler/passes/report-duplicate-labels");
-
-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'");
- });
- });
-});
+const chai = require( "chai" );
+const helpers = require( "./helpers" );
+const pass = require( "../../../../../lib/compiler/passes/report-duplicate-labels" );
+
+chai.use( helpers );
+
+const 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'" );
+
+ } );
+
+ } );
+
+} );
diff --git a/test/spec/unit/compiler/passes/report-duplicate-rules.spec.js b/test/spec/unit/compiler/passes/report-duplicate-rules.spec.js
index cf619ac..d73a7cf 100644
--- a/test/spec/unit/compiler/passes/report-duplicate-rules.spec.js
+++ b/test/spec/unit/compiler/passes/report-duplicate-rules.spec.js
@@ -1,24 +1,28 @@
"use strict";
-let chai = require("chai");
-let helpers = require("./helpers");
-let pass = require("../../../../../lib/compiler/passes/report-duplicate-rules");
-
-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 }
- }
- });
- });
-});
+const chai = require( "chai" );
+const helpers = require( "./helpers" );
+const pass = require( "../../../../../lib/compiler/passes/report-duplicate-rules" );
+
+chai.use( helpers );
+
+const 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 }
+ }
+ } );
+
+ } );
+
+} );
diff --git a/test/spec/unit/compiler/passes/report-infinite-recursion.spec.js b/test/spec/unit/compiler/passes/report-infinite-recursion.spec.js
index 3d668c7..f11b939 100644
--- a/test/spec/unit/compiler/passes/report-infinite-recursion.spec.js
+++ b/test/spec/unit/compiler/passes/report-infinite-recursion.spec.js
@@ -1,119 +1,135 @@
"use strict";
-let chai = require("chai");
-let helpers = require("./helpers");
-let pass = require("../../../../../lib/compiler/passes/report-infinite-recursion");
-
-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");
- });
- });
-});
+const chai = require( "chai" );
+const helpers = require( "./helpers" );
+const pass = require( "../../../../../lib/compiler/passes/report-infinite-recursion" );
+
+chai.use( helpers );
+
+const 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" );
+
+ } );
+
+ } );
+
+} );
diff --git a/test/spec/unit/compiler/passes/report-infinite-repetition.spec.js b/test/spec/unit/compiler/passes/report-infinite-repetition.spec.js
index ede874a..c7119f8 100644
--- a/test/spec/unit/compiler/passes/report-infinite-repetition.spec.js
+++ b/test/spec/unit/compiler/passes/report-infinite-repetition.spec.js
@@ -1,99 +1,107 @@
"use strict";
-let chai = require("chai");
-let helpers = require("./helpers");
-let pass = require("../../../../../lib/compiler/passes/report-infinite-repetition");
-
-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 = .*");
- });
-});
+const chai = require( "chai" );
+const helpers = require( "./helpers" );
+const pass = require( "../../../../../lib/compiler/passes/report-infinite-repetition" );
+
+chai.use( helpers );
+
+const 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 = .*" );
+
+ } );
+
+} );
diff --git a/test/spec/unit/compiler/passes/report-undefined-rules.spec.js b/test/spec/unit/compiler/passes/report-undefined-rules.spec.js
index 082c7b7..3a0b18e 100644
--- a/test/spec/unit/compiler/passes/report-undefined-rules.spec.js
+++ b/test/spec/unit/compiler/passes/report-undefined-rules.spec.js
@@ -1,29 +1,35 @@
"use strict";
-let chai = require("chai");
-let helpers = require("./helpers");
-let pass = require("../../../../../lib/compiler/passes/report-undefined-rules");
-
-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("checks allowedStartRules", function() {
- expect(pass).to.reportError("start = 'a'", {
- message: "Start rule \"missing\" is not defined."
- }, {
- allowedStartRules: ["missing"]
- });
- });
-});
+const chai = require( "chai" );
+const helpers = require( "./helpers" );
+const pass = require( "../../../../../lib/compiler/passes/report-undefined-rules" );
+
+chai.use( helpers );
+
+const 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( "checks allowedStartRules", function () {
+
+ expect( pass ).to.reportError( "start = 'a'", {
+ message: "Start rule \"missing\" is not defined."
+ }, {
+ allowedStartRules: [ "missing" ]
+ } );
+
+ } );
+
+} );
diff --git a/test/spec/unit/parser.spec.js b/test/spec/unit/parser.spec.js
index f4a749c..6a5c72f 100644
--- a/test/spec/unit/parser.spec.js
+++ b/test/spec/unit/parser.spec.js
@@ -1,689 +1,842 @@
"use strict";
-let chai = require("chai");
-let parser = require("../../../lib/parser");
+const chai = require( "chai" );
+const parser = require( "../../../lib/parser" );
-let expect = chai.expect;
+const expect = chai.expect;
// better diagnostics for deep eq failure
chai.config.truncateThreshold = 0;
-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 }]
+describe( "PEG.js grammar parser", function () {
+
+ const literalAbcd = { type: "literal", value: "abcd", ignoreCase: false };
+ const literalEfgh = { type: "literal", value: "efgh", ignoreCase: false };
+ const literalIjkl = { type: "literal", value: "ijkl", ignoreCase: false };
+ const literalMnop = { type: "literal", value: "mnop", ignoreCase: false };
+ const semanticAnd = { type: "semantic_and", code: " code " };
+ const semanticNot = { type: "semantic_not", code: " code " };
+ const optional = { type: "optional", expression: literalAbcd };
+ const zeroOrMore = { type: "zero_or_more", expression: literalAbcd };
+ const oneOrMore = { type: "one_or_more", expression: literalAbcd };
+ const textOptional = { type: "text", expression: optional };
+ const simpleNotAbcd = { type: "simple_not", expression: literalAbcd };
+ const simpleAndOptional = { type: "simple_and", expression: optional };
+ const simpleNotOptional = { type: "simple_not", expression: optional };
+ const labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd };
+ const labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh };
+ const labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl };
+ const labeledMnop = { type: "labeled", label: "d", expression: literalMnop };
+ const labeledSimpleNot = { type: "labeled", label: "a", expression: simpleNotAbcd };
+ const sequence = {
+ type: "sequence",
+ elements: [ literalAbcd, literalEfgh, literalIjkl ]
};
- }
-
- 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);
- };
+ const sequence2 = {
+ type: "sequence",
+ elements: [ labeledAbcd, labeledEfgh ]
+ };
+ const sequence4 = {
+ type: "sequence",
+ elements: [ labeledAbcd, labeledEfgh, labeledIjkl, labeledMnop ]
+ };
+ const groupLabeled = { type: "group", expression: labeledAbcd };
+ const groupSequence = { type: "group", expression: sequence };
+ const actionAbcd = { type: "action", expression: literalAbcd, code: " code " };
+ const actionEfgh = { type: "action", expression: literalEfgh, code: " code " };
+ const actionIjkl = { type: "action", expression: literalIjkl, code: " code " };
+ const actionMnop = { type: "action", expression: literalMnop, code: " code " };
+ const actionSequence = { type: "action", expression: sequence, code: " code " };
+ const choice = {
+ type: "choice",
+ alternatives: [ literalAbcd, literalEfgh, literalIjkl ]
+ };
+ const choice2 = {
+ type: "choice",
+ alternatives: [ actionAbcd, actionEfgh ]
+ };
+ const choice4 = {
+ type: "choice",
+ alternatives: [ actionAbcd, actionEfgh, actionIjkl, actionMnop ]
+ };
+ const named = { type: "named", name: "start rule", expression: literalAbcd };
+ const ruleA = { type: "rule", name: "a", expression: literalAbcd };
+ const ruleB = { type: "rule", name: "b", expression: literalEfgh };
+ const ruleC = { type: "rule", name: "c", expression: literalIjkl };
+ const ruleStart = { type: "rule", name: "start", expression: literalAbcd };
+ const initializer = { type: "initializer", code: " code " };
+
+ function oneRuleGrammar( expression ) {
+
+ return {
+ type: "grammar",
+ initializer: null,
+ rules: [ { type: "rule", name: "start", expression: expression } ]
+ };
+
}
- function stripLeaf(node) {
- delete node.location;
+ function actionGrammar( code ) {
+
+ return oneRuleGrammar(
+ { type: "action", expression: literalAbcd, code: code }
+ );
+
}
- function stripExpression(node) {
- delete node.location;
+ function literalGrammar( value, ignoreCase ) {
+
+ return oneRuleGrammar(
+ { type: "literal", value: value, ignoreCase: ignoreCase }
+ );
- strip(node.expression);
}
- function stripChildren(property) {
- return function(node) {
- delete node.location;
+ function classGrammar( parts, inverted, ignoreCase ) {
+
+ return oneRuleGrammar( {
+ type: "class",
+ parts: parts,
+ inverted: inverted,
+ ignoreCase: ignoreCase
+ } );
- node[property].forEach(strip);
- };
}
- let strip = buildVisitor({
- grammar(node) {
- delete node.location;
+ function anyGrammar() {
- 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);
- });
-
- // Grammars without any rules are not accepted.
- it("parses Rule+", function() {
- expect("start = a").to.parseAs(ruleRefGrammar("a"));
- let grammar = ruleRefGrammar("a");
- grammar.initializer = {
- "type": "initializer",
- "code": ""
+ return oneRuleGrammar( { type: "any" } );
+
+ }
+
+ function ruleRefGrammar( name ) {
+
+ return oneRuleGrammar( { type: "rule_ref", name: name } );
+
+ }
+
+ const trivialGrammar = literalGrammar( "abcd", false );
+ const twoRuleGrammar = {
+ type: "grammar",
+ initializer: null,
+ rules: [ ruleA, ruleB ]
};
- expect("{}\nstart = a").to.parseAs(grammar);
-
- expect("").to.failToParse();
- expect("{}").to.failToParse();
- });
-
- // 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));
+
+ const stripLocation = ( function () {
+
+ let strip;
+
+ 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 );
+
+ };
+
+ }
+
+ 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 ) {
+
+ const Assertion = chai.Assertion;
+
+ Assertion.addMethod( "parseAs", function ( expected ) {
+
+ const 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 && typeof 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 );
+
+ } );
+
+ // Grammars without any rules are not accepted.
+ it( "parses Rule+", function () {
+
+ expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
+ const grammar = ruleRefGrammar( "a" );
+ grammar.initializer = {
+ "type": "initializer",
+ "code": ""
+ };
+ expect( "{}\nstart = a" ).to.parseAs( grammar );
+
+ expect( "" ).to.failToParse();
+ expect( "{}" ).to.failToParse();
+
+ } );
+
+ // 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);
- });
-
- it("reports unmatched brace", function() {
- const text = "rule = \n 'x' { y \n z";
- const errorLocation = {
- start: { offset: 13, line: 2, column: 6 },
- end: { offset: 14, line: 2, column: 7 }
- };
- expect(() => parser.parse(text))
- .to.throw("Unbalanced brace.")
- .with.property("location")
- .that.deep.equals(errorLocation);
- });
-});
+
+ } );
+
+ // 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 );
+
+ } );
+
+ it( "reports unmatched brace", function () {
+
+ const text = "rule = \n 'x' { y \n z";
+ const errorLocation = {
+ start: { offset: 13, line: 2, column: 6 },
+ end: { offset: 14, line: 2, column: 7 }
+ };
+ expect( () => parser.parse( text ) )
+ .to.throw( "Unbalanced brace." )
+ .with.property( "location" )
+ .that.deep.equals( errorLocation );
+
+ } );
+
+} );