Update code format and style

This is related to my last commit. I've updated all the JavaScript files to satisfy 'eslint-config-futagozaryuu', my eslint configuration.

I'm sure I've probally missed something, but I've run all NPM scripts and Gulp tasks, fixed any bugs that cropped up, and updated some stuff (mainly related to generated messages), so as far as I can, tell this conversion is over (I know I've probally jixed it just by saying this ;P).
master
Futago-za Ryuu 7 years ago
parent 3c6523ff83
commit e6d018a88d

@ -3,6 +3,11 @@
module.exports = { module.exports = {
"extends": "futagozaryuu/node-v4", "extends": "futagozaryuu/node-v4",
"root": true "root": true,
"rules": {
"prefer-rest-params": 0,
},
}; };

@ -1,15 +1,15 @@
"use strict"; "use strict";
let fs = require("fs"); const fs = require( "fs" );
let path = require("path"); const path = require( "path" );
let peg = require("../"); const peg = require( "../" );
// Options // Options
let inputFile = null; let inputFile = null;
let outputFile = null; let outputFile = null;
let options = { const options = {
"--": [], "--": [],
"cache": false, "cache": false,
"dependencies": {}, "dependencies": {},
@ -29,49 +29,68 @@ const OPTIMIZATION_GOALS = ["size", "speed"];
// Helpers // Helpers
function abort( message ) { function abort( message ) {
console.error( message ); console.error( message );
process.exit( 1 ); process.exit( 1 );
} }
function addExtraOptions( json ) { function addExtraOptions( json ) {
let extraOptions; let extraOptions;
try { try {
extraOptions = JSON.parse( json ); extraOptions = JSON.parse( json );
} catch ( e ) { } catch ( e ) {
if (!(e instanceof SyntaxError)) { throw e; }
if ( ! ( e instanceof SyntaxError ) ) throw e;
abort( "Error parsing JSON: " + e.message ); abort( "Error parsing JSON: " + e.message );
} }
if ( typeof extraOptions !== "object" ) { if ( typeof extraOptions !== "object" ) {
abort( "The JSON with extra options has to represent an object." ); abort( "The JSON with extra options has to represent an object." );
} }
Object Object
.keys( extraOptions ) .keys( extraOptions )
.forEach( key => { .forEach( key => {
options[ key ] = extraOptions[ key ]; options[ key ] = extraOptions[ key ];
} ); } );
} }
function formatChoicesList( list ) { function formatChoicesList( list ) {
list = list.map( entry => `"${ entry }"` ); list = list.map( entry => `"${ entry }"` );
let lastOption = list.pop(); const lastOption = list.pop();
return list.length === 0 return list.length === 0
? lastOption ? lastOption
: list.join( ", " ) + " or " + lastOption; : list.join( ", " ) + " or " + lastOption;
} }
function updateList( list, string ) { function updateList( list, string ) {
string string
.split( "," ) .split( "," )
.forEach( entry => { .forEach( entry => {
entry = entry.trim(); entry = entry.trim();
if ( list.indexOf( entry ) === -1 ) { if ( list.indexOf( entry ) === -1 ) {
list.push( entry ); list.push( entry );
} }
} ); } );
} }
// Arguments // Arguments
@ -79,23 +98,29 @@ function updateList(list, string) {
let args = process.argv.slice( 2 ); let args = process.argv.slice( 2 );
function nextArg( option ) { function nextArg( option ) {
if ( args.length === 0 ) { 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 // Parse Arguments
while ( args.length > 0 ) { while ( args.length > 0 ) {
let json, mod; let json, mod;
let argument = args.shift(); let argument = args.shift();
if ( argument.indexOf( "-" ) === 0 && argument.indexOf( "=" ) > 1 ) { if ( argument.indexOf( "-" ) === 0 && argument.indexOf( "=" ) > 1 ) {
argument = argument.split( "=" ); argument = argument.split( "=" );
args.unshift( argument.length > 2 ? argument.slice( 1 ) : argument[ 1 ] ); args.unshift( argument.length > 2 ? argument.slice( 1 ) : argument[ 1 ] );
argument = argument[ 0 ]; argument = argument[ 0 ];
} }
switch ( argument ) { switch ( argument ) {
@ -107,9 +132,7 @@ while (args.length > 0) {
case "-a": case "-a":
case "--allowed-start-rules": case "--allowed-start-rules":
if (!options.allowedStartRules) { if ( ! options.allowedStartRules ) options.allowedStartRules = [];
options.allowedStartRules = [];
}
updateList( options.allowedStartRules, nextArg( "--allowed-start-rules" ) ); updateList( options.allowedStartRules, nextArg( "--allowed-start-rules" ) );
break; break;
@ -124,14 +147,11 @@ while (args.length > 0) {
case "-d": case "-d":
case "--dependency": case "--dependency":
argument = nextArg( "-d/--dependency" ); argument = nextArg( "-d/--dependency" );
if (argument.indexOf(":") === -1) {
mod = [argument, argument];
} else {
mod = argument.split( ":" ); mod = argument.split( ":" );
if (mod.length > 2) {
mod[1] = mod.slice(1); if ( mod.length === 1 ) mod = [ argument, argument ];
} else if ( mod.length > 2 ) mod[ 1 ] = mod.slice( 1 );
}
options.dependencies[ mod[ 0 ] ] = mod[ 1 ]; options.dependencies[ mod[ 0 ] ] = mod[ 1 ];
break; break;
@ -149,9 +169,13 @@ while (args.length > 0) {
case "--extra-options-file": case "--extra-options-file":
argument = nextArg( "-c/--config/--extra-options-file" ); argument = nextArg( "-c/--config/--extra-options-file" );
try { try {
json = fs.readFileSync( argument, "utf8" ); json = fs.readFileSync( argument, "utf8" );
} catch ( e ) { } catch ( e ) {
abort( `Can't read from file "${ argument }".` ); abort( `Can't read from file "${ argument }".` );
} }
addExtraOptions( json ); addExtraOptions( json );
break; break;
@ -160,7 +184,9 @@ while (args.length > 0) {
case "--format": case "--format":
argument = nextArg( "-f/--format" ); argument = nextArg( "-f/--format" );
if ( MODULE_FORMATS.indexOf( argument ) === -1 ) { if ( MODULE_FORMATS.indexOf( argument ) === -1 ) {
abort( `Module format must be either ${ formatChoicesList( MODULE_FORMATS ) }.` ); abort( `Module format must be either ${ formatChoicesList( MODULE_FORMATS ) }.` );
} }
options.format = argument; options.format = argument;
break; break;
@ -175,7 +201,9 @@ while (args.length > 0) {
case "--optimize": case "--optimize":
argument = nextArg( "-O/--optimize" ); argument = nextArg( "-O/--optimize" );
if ( OPTIMIZATION_GOALS.indexOf( argument ) === -1 ) { if ( OPTIMIZATION_GOALS.indexOf( argument ) === -1 ) {
abort( `Optimization goal must be either ${ formatChoicesList( OPTIMIZATION_GOALS ) }.` ); abort( `Optimization goal must be either ${ formatChoicesList( OPTIMIZATION_GOALS ) }.` );
} }
options.optimize = argument; options.optimize = argument;
break; break;
@ -189,17 +217,23 @@ while (args.length > 0) {
case "--plugin": case "--plugin":
argument = nextArg( "-p/--plugin" ); argument = nextArg( "-p/--plugin" );
try { try {
mod = require( argument ); mod = require( argument );
} catch ( ex1 ) { } catch ( ex1 ) {
if (ex1.code !== "MODULE_NOT_FOUND") { throw ex1; }
if ( ex1.code !== "MODULE_NOT_FOUND" ) throw ex1;
try { try {
mod = require( path.resolve( argument ) ); mod = require( path.resolve( argument ) );
} catch ( ex2 ) { } catch ( ex2 ) {
if (ex2.code !== "MODULE_NOT_FOUND") { throw ex2; }
if ( ex2.code !== "MODULE_NOT_FOUND" ) throw ex2;
abort( `Can't load module "${ argument }".` ); abort( `Can't load module "${ argument }".` );
} }
} }
options.plugins.push( mod ); options.plugins.push( mod );
break; break;
@ -220,36 +254,51 @@ while (args.length > 0) {
default: default:
if ( inputFile !== null ) { if ( inputFile !== null ) {
abort( `Unknown option: "${ argument }".` ); abort( `Unknown option: "${ argument }".` );
} }
inputFile = argument; inputFile = argument;
} }
} }
// Validation and defaults // Validation and defaults
if ( Object.keys( options.dependencies ).length > 0 ) { if ( Object.keys( options.dependencies ).length > 0 ) {
if ( DEPENDENCY_FORMATS.indexOf( options.format ) === -1 ) { if ( DEPENDENCY_FORMATS.indexOf( options.format ) === -1 ) {
abort( `Can't use the -d/--dependency option with the "${ options.format }" module format.` ); abort( `Can't use the -d/--dependency option with the "${ options.format }" module format.` );
} }
} }
if ( options.exportVar !== null ) { if ( options.exportVar !== null ) {
if ( EXPORT_VAR_FORMATS.indexOf( options.format ) === -1 ) { if ( EXPORT_VAR_FORMATS.indexOf( options.format ) === -1 ) {
abort( `Can't use the -e/--export-var option with the "${ options.format }" module format.` ); abort( `Can't use the -e/--export-var option with the "${ options.format }" module format.` );
}
} }
if (inputFile === null) {
inputFile = "-";
} }
if ( inputFile === null ) inputFile = "-";
if ( outputFile === null ) { if ( outputFile === null ) {
if (inputFile === "-") {
outputFile = "-"; if ( inputFile === "-" ) outputFile = "-";
} else if (inputFile) { else if ( inputFile ) {
outputFile = inputFile.substr(0, inputFile.length - path.extname(inputFile).length) + ".js";
outputFile = inputFile
.substr( 0, inputFile.length - path.extname( inputFile ).length )
+ ".js";
} }
} }
// Export // Export

@ -2,21 +2,33 @@
"use strict"; "use strict";
let fs = require("fs"); const fs = require( "fs" );
let peg = require("../lib/peg"); const peg = require( "../lib/peg" );
let options = require("./options"); const options = require( "./options" );
// Helpers // Helpers
function readStream( inputStream, callback ) { function readStream( inputStream, callback ) {
let input = ""; let input = "";
inputStream.on("data", data => { input += data; }); inputStream.on( "data", data => {
inputStream.on("end", () => { callback(input); });
input += data;
} );
inputStream.on( "end", () => {
callback( input );
} );
} }
function abort( message ) { function abort( message ) {
console.error( message ); console.error( message );
process.exit( 1 ); process.exit( 1 );
} }
// Main // Main
@ -24,41 +36,66 @@ function abort(message) {
let inputStream, outputStream; let inputStream, outputStream;
if ( options.inputFile === "-" ) { if ( options.inputFile === "-" ) {
process.stdin.resume(); process.stdin.resume();
inputStream = process.stdin; inputStream = process.stdin;
inputStream.on( "error", () => { inputStream.on( "error", () => {
abort( `Can't read from file "${ options.inputFile }".` ); abort( `Can't read from file "${ options.inputFile }".` );
} ); } );
} else { } else {
inputStream = fs.createReadStream( options.inputFile ); inputStream = fs.createReadStream( options.inputFile );
} }
if ( options.outputFile === "-" ) { if ( options.outputFile === "-" ) {
outputStream = process.stdout; outputStream = process.stdout;
} else { } else {
outputStream = fs.createWriteStream( options.outputFile ); outputStream = fs.createWriteStream( options.outputFile );
outputStream.on( "error", () => { outputStream.on( "error", () => {
abort( `Can't write to file "${ options.outputFile }".` ); abort( `Can't write to file "${ options.outputFile }".` );
} ); } );
} }
readStream( inputStream, input => { readStream( inputStream, input => {
let location, source; let location, source;
try { try {
source = peg.generate( input, options ); source = peg.generate( input, options );
} catch ( e ) { } catch ( e ) {
if (e.location !== undefined) {
if ( typeof e.location === "object" ) {
location = e.location.start; location = e.location.start;
abort(location.line + ":" + location.column + ": " + e.message); if ( typeof location === "object" ) {
} else {
abort(e.message); return abort( location.line + ":" + location.column + ": " + e.message );
}
} }
return abort( e.message );
} }
outputStream.write( source ); outputStream.write( source );
if ( outputStream !== process.stdout ) { if ( outputStream !== process.stdout ) {
outputStream.end(); outputStream.end();
} }
});
} );

@ -1,26 +1,32 @@
"use strict"; "use strict";
let babelify = require("babelify"); const version = require( "./package" ).version;
let browserify = require("browserify"); const spawn = require( "child_process" ).spawn;
let buffer = require("vinyl-buffer"); const gulp = require( "gulp" );
let del = require("del"); const task = gulp.task.bind( gulp );
let eslint = require("gulp-eslint"); const eslint = require( "gulp-eslint" );
let gulp = require("gulp"); const mocha = require( "gulp-mocha" );
let header = require("gulp-header"); const dedent = require( "dedent" );
let mocha = require("gulp-mocha"); const browserify = require( "browserify" );
let rename = require("gulp-rename"); const babelify = require( "babelify" );
let runSequence = require("run-sequence"); const source = require( "vinyl-source-stream" );
let source = require("vinyl-source-stream"); const rename = require( "gulp-rename" );
let spawn = require("child_process").spawn; const buffer = require( "vinyl-buffer" );
let uglify = require("gulp-uglify"); const uglify = require( "gulp-uglify" );
const header = require( "gulp-header" );
function execFile(args) { const del = require( "del" );
const runSequence = require( "run-sequence" );
function node( args ) {
return spawn( "node", args.split( " " ), { stdio: "inherit" } ); return spawn( "node", args.split( " " ), { stdio: "inherit" } );
} }
// Run ESLint on all JavaScript files. // Run ESLint on all JavaScript files.
gulp.task("lint", () => task( "lint", () => gulp
gulp.src([ .src( [
"**/.*rc.js",
"lib/**/*.js", "lib/**/*.js",
"!lib/parser.js", "!lib/parser.js",
"test/benchmark/**/*.js", "test/benchmark/**/*.js",
@ -31,36 +37,36 @@ gulp.task("lint", () =>
"bin/*.js", "bin/*.js",
"gulpfile.js" "gulpfile.js"
] ) ] )
.pipe(eslint()) .pipe( eslint( { dotfiles: true } ) )
.pipe( eslint.format() ) .pipe( eslint.format() )
.pipe( eslint.failAfterError() ) .pipe( eslint.failAfterError() )
); );
// Run tests. // Run tests.
gulp.task("test", () => task( "test", () => gulp
gulp.src("test/spec/**/*.spec.js", { read: false }) .src( "test/spec/**/*.spec.js", { read: false } )
.pipe( mocha() ) .pipe( mocha() )
); );
// Run benchmarks. // Run benchmarks.
gulp.task("benchmark", () => execFile("test/benchmark/run")); task( "benchmark", () => node( "test/benchmark/run" ) );
// Create the browser build. // Create the browser build.
gulp.task("browser:build", () => { task( "browser:build", () => {
const HEADER = [
"//", const HEADER = dedent`
"// PEG.js v" + require("./package").version,
"// https://pegjs.org/", /**
"//", * PEG.js v${ version }
"// Copyright (c) 2010-2016 David Majda", * https://pegjs.org/
"// Copyright (c) 2017+ Futago-za Ryuu", *
"//", * Copyright (c) 2010-2016 David Majda
"// Licensed under the MIT License.", * Copyright (c) 2017+ Futago-za Ryuu
"//", *
"" * Released under the MIT License.
] */\n\n
.map(line => `${line}\n`)
.join(""); `;
return browserify( "lib/peg.js", { standalone: "peg" } ) return browserify( "lib/peg.js", { standalone: "peg" } )
.transform( babelify, { presets: "es2015", compact: false } ) .transform( babelify, { presets: "es2015", compact: false } )
@ -73,17 +79,18 @@ gulp.task("browser:build", () => {
.pipe( uglify() ) .pipe( uglify() )
.pipe( header( HEADER ) ) .pipe( header( HEADER ) )
.pipe( gulp.dest( "browser" ) ); .pipe( gulp.dest( "browser" ) );
} ); } );
// Delete the browser build. // Delete the browser build.
gulp.task("browser:clean", () => del("browser")); task( "browser:clean", () => del( "browser" ) );
// Generate the grammar parser. // Generate the grammar parser.
gulp.task("parser", () => task( "parser", () =>
execFile("bin/peg src/parser.pegjs -o lib/parser.js") node( "bin/peg src/parser.pegjs -o lib/parser.js" )
); );
// Default task. // Default task.
gulp.task("default", cb => task( "default", cb =>
runSequence("lint", "test", cb) runSequence( "benchmark", "test", cb )
); );

@ -4,8 +4,16 @@ module.exports = {
"extends": "futagozaryuu/es2015", "extends": "futagozaryuu/es2015",
"env": { "env": {
"commonjs": true
"commonjs": true,
},
"root": true,
"rules": {
"prefer-rest-params": 0,
"strict": 0,
}, },
"root": true
}; };

@ -1,49 +1,70 @@
"use strict"; "use strict";
let visitor = require("./visitor"); const visitor = require( "./visitor" );
// AST utilities. // AST utilities.
let asts = { const asts = {
findRule( ast, name ) { findRule( ast, name ) {
for ( let i = 0; i < ast.rules.length; i++ ) { for ( let i = 0; i < ast.rules.length; i++ ) {
if (ast.rules[i].name === name) {
return ast.rules[i]; if ( ast.rules[ i ].name === name ) return ast.rules[ i ];
}
} }
return undefined; return void 0;
}, },
indexOfRule( ast, name ) { indexOfRule( ast, name ) {
for ( let i = 0; i < ast.rules.length; i++ ) { for ( let i = 0; i < ast.rules.length; i++ ) {
if (ast.rules[i].name === name) {
return i; if ( ast.rules[ i ].name === name ) return i;
}
} }
return -1; return -1;
}, },
alwaysConsumesOnSuccess( ast, node ) { alwaysConsumesOnSuccess( ast, node ) {
function consumesTrue() { return true; }
function consumesFalse() { return false; } let consumes;
function consumesTrue() {
return true;
}
function consumesFalse() {
return false;
}
function consumesExpression( node ) { function consumesExpression( node ) {
return consumes( node.expression ); return consumes( node.expression );
} }
let consumes = visitor.build({ consumes = visitor.build( {
rule: consumesExpression, rule: consumesExpression,
named: consumesExpression, named: consumesExpression,
choice( node ) { choice( node ) {
return node.alternatives.every( consumes ); return node.alternatives.every( consumes );
}, },
action: consumesExpression, action: consumesExpression,
sequence( node ) { sequence( node ) {
return node.elements.some( consumes ); return node.elements.some( consumes );
}, },
labeled: consumesExpression, labeled: consumesExpression,
@ -58,11 +79,15 @@ let asts = {
semantic_not: consumesFalse, semantic_not: consumesFalse,
rule_ref( node ) { rule_ref( node ) {
return consumes( asts.findRule( ast, node.name ) ); return consumes( asts.findRule( ast, node.name ) );
}, },
literal( node ) { literal( node ) {
return node.value !== ""; return node.value !== "";
}, },
class: consumesTrue, class: consumesTrue,
@ -70,6 +95,7 @@ let asts = {
} ); } );
return consumes( node ); return consumes( node );
} }
}; };

@ -1,32 +1,40 @@
"use strict"; "use strict";
let generateBytecode = require("./passes/generate-bytecode"); const generateBytecode = require( "./passes/generate-bytecode" );
let generateJS = require("./passes/generate-js"); const generateJS = require( "./passes/generate-js" );
let removeProxyRules = require("./passes/remove-proxy-rules"); const removeProxyRules = require( "./passes/remove-proxy-rules" );
let reportDuplicateLabels = require("./passes/report-duplicate-labels"); const reportDuplicateLabels = require( "./passes/report-duplicate-labels" );
let reportDuplicateRules = require("./passes/report-duplicate-rules"); const reportDuplicateRules = require( "./passes/report-duplicate-rules" );
let reportInfiniteRecursion = require("./passes/report-infinite-recursion"); const reportInfiniteRecursion = require( "./passes/report-infinite-recursion" );
let reportInfiniteRepetition = require("./passes/report-infinite-repetition"); const reportInfiniteRepetition = require( "./passes/report-infinite-repetition" );
let reportUndefinedRules = require("./passes/report-undefined-rules"); const reportUndefinedRules = require( "./passes/report-undefined-rules" );
let visitor = require("./visitor"); const visitor = require( "./visitor" );
function processOptions( options, defaults ) { function processOptions( options, defaults ) {
let processedOptions = {};
const processedOptions = {};
Object.keys( options ).forEach( name => { Object.keys( options ).forEach( name => {
processedOptions[ name ] = options[ name ]; processedOptions[ name ] = options[ name ];
} ); } );
Object.keys( defaults ).forEach( name => { Object.keys( defaults ).forEach( name => {
if ( ! Object.prototype.hasOwnProperty.call( processedOptions, name ) ) { if ( ! Object.prototype.hasOwnProperty.call( processedOptions, name ) ) {
processedOptions[ name ] = defaults[ name ]; processedOptions[ name ] = defaults[ name ];
} }
} ); } );
return processedOptions; return processedOptions;
} }
let compiler = { const compiler = {
// AST node visitor builder. Useful mainly for plugins which manipulate the // AST node visitor builder. Useful mainly for plugins which manipulate the
// AST. // AST.
visitor: visitor, visitor: visitor,
@ -58,7 +66,8 @@ let compiler = {
// during the generation and some may protrude to the generated parser and // during the generation and some may protrude to the generated parser and
// cause its malfunction. // cause its malfunction.
compile( ast, passes, options ) { compile( ast, passes, options ) {
options = options !== undefined ? options : {};
options = typeof options !== "undefined" ? options : {};
options = processOptions( options, { options = processOptions( options, {
allowedStartRules: [ ast.rules[ 0 ].name ], allowedStartRules: [ ast.rules[ 0 ].name ],
@ -72,10 +81,17 @@ let compiler = {
} ); } );
Object.keys( passes ).forEach( stage => { Object.keys( passes ).forEach( stage => {
passes[stage].forEach(p => { p(ast, options); });
passes[ stage ].forEach( pass => {
pass( ast, options );
} );
} ); } );
switch ( options.output ) { switch ( options.output ) {
case "parser": case "parser":
return eval( ast.code ); return eval( ast.code );
@ -83,8 +99,10 @@ let compiler = {
return ast.code; return ast.code;
default: default:
throw new Error("Invalid output format: " + options.output + "."); throw new Error( `Invalid output format: ${ options.output }.` );
} }
} }
}; };

@ -1,10 +1,15 @@
"use strict"; "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. // JavaScript code generation helpers.
let js = { const js = {
stringEscape( s ) { stringEscape( s ) {
// ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string // ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
// literal except for the closing quote character, backslash, carriage // literal except for the closing quote character, backslash, carriage
// return, line separator, paragraph separator, and line feed. Any character // return, line separator, paragraph separator, and line feed. Any character
@ -25,9 +30,11 @@ let js = {
.replace( /[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex( ch ) ) .replace( /[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex( ch ) )
.replace( /[\u0100-\u0FFF]/g, ch => "\\u0" + hex( ch ) ) .replace( /[\u0100-\u0FFF]/g, ch => "\\u0" + hex( ch ) )
.replace( /[\u1000-\uFFFF]/g, ch => "\\u" + hex( ch ) ); .replace( /[\u1000-\uFFFF]/g, ch => "\\u" + hex( ch ) );
}, },
regexpClassEscape( s ) { regexpClassEscape( s ) {
// Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1. // Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
// //
// For portability, we also escape all control and non-ASCII characters. // For portability, we also escape all control and non-ASCII characters.
@ -48,6 +55,7 @@ let js = {
.replace( /[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex( ch ) ) .replace( /[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex( ch ) )
.replace( /[\u0100-\u0FFF]/g, ch => "\\u0" + hex( ch ) ) .replace( /[\u0100-\u0FFF]/g, ch => "\\u0" + hex( ch ) )
.replace( /[\u1000-\uFFFF]/g, ch => "\\u" + hex( ch ) ); .replace( /[\u1000-\uFFFF]/g, ch => "\\u" + hex( ch ) );
} }
}; };

@ -1,7 +1,8 @@
"use strict"; "use strict";
// Bytecode instruction opcodes. // Bytecode instruction opcodes.
let opcodes = { const opcodes = {
// Stack Manipulation // Stack Manipulation
PUSH: 0, // PUSH c PUSH: 0, // PUSH c
@ -49,6 +50,7 @@ let opcodes = {
SILENT_FAILS_ON: 28, // SILENT_FAILS_ON SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
}; };
module.exports = opcodes; module.exports = opcodes;

@ -1,9 +1,9 @@
"use strict"; "use strict";
let asts = require("../asts"); const asts = require( "../asts" );
let js = require("../js"); const js = require( "../js" );
let op = require("../opcodes"); const op = require( "../opcodes" );
let visitor = require("../visitor"); const visitor = require( "../visitor" );
// Generates bytecode. // Generates bytecode.
// //
@ -188,53 +188,68 @@ let visitor = require("../visitor");
// //
// silentFails--; // silentFails--;
function generateBytecode( ast ) { function generateBytecode( ast ) {
let consts = [];
const consts = [];
let generate;
function addConst( value ) { function addConst( value ) {
let index = consts.indexOf(value);
const index = consts.indexOf( value );
return index === -1 ? consts.push( value ) - 1 : index; return index === -1 ? consts.push( value ) - 1 : index;
} }
function addFunctionConst( params, code ) { function addFunctionConst( params, code ) {
return addConst(
"function(" + params.join(", ") + ") {" + code + "}" return addConst( `function(${ params.join( ", " ) }) {${ code }}` );
);
} }
function cloneEnv( env ) { function cloneEnv( env ) {
let clone = {};
const clone = {};
Object.keys( env ).forEach( name => { Object.keys( env ).forEach( name => {
clone[ name ] = env[ name ]; clone[ name ] = env[ name ];
} ); } );
return clone; return clone;
} }
function buildSequence() { function buildSequence() {
return Array.prototype.concat.apply( [], arguments ); return Array.prototype.concat.apply( [], arguments );
} }
function buildCondition( condCode, thenCode, elseCode ) { function buildCondition( condCode, thenCode, elseCode ) {
return condCode.concat( return condCode.concat(
[ thenCode.length, elseCode.length ], [ thenCode.length, elseCode.length ],
thenCode, thenCode,
elseCode elseCode
); );
} }
function buildLoop( condCode, bodyCode ) { function buildLoop( condCode, bodyCode ) {
return condCode.concat( [ bodyCode.length ], bodyCode ); return condCode.concat( [ bodyCode.length ], bodyCode );
} }
function buildCall( functionIndex, delta, env, sp ) { function buildCall( functionIndex, delta, env, sp ) {
let params = Object.keys(env).map(name => sp - env[name]);
const params = Object.keys( env ).map( name => sp - env[ name ] );
return [ op.CALL, functionIndex, delta, params.length ].concat( params ); return [ op.CALL, functionIndex, delta, params.length ].concat( params );
} }
function buildSimplePredicate( expression, negative, context ) { function buildSimplePredicate( expression, negative, context ) {
return buildSequence( return buildSequence(
[ op.PUSH_CURR_POS ], [ op.PUSH_CURR_POS ],
[ op.SILENT_FAILS_ON ], [ op.SILENT_FAILS_ON ],
@ -258,53 +273,56 @@ function generateBytecode(ast) {
) )
) )
); );
} }
function buildSemanticPredicate( code, negative, context ) { function buildSemanticPredicate( code, negative, context ) {
let functionIndex = addFunctionConst(Object.keys(context.env), code);
const functionIndex = addFunctionConst( Object.keys( context.env ), code );
return buildSequence( return buildSequence(
[ op.UPDATE_SAVED_POS ], [ op.UPDATE_SAVED_POS ],
buildCall( functionIndex, 0, context.env, context.sp ), buildCall( functionIndex, 0, context.env, context.sp ),
buildCondition( buildCondition(
[ op.IF ], [ op.IF ],
buildSequence( buildSequence( [ op.POP ], negative ? [ op.PUSH_FAILED ] : [ op.PUSH_UNDEFINED ] ),
[op.POP], buildSequence( [ op.POP ], negative ? [ op.PUSH_UNDEFINED ] : [ op.PUSH_FAILED ] )
negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]
)
) )
); );
} }
function buildAppendLoop( expressionCode ) { function buildAppendLoop( expressionCode ) {
return buildLoop( return buildLoop(
[ op.WHILE_NOT_ERROR ], [ op.WHILE_NOT_ERROR ],
buildSequence( [ op.APPEND ], expressionCode ) buildSequence( [ op.APPEND ], expressionCode )
); );
} }
let generate = visitor.build({ generate = visitor.build( {
grammar( node ) { grammar( node ) {
node.rules.forEach(generate);
node.rules.forEach( generate );
node.consts = consts; node.consts = consts;
}, },
rule( node ) { rule( node ) {
node.bytecode = generate( node.expression, { node.bytecode = generate( node.expression, {
sp: -1, // stack pointer sp: -1, // stack pointer
env: { }, // mapping of label names to stack positions env: { }, // mapping of label names to stack positions
action: null // action nodes pass themselves to children here action: null // action nodes pass themselves to children here
} ); } );
}, },
named( node, context ) { named( node, context ) {
let nameIndex = addConst(
"peg$otherExpectation(\"" + js.stringEscape(node.name) + "\")" const nameIndex = addConst(
`peg$otherExpectation("${ js.stringEscape( node.name ) }")`
); );
// The code generated below is slightly suboptimal because |FAIL| pushes // The code generated below is slightly suboptimal because |FAIL| pushes
@ -317,18 +335,22 @@ function generateBytecode(ast) {
[ op.SILENT_FAILS_OFF ], [ op.SILENT_FAILS_OFF ],
buildCondition( [ op.IF_ERROR ], [ op.FAIL, nameIndex ], [] ) buildCondition( [ op.IF_ERROR ], [ op.FAIL, nameIndex ], [] )
); );
}, },
choice( node, context ) { choice( node, context ) {
function buildAlternativesCode( alternatives, context ) { function buildAlternativesCode( alternatives, context ) {
return buildSequence( return buildSequence(
generate( alternatives[ 0 ], { generate( alternatives[ 0 ], {
sp: context.sp, sp: context.sp,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null
} ), } ),
alternatives.length > 1 alternatives.length < 2
? buildCondition( ? []
: buildCondition(
[ op.IF_ERROR ], [ op.IF_ERROR ],
buildSequence( buildSequence(
[ op.POP ], [ op.POP ],
@ -336,26 +358,28 @@ function generateBytecode(ast) {
), ),
[] []
) )
: []
); );
} }
return buildAlternativesCode( node.alternatives, context ); return buildAlternativesCode( node.alternatives, context );
}, },
action( node, context ) { action( node, context ) {
let env = cloneEnv(context.env);
let emitCall = node.expression.type !== "sequence" const env = cloneEnv( context.env );
|| node.expression.elements.length === 0; const emitCall = node.expression.type !== "sequence" || node.expression.elements.length === 0;
let expressionCode = generate(node.expression, { const expressionCode = generate( node.expression, {
sp: context.sp + ( emitCall ? 1 : 0 ), sp: context.sp + ( emitCall ? 1 : 0 ),
env: env, env: env,
action: node action: node
} ); } );
let functionIndex = addFunctionConst(Object.keys(env), node.code); const functionIndex = addFunctionConst( Object.keys( env ), node.code );
return emitCall return emitCall === false
? buildSequence( ? expressionCode
: buildSequence(
[ op.PUSH_CURR_POS ], [ op.PUSH_CURR_POS ],
expressionCode, expressionCode,
buildCondition( buildCondition(
@ -367,14 +391,17 @@ function generateBytecode(ast) {
[] []
), ),
[ op.NIP ] [ op.NIP ]
) );
: expressionCode;
}, },
sequence( node, context ) { sequence( node, context ) {
function buildElementsCode( elements, context ) { function buildElementsCode( elements, context ) {
if ( elements.length > 0 ) { if ( elements.length > 0 ) {
let processedCount = node.elements.length - elements.slice(1).length;
const processedCount = node.elements.length - elements.slice( 1 ).length;
return buildSequence( return buildSequence(
generate( elements[ 0 ], { generate( elements[ 0 ], {
@ -396,9 +423,10 @@ function generateBytecode(ast) {
) )
) )
); );
} else {
if (context.action) { } else if ( context.action ) {
let functionIndex = addFunctionConst(
const functionIndex = addFunctionConst(
Object.keys( context.env ), Object.keys( context.env ),
context.action.code context.action.code
); );
@ -412,10 +440,10 @@ function generateBytecode(ast) {
context.sp context.sp
) )
); );
} else {
return buildSequence([op.WRAP, node.elements.length], [op.NIP]);
}
} }
return buildSequence( [ op.WRAP, node.elements.length ], [ op.NIP ] );
} }
return buildSequence( return buildSequence(
@ -426,10 +454,12 @@ function generateBytecode(ast) {
action: context.action action: context.action
} ) } )
); );
}, },
labeled( node, context ) { labeled( node, context ) {
let env = cloneEnv(context.env);
const env = cloneEnv( context.env );
context.env[ node.label ] = context.sp + 1; context.env[ node.label ] = context.sp + 1;
@ -438,9 +468,11 @@ function generateBytecode(ast) {
env: env, env: env,
action: null action: null
} ); } );
}, },
text( node, context ) { text( node, context ) {
return buildSequence( return buildSequence(
[ op.PUSH_CURR_POS ], [ op.PUSH_CURR_POS ],
generate( node.expression, { generate( node.expression, {
@ -454,17 +486,23 @@ function generateBytecode(ast) {
[ op.NIP ] [ op.NIP ]
) )
); );
}, },
simple_and( node, context ) { simple_and( node, context ) {
return buildSimplePredicate( node.expression, false, context ); return buildSimplePredicate( node.expression, false, context );
}, },
simple_not( node, context ) { simple_not( node, context ) {
return buildSimplePredicate( node.expression, true, context ); return buildSimplePredicate( node.expression, true, context );
}, },
optional( node, context ) { optional( node, context ) {
return buildSequence( return buildSequence(
generate( node.expression, { generate( node.expression, {
sp: context.sp, sp: context.sp,
@ -477,10 +515,12 @@ function generateBytecode(ast) {
[] []
) )
); );
}, },
zero_or_more( node, context ) { zero_or_more( node, context ) {
let expressionCode = generate(node.expression, {
const expressionCode = generate( node.expression, {
sp: context.sp + 1, sp: context.sp + 1,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null
@ -492,10 +532,12 @@ function generateBytecode(ast) {
buildAppendLoop( expressionCode ), buildAppendLoop( expressionCode ),
[ op.POP ] [ op.POP ]
); );
}, },
one_or_more( node, context ) { one_or_more( node, context ) {
let expressionCode = generate(node.expression, {
const expressionCode = generate( node.expression, {
sp: context.sp + 1, sp: context.sp + 1,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null
@ -510,39 +552,47 @@ function generateBytecode(ast) {
buildSequence( [ op.POP ], [ op.POP ], [ op.PUSH_FAILED ] ) buildSequence( [ op.POP ], [ op.POP ], [ op.PUSH_FAILED ] )
) )
); );
}, },
group( node, context ) { group( node, context ) {
return generate( node.expression, { return generate( node.expression, {
sp: context.sp, sp: context.sp,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null
} ); } );
}, },
semantic_and( node, context ) { semantic_and( node, context ) {
return buildSemanticPredicate( node.code, false, context ); return buildSemanticPredicate( node.code, false, context );
}, },
semantic_not( node, context ) { semantic_not( node, context ) {
return buildSemanticPredicate( node.code, true, context ); return buildSemanticPredicate( node.code, true, context );
}, },
rule_ref( node ) { rule_ref( node ) {
return [ op.RULE, asts.indexOfRule( ast, node.name ) ]; return [ op.RULE, asts.indexOfRule( ast, node.name ) ];
}, },
literal( node ) { literal( node ) {
if ( node.value.length > 0 ) { if ( node.value.length > 0 ) {
let stringIndex = addConst("\""
+ js.stringEscape( const stringIndex = addConst( `"${ js.stringEscape(
node.ignoreCase ? node.value.toLowerCase() : node.value node.ignoreCase ? node.value.toLowerCase() : node.value
) ) }"` );
+ "\"" const expectedIndex = addConst(
);
let expectedIndex = addConst(
"peg$literalExpectation(" "peg$literalExpectation("
+ "\"" + js.stringEscape(node.value) + "\", " + `"${ js.stringEscape( node.value ) }", `
+ node.ignoreCase + node.ignoreCase
+ ")" + ")"
); );
@ -559,33 +609,42 @@ function generateBytecode(ast) {
: [ op.ACCEPT_STRING, stringIndex ], : [ op.ACCEPT_STRING, stringIndex ],
[ op.FAIL, expectedIndex ] [ op.FAIL, expectedIndex ]
); );
} else {
let stringIndex = addConst("\"\"");
return [op.PUSH, stringIndex];
} }
const stringIndex = addConst( "\"\"" );
return [ op.PUSH, stringIndex ];
}, },
class( node ) { class( node ) {
let regexp = "/^["
const regexp = "/^["
+ ( node.inverted ? "^" : "" ) + ( node.inverted ? "^" : "" )
+ node.parts.map(part => + node.parts
Array.isArray(part) .map( part =>
( Array.isArray( part )
? js.regexpClassEscape( part[ 0 ] ) ? js.regexpClassEscape( part[ 0 ] )
+ "-" + "-"
+ js.regexpClassEscape( part[ 1 ] ) + js.regexpClassEscape( part[ 1 ] )
: js.regexpClassEscape(part) : js.regexpClassEscape( part ) )
).join("") )
+ "]/" + (node.ignoreCase ? "i" : ""); .join( "" )
let parts = "[" + "]/"
+ node.parts.map(part => + ( node.ignoreCase ? "i" : "" );
Array.isArray(part)
? "[\"" + js.stringEscape(part[0]) + "\", \"" + js.stringEscape(part[1]) + "\"]" const parts = "["
: "\"" + js.stringEscape(part) + "\"" + node.parts
).join(", ") .map( part =>
( Array.isArray( part )
? `["${ js.stringEscape( part[ 0 ] ) }", "${ js.stringEscape( part[ 1 ] ) }"]`
: "\"" + js.stringEscape( part ) + "\"" )
)
.join( ", " )
+ "]"; + "]";
let regexpIndex = addConst(regexp);
let expectedIndex = addConst( const regexpIndex = addConst( regexp );
const expectedIndex = addConst(
"peg$classExpectation(" "peg$classExpectation("
+ parts + ", " + parts + ", "
+ node.inverted + ", " + node.inverted + ", "
@ -598,20 +657,24 @@ function generateBytecode(ast) {
[ op.ACCEPT_N, 1 ], [ op.ACCEPT_N, 1 ],
[ op.FAIL, expectedIndex ] [ op.FAIL, expectedIndex ]
); );
}, },
any() { any() {
let expectedIndex = addConst("peg$anyExpectation()");
const expectedIndex = addConst( "peg$anyExpectation()" );
return buildCondition( return buildCondition(
[ op.MATCH_ANY ], [ op.MATCH_ANY ],
[ op.ACCEPT_N, 1 ], [ op.ACCEPT_N, 1 ],
[ op.FAIL, expectedIndex ] [ op.FAIL, expectedIndex ]
); );
} }
} ); } );
generate( ast ); generate( ast );
} }
module.exports = generateBytecode; module.exports = generateBytecode;

@ -1,44 +1,65 @@
/* eslint no-mixed-operators: 0, prefer-const: 0 */
"use strict"; "use strict";
let asts = require("../asts"); const asts = require( "../asts" );
let js = require("../js"); const js = require( "../js" );
let op = require("../opcodes"); const op = require( "../opcodes" );
// Generates parser JavaScript code. // Generates parser JavaScript code.
function generateJS( ast, options ) { function generateJS( ast, options ) {
/* These only indent non-empty lines to avoid trailing whitespace. */ /* These only indent non-empty lines to avoid trailing whitespace. */
const lineMatchRE = /^([^`\r\n]+?(?:`[^`]*?`[^\r\n]*?)?)$/gm; const lineMatchRE = /^([^`\r\n]+?(?:`[^`]*?`[^\r\n]*?)?)$/gm;
function indent2(code) { return code.replace(lineMatchRE, " $1"); } function indent2( code ) {
function indent10(code) { return code.replace(lineMatchRE, " $1"); }
return code.replace( lineMatchRE, " $1" );
}
function indent10( code ) {
return code.replace( lineMatchRE, " $1" );
}
function generateTables() { function generateTables() {
if ( options.optimize === "size" ) { if ( options.optimize === "size" ) {
return [ return [
"var peg$consts = [", "var peg$consts = [",
indent2( ast.consts.join( ",\n" ) ), indent2( ast.consts.join( ",\n" ) ),
"];", "];",
"", "",
"var peg$bytecode = [", "var peg$bytecode = [",
indent2(ast.rules.map(rule => indent2( ast.rules
"peg$decode(\"" .map( rule =>
+ js.stringEscape(rule.bytecode.map( `peg$decode("${
b => String.fromCharCode(b + 32) js.stringEscape( rule.bytecode
).join("")) .map( b => String.fromCharCode( b + 32 ) )
+ "\")" .join( "" )
).join(",\n")), )
}")`
)
.join( ",\n" )
),
"];" "];"
].join( "\n" ); ].join( "\n" );
} else {
return ast.consts.map((c, i) => "var peg$c" + i + " = " + c + ";").join("\n");
} }
return ast.consts.map( ( c, i ) => "var peg$c" + i + " = " + c + ";" ).join( "\n" );
} }
function generateRuleHeader( ruleNameCode, ruleIndexCode ) { function generateRuleHeader( ruleNameCode, ruleIndexCode ) {
let parts = [];
const parts = [];
parts.push( "" ); parts.push( "" );
if ( options.trace ) { if ( options.trace ) {
parts.push( [ parts.push( [
"peg$tracer.trace({", "peg$tracer.trace({",
" type: \"rule.enter\",", " type: \"rule.enter\",",
@ -47,9 +68,11 @@ function generateJS(ast, options) {
"});", "});",
"" ""
].join( "\n" ) ); ].join( "\n" ) );
} }
if ( options.cache ) { if ( options.cache ) {
parts.push( [ parts.push( [
"var key = peg$currPos * " + ast.rules.length + " + " + ruleIndexCode + ";", "var key = peg$currPos * " + ast.rules.length + " + " + ruleIndexCode + ";",
"var cached = peg$resultsCache[key];", "var cached = peg$resultsCache[key];",
@ -60,6 +83,7 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.trace ) { if ( options.trace ) {
parts.push( [ parts.push( [
"if (cached.result !== peg$FAILED) {", "if (cached.result !== peg$FAILED) {",
" peg$tracer.trace({", " peg$tracer.trace({",
@ -77,6 +101,7 @@ function generateJS(ast, options) {
"}", "}",
"" ""
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( [ parts.push( [
@ -84,22 +109,28 @@ function generateJS(ast, options) {
"}", "}",
"" ""
].join( "\n" ) ); ].join( "\n" ) );
} }
return parts.join( "\n" ); return parts.join( "\n" );
} }
function generateRuleFooter( ruleNameCode, resultCode ) { function generateRuleFooter( ruleNameCode, resultCode ) {
let parts = [];
const parts = [];
if ( options.cache ) { if ( options.cache ) {
parts.push( [ parts.push( [
"", "",
"peg$resultsCache[key] = { nextPos: peg$currPos, result: " + resultCode + " };" "peg$resultsCache[key] = { nextPos: peg$currPos, result: " + resultCode + " };"
].join( "\n" ) ); ].join( "\n" ) );
} }
if ( options.trace ) { if ( options.trace ) {
parts.push( [ parts.push( [
"", "",
"if (" + resultCode + " !== peg$FAILED) {", "if (" + resultCode + " !== peg$FAILED) {",
@ -117,6 +148,7 @@ function generateJS(ast, options) {
" });", " });",
"}" "}"
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( [ parts.push( [
@ -125,15 +157,18 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
return parts.join( "\n" ); return parts.join( "\n" );
} }
function generateInterpreter() { function generateInterpreter() {
let parts = [];
const parts = [];
function generateCondition( cond, argsLength ) { function generateCondition( cond, argsLength ) {
let baseLength = argsLength + 3;
let thenLengthCode = "bc[ip + " + (baseLength - 2) + "]"; const baseLength = argsLength + 3;
let elseLengthCode = "bc[ip + " + (baseLength - 1) + "]"; const thenLengthCode = "bc[ip + " + ( baseLength - 2 ) + "]";
const elseLengthCode = "bc[ip + " + ( baseLength - 1 ) + "]";
return [ return [
"ends.push(end);", "ends.push(end);",
@ -149,11 +184,13 @@ function generateJS(ast, options) {
"", "",
"break;" "break;"
].join( "\n" ); ].join( "\n" );
} }
function generateLoop( cond ) { function generateLoop( cond ) {
let baseLength = 2;
let bodyLengthCode = "bc[ip + " + (baseLength - 1) + "]"; const baseLength = 2;
const bodyLengthCode = "bc[ip + " + ( baseLength - 1 ) + "]";
return [ return [
"if (" + cond + ") {", "if (" + cond + ") {",
@ -168,11 +205,13 @@ function generateJS(ast, options) {
"", "",
"break;" "break;"
].join( "\n" ); ].join( "\n" );
} }
function generateCall() { function generateCall() {
let baseLength = 4;
let paramsLengthCode = "bc[ip + " + (baseLength - 1) + "]"; const baseLength = 4;
const paramsLengthCode = "bc[ip + " + ( baseLength - 1 ) + "]";
return [ return [
"params = bc.slice(ip + " + baseLength + ", ip + " + baseLength + " + " + paramsLengthCode + ")", "params = bc.slice(ip + " + baseLength + ", ip + " + baseLength + " + " + paramsLengthCode + ")",
@ -187,6 +226,7 @@ function generateJS(ast, options) {
"ip += " + baseLength + " + " + paramsLengthCode + ";", "ip += " + baseLength + " + " + paramsLengthCode + ";",
"break;" "break;"
].join( "\n" ); ].join( "\n" );
} }
parts.push( [ parts.push( [
@ -198,6 +238,7 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.trace ) { if ( options.trace ) {
parts.push( [ parts.push( [
" var bc = peg$bytecode[index];", " var bc = peg$bytecode[index];",
" var ip = 0;", " var ip = 0;",
@ -208,7 +249,9 @@ function generateJS(ast, options) {
" var startPos = peg$currPos;", " var startPos = peg$currPos;",
" var params;" " var params;"
].join( "\n" ) ); ].join( "\n" ) );
} else { } else {
parts.push( [ parts.push( [
" var bc = peg$bytecode[index];", " var bc = peg$bytecode[index];",
" var ip = 0;", " var ip = 0;",
@ -218,6 +261,7 @@ function generateJS(ast, options) {
" var stack = [];", " var stack = [];",
" var params;" " var params;"
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( indent2( generateRuleHeader( "peg$ruleNames[index]", "index" ) ) ); parts.push( indent2( generateRuleHeader( "peg$ruleNames[index]", "index" ) ) );
@ -401,64 +445,80 @@ function generateJS(ast, options) {
parts.push( "}" ); parts.push( "}" );
return parts.join( "\n" ); return parts.join( "\n" );
} }
function generateRuleFunction( rule ) { function generateRuleFunction( rule ) {
let parts = [];
let stackVars = [];
let code;
function c(i) { return "peg$c" + i; } // |consts[i]| of the abstract machine const parts = [];
function s(i) { return "s" + i; } // |stack[i]| of the abstract machine const stackVars = [];
function c( i ) {
return "peg$c" + i;
let stack = { } // |consts[i]| of the abstract machine
function s( i ) {
return "s" + i;
} // |stack[i]| of the abstract machine
const stack = {
sp: -1, sp: -1,
maxSp: -1, maxSp: -1,
push( exprCode ) { push( exprCode ) {
let code = s(++this.sp) + " = " + exprCode + ";";
if (this.sp > this.maxSp) { this.maxSp = this.sp; }
const code = s( ++this.sp ) + " = " + exprCode + ";";
if ( this.sp > this.maxSp ) this.maxSp = this.sp;
return code; return code;
}, },
pop( n ) { pop( n ) {
if (n === undefined) {
return s(this.sp--); if ( typeof n === "undefined" ) return s( this.sp-- );
} else {
let values = Array(n); const values = Array( n );
for ( let i = 0; i < n; i++ ) { for ( let i = 0; i < n; i++ ) {
values[ i ] = s( this.sp - n + 1 + i ); values[ i ] = s( this.sp - n + 1 + i );
} }
this.sp -= n; this.sp -= n;
return values; return values;
}
}, },
top() { top() {
return s( this.sp ); return s( this.sp );
}, },
index( i ) { index( i ) {
return s( this.sp - i ); return s( this.sp - i );
} }
}; };
function compile( bc ) { function compile( bc ) {
let ip = 0; let ip = 0;
let end = bc.length; const end = bc.length;
let parts = []; const parts = [];
let value; let value;
function compileCondition( cond, argCount ) { function compileCondition( cond, argCount ) {
let baseLength = argCount + 3;
let thenLength = bc[ip + baseLength - 2]; const baseLength = argCount + 3;
let elseLength = bc[ip + baseLength - 1]; const thenLength = bc[ ip + baseLength - 2 ];
let baseSp = stack.sp; const elseLength = bc[ ip + baseLength - 1 ];
const baseSp = stack.sp;
let thenCode, elseCode, thenSp, elseSp; let thenCode, elseCode, thenSp, elseSp;
ip += baseLength; ip += baseLength;
@ -467,31 +527,39 @@ function generateJS(ast, options) {
ip += thenLength; ip += thenLength;
if ( elseLength > 0 ) { if ( elseLength > 0 ) {
stack.sp = baseSp; stack.sp = baseSp;
elseCode = compile( bc.slice( ip, ip + elseLength ) ); elseCode = compile( bc.slice( ip, ip + elseLength ) );
elseSp = stack.sp; elseSp = stack.sp;
ip += elseLength; ip += elseLength;
if ( thenSp !== elseSp ) { if ( thenSp !== elseSp ) {
throw new Error( throw new Error(
"Branches of a condition must move the stack pointer in the same way." "Branches of a condition must move the stack pointer in the same way."
); );
} }
} }
parts.push( "if (" + cond + ") {" ); parts.push( "if (" + cond + ") {" );
parts.push( indent2( thenCode ) ); parts.push( indent2( thenCode ) );
if ( elseLength > 0 ) { if ( elseLength > 0 ) {
parts.push( "} else {" ); parts.push( "} else {" );
parts.push( indent2( elseCode ) ); parts.push( indent2( elseCode ) );
} }
parts.push( "}" ); parts.push( "}" );
} }
function compileLoop( cond ) { function compileLoop( cond ) {
let baseLength = 2;
let bodyLength = bc[ip + baseLength - 1]; const baseLength = 2;
let baseSp = stack.sp; const bodyLength = bc[ ip + baseLength - 1 ];
const baseSp = stack.sp;
let bodyCode, bodySp; let bodyCode, bodySp;
ip += baseLength; ip += baseLength;
@ -500,30 +568,40 @@ function generateJS(ast, options) {
ip += bodyLength; ip += bodyLength;
if ( bodySp !== baseSp ) { if ( bodySp !== baseSp ) {
throw new Error( "Body of a loop can't move the stack pointer." ); throw new Error( "Body of a loop can't move the stack pointer." );
} }
parts.push( "while (" + cond + ") {" ); parts.push( "while (" + cond + ") {" );
parts.push( indent2( bodyCode ) ); parts.push( indent2( bodyCode ) );
parts.push( "}" ); parts.push( "}" );
} }
function compileCall() { function compileCall() {
let baseLength = 4;
let paramsLength = bc[ip + baseLength - 1];
let value = c(bc[ip + 1]) + "(" const baseLength = 4;
+ bc.slice(ip + baseLength, ip + baseLength + paramsLength).map( const paramsLength = bc[ ip + baseLength - 1 ];
p => stack.index(p)
).join(", ") const value = c( bc[ ip + 1 ] )
+ "("
+ bc
.slice( ip + baseLength, ip + baseLength + paramsLength )
.map( p => stack.index( p ) )
.join( ", " )
+ ")"; + ")";
stack.pop( bc[ ip + 2 ] ); stack.pop( bc[ ip + 2 ] );
parts.push( stack.push( value ) ); parts.push( stack.push( value ) );
ip += baseLength + paramsLength; ip += baseLength + paramsLength;
} }
while ( ip < end ) { while ( ip < end ) {
switch ( bc[ ip ] ) { switch ( bc[ ip ] ) {
case op.PUSH: // PUSH c case op.PUSH: // PUSH c
parts.push( stack.push( c( bc[ ip + 1 ] ) ) ); parts.push( stack.push( c( bc[ ip + 1 ] ) ) );
ip += 2; ip += 2;
@ -624,8 +702,8 @@ function generateJS(ast, options) {
+ ") === " + ") === "
+ c( bc[ ip + 1 ] ) + c( bc[ ip + 1 ] )
: "input.charCodeAt(peg$currPos) === " : "input.charCodeAt(peg$currPos) === "
+ eval(ast.consts[bc[ip + 1]]).charCodeAt(0), + eval( ast.consts[ bc[ ip + 1 ] ] ).charCodeAt( 0 )
1 , 1
); );
break; break;
@ -634,16 +712,13 @@ function generateJS(ast, options) {
"input.substr(peg$currPos, " "input.substr(peg$currPos, "
+ eval( ast.consts[ bc[ ip + 1 ] ] ).length + eval( ast.consts[ bc[ ip + 1 ] ] ).length
+ ").toLowerCase() === " + ").toLowerCase() === "
+ c(bc[ip + 1]), + c( bc[ ip + 1 ] )
1 , 1
); );
break; break;
case op.MATCH_REGEXP: // MATCH_REGEXP r, a, f, ... case op.MATCH_REGEXP: // MATCH_REGEXP r, a, f, ...
compileCondition( compileCondition( c( bc[ ip + 1 ] ) + ".test(input.charAt(peg$currPos))", 1 );
c(bc[ip + 1]) + ".test(input.charAt(peg$currPos))",
1
);
break; break;
case op.ACCEPT_N: // ACCEPT_N n case op.ACCEPT_N: // ACCEPT_N n
@ -707,22 +782,29 @@ function generateJS(ast, options) {
default: default:
throw new Error( "Invalid opcode: " + bc[ ip ] + "." ); throw new Error( "Invalid opcode: " + bc[ ip ] + "." );
} }
} }
return parts.join( "\n" ); return parts.join( "\n" );
} }
code = compile(rule.bytecode); const code = compile( rule.bytecode );
parts.push( "function peg$parse" + rule.name + "() {" ); parts.push( "function peg$parse" + rule.name + "() {" );
if ( options.trace ) { if ( options.trace ) {
parts.push( " var startPos = peg$currPos;" ); parts.push( " var startPos = peg$currPos;" );
} }
for ( let i = 0; i <= stack.maxSp; i++ ) { for ( let i = 0; i <= stack.maxSp; i++ ) {
stackVars[ i ] = s( i ); stackVars[ i ] = s( i );
} }
parts.push( " var " + stackVars.join( ", " ) + ";" ); parts.push( " var " + stackVars.join( ", " ) + ";" );
@ -740,10 +822,12 @@ function generateJS(ast, options) {
parts.push( "}" ); parts.push( "}" );
return parts.join( "\n" ); return parts.join( "\n" );
} }
function generateToplevel() { function generateToplevel() {
let parts = [];
const parts = [];
parts.push( [ parts.push( [
"function peg$subclass(child, parent) {", "function peg$subclass(child, parent) {",
@ -869,6 +953,7 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.trace ) { if ( options.trace ) {
parts.push( [ parts.push( [
"function peg$DefaultTracer() {", "function peg$DefaultTracer() {",
" this.indentLevel = 0;", " this.indentLevel = 0;",
@ -924,6 +1009,7 @@ function generateJS(ast, options) {
"};", "};",
"" ""
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( [ parts.push( [
@ -935,29 +1021,33 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.optimize === "size" ) { if ( options.optimize === "size" ) {
let startRuleIndices = "{ "
+ options.allowedStartRules.map( const startRuleIndices = "{ "
r => r + ": " + asts.indexOfRule(ast, r) + options.allowedStartRules
).join(", ") .map( r => r + ": " + asts.indexOfRule( ast, r ) )
.join( ", " )
+ " }"; + " }";
let startRuleIndex = asts.indexOfRule(ast, options.allowedStartRules[0]); const startRuleIndex = asts.indexOfRule( ast, options.allowedStartRules[ 0 ] );
parts.push( [ parts.push( [
" var peg$startRuleIndices = " + startRuleIndices + ";", " var peg$startRuleIndices = " + startRuleIndices + ";",
" var peg$startRuleIndex = " + startRuleIndex + ";" " var peg$startRuleIndex = " + startRuleIndex + ";"
].join( "\n" ) ); ].join( "\n" ) );
} else { } else {
let startRuleFunctions = "{ "
+ options.allowedStartRules.map( const startRuleFunctions = "{ "
r => r + ": peg$parse" + r + options.allowedStartRules
).join(", ") .map( r => r + ": peg$parse" + r )
.join( ", " )
+ " }"; + " }";
let startRuleFunction = "peg$parse" + options.allowedStartRules[0]; const startRuleFunction = "peg$parse" + options.allowedStartRules[ 0 ];
parts.push( [ parts.push( [
" var peg$startRuleFunctions = " + startRuleFunctions + ";", " var peg$startRuleFunctions = " + startRuleFunctions + ";",
" var peg$startRuleFunction = " + startRuleFunction + ";" " var peg$startRuleFunction = " + startRuleFunction + ";"
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( "" ); parts.push( "" );
@ -976,30 +1066,36 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.cache ) { if ( options.cache ) {
parts.push( [ parts.push( [
" var peg$resultsCache = {};", " var peg$resultsCache = {};",
"" ""
].join( "\n" ) ); ].join( "\n" ) );
} }
if ( options.trace ) { if ( options.trace ) {
if ( options.optimize === "size" ) { if ( options.optimize === "size" ) {
let ruleNames = "["
+ ast.rules.map( const ruleNames = "["
r => "\"" + js.stringEscape(r.name) + "\"" + ast.rules
).join(", ") .map( r => `"${ js.stringEscape( r.name ) }"` )
.join( ", " )
+ "]"; + "]";
parts.push( [ parts.push( [
" var peg$ruleNames = " + ruleNames + ";", " var peg$ruleNames = " + ruleNames + ";",
"" ""
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( [ parts.push( [
" var peg$tracer = \"tracer\" in options ? options.tracer : new peg$DefaultTracer();", " var peg$tracer = \"tracer\" in options ? options.tracer : new peg$DefaultTracer();",
"" ""
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( [ parts.push( [
@ -1008,6 +1104,7 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.optimize === "size" ) { if ( options.optimize === "size" ) {
parts.push( [ parts.push( [
" if (\"startRule\" in options) {", " if (\"startRule\" in options) {",
" if (!(options.startRule in peg$startRuleIndices)) {", " if (!(options.startRule in peg$startRuleIndices)) {",
@ -1017,7 +1114,9 @@ function generateJS(ast, options) {
" peg$startRuleIndex = peg$startRuleIndices[options.startRule];", " peg$startRuleIndex = peg$startRuleIndices[options.startRule];",
" }" " }"
].join( "\n" ) ); ].join( "\n" ) );
} else { } else {
parts.push( [ parts.push( [
" if (\"startRule\" in options) {", " if (\"startRule\" in options) {",
" if (!(options.startRule in peg$startRuleFunctions)) {", " if (!(options.startRule in peg$startRuleFunctions)) {",
@ -1027,6 +1126,7 @@ function generateJS(ast, options) {
" peg$startRuleFunction = peg$startRuleFunctions[options.startRule];", " peg$startRuleFunction = peg$startRuleFunctions[options.startRule];",
" }" " }"
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( [ parts.push( [
@ -1167,24 +1267,36 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.optimize === "size" ) { if ( options.optimize === "size" ) {
parts.push( indent2( generateInterpreter() ) ); parts.push( indent2( generateInterpreter() ) );
parts.push( "" ); parts.push( "" );
} else { } else {
ast.rules.forEach( rule => { ast.rules.forEach( rule => {
parts.push( indent2( generateRuleFunction( rule ) ) ); parts.push( indent2( generateRuleFunction( rule ) ) );
parts.push( "" ); parts.push( "" );
} ); } );
} }
if ( ast.initializer ) { if ( ast.initializer ) {
parts.push( indent2( ast.initializer.code ) ); parts.push( indent2( ast.initializer.code ) );
parts.push( "" ); parts.push( "" );
} }
if ( options.optimize === "size" ) { if ( options.optimize === "size" ) {
parts.push( " peg$result = peg$parseRule(peg$startRuleIndex);" ); parts.push( " peg$result = peg$parseRule(peg$startRuleIndex);" );
} else { } else {
parts.push( " peg$result = peg$startRuleFunction();" ); parts.push( " peg$result = peg$startRuleFunction();" );
} }
parts.push( [ parts.push( [
@ -1208,18 +1320,23 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
return parts.join( "\n" ); return parts.join( "\n" );
} }
function generateWrapper( toplevelCode ) { function generateWrapper( toplevelCode ) {
function generateGeneratedByComment() { function generateGeneratedByComment() {
return [ return [
"// Generated by PEG.js 0.10.0.", "// Generated by PEG.js 0.10.0.",
"//", "//",
"// https://pegjs.org/" "// https://pegjs.org/"
].join( "\n" ); ].join( "\n" );
} }
function generateParserObject() { function generateParserObject() {
return options.trace return options.trace
? [ ? [
"{", "{",
@ -1234,9 +1351,11 @@ function generateJS(ast, options) {
" parse: peg$parse", " parse: peg$parse",
"}" "}"
].join( "\n" ); ].join( "\n" );
} }
function generateParserExports() { function generateParserExports() {
return options.trace return options.trace
? [ ? [
"{", "{",
@ -1251,10 +1370,12 @@ function generateJS(ast, options) {
" peg$parse as parse", " peg$parse as parse",
"}" "}"
].join( "\n" ); ].join( "\n" );
} }
let generators = { const generators = {
bare() { bare() {
return [ return [
generateGeneratedByComment(), generateGeneratedByComment(),
"(function() {", "(function() {",
@ -1265,11 +1386,13 @@ function generateJS(ast, options) {
indent2( "return " + generateParserObject() + ";" ), indent2( "return " + generateParserObject() + ";" ),
"})()" "})()"
].join( "\n" ); ].join( "\n" );
}, },
commonjs() { commonjs() {
let parts = [];
let dependencyVars = Object.keys(options.dependencies); const parts = [];
const dependencyVars = Object.keys( options.dependencies );
parts.push( [ parts.push( [
generateGeneratedByComment(), generateGeneratedByComment(),
@ -1279,14 +1402,18 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( dependencyVars.length > 0 ) { if ( dependencyVars.length > 0 ) {
dependencyVars.forEach( variable => { dependencyVars.forEach( variable => {
parts.push( "var " + variable parts.push( "var " + variable
+ " = require(\"" + " = require(\""
+ js.stringEscape( options.dependencies[ variable ] ) + js.stringEscape( options.dependencies[ variable ] )
+ "\");" + "\");"
); );
} ); } );
parts.push( "" ); parts.push( "" );
} }
parts.push( [ parts.push( [
@ -1297,11 +1424,13 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
return parts.join( "\n" ); return parts.join( "\n" );
}, },
es() { es() {
let parts = [];
let dependencyVars = Object.keys(options.dependencies); const parts = [];
const dependencyVars = Object.keys( options.dependencies );
parts.push( parts.push(
generateGeneratedByComment(), generateGeneratedByComment(),
@ -1309,14 +1438,18 @@ function generateJS(ast, options) {
); );
if ( dependencyVars.length > 0 ) { if ( dependencyVars.length > 0 ) {
dependencyVars.forEach( variable => { dependencyVars.forEach( variable => {
parts.push( "import " + variable parts.push( "import " + variable
+ " from \"" + " from \""
+ js.stringEscape( options.dependencies[ variable ] ) + js.stringEscape( options.dependencies[ variable ] )
+ "\";" + "\";"
); );
} ); } );
parts.push( "" ); parts.push( "" );
} }
parts.push( parts.push(
@ -1329,17 +1462,19 @@ function generateJS(ast, options) {
); );
return parts.join( "\n" ); return parts.join( "\n" );
}, },
amd() { amd() {
let dependencyVars = Object.keys(options.dependencies);
let dependencyIds = dependencyVars.map(v => options.dependencies[v]); const dependencyVars = Object.keys( options.dependencies );
let dependencies = "[" const dependencyIds = dependencyVars.map( v => options.dependencies[ v ] );
+ dependencyIds.map( const dependencies = "["
id => "\"" + js.stringEscape(id) + "\"" + dependencyIds
).join(", ") .map( id => `"${ js.stringEscape( id ) }"` )
.join( ", " )
+ "]"; + "]";
let params = dependencyVars.join(", "); const params = dependencyVars.join( ", " );
return [ return [
generateGeneratedByComment(), generateGeneratedByComment(),
@ -1352,9 +1487,11 @@ function generateJS(ast, options) {
"});", "});",
"" ""
].join( "\n" ); ].join( "\n" );
}, },
globals() { globals() {
return [ return [
generateGeneratedByComment(), generateGeneratedByComment(),
"(function(root) {", "(function(root) {",
@ -1366,21 +1503,23 @@ function generateJS(ast, options) {
"})(this);", "})(this);",
"" ""
].join( "\n" ); ].join( "\n" );
}, },
umd() { umd() {
let parts = [];
let dependencyVars = Object.keys(options.dependencies); const parts = [];
let dependencyIds = dependencyVars.map(v => options.dependencies[v]); const dependencyVars = Object.keys( options.dependencies );
let dependencies = "[" const dependencyIds = dependencyVars.map( v => options.dependencies[ v ] );
+ dependencyIds.map( const dependencies = "["
id => "\"" + js.stringEscape(id) + "\"" + dependencyIds
).join(", ") .map( id => `"${ js.stringEscape( id ) }"` )
.join( ", " )
+ "]"; + "]";
let requires = dependencyIds.map( const requires = dependencyIds
id => "require(\"" + js.stringEscape(id) + "\")" .map( id => `require("${ js.stringEscape( id ) }")` )
).join(", "); .join( ", " );
let params = dependencyVars.join(", "); const params = dependencyVars.join( ", " );
parts.push( [ parts.push( [
generateGeneratedByComment(), generateGeneratedByComment(),
@ -1392,10 +1531,12 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
if ( options.exportVar !== null ) { if ( options.exportVar !== null ) {
parts.push( [ parts.push( [
" } else {", " } else {",
" root." + options.exportVar + " = factory();" " root." + options.exportVar + " = factory();"
].join( "\n" ) ); ].join( "\n" ) );
} }
parts.push( [ parts.push( [
@ -1411,13 +1552,16 @@ function generateJS(ast, options) {
].join( "\n" ) ); ].join( "\n" ) );
return parts.join( "\n" ); return parts.join( "\n" );
} }
}; };
return generators[ options.format ](); return generators[ options.format ]();
} }
ast.code = generateWrapper( generateToplevel() ); ast.code = generateWrapper( generateToplevel() );
} }
module.exports = generateJS; module.exports = generateJS;

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

@ -1,50 +1,70 @@
"use strict"; "use strict";
let GrammarError = require("../../grammar-error"); const GrammarError = require( "../../grammar-error" );
let visitor = require("../visitor"); const visitor = require( "../visitor" );
// Checks that each label is defined only once within each scope. // Checks that each label is defined only once within each scope.
function reportDuplicateLabels( ast ) { function reportDuplicateLabels( ast ) {
let check;
function cloneEnv( env ) { function cloneEnv( env ) {
let clone = {};
const clone = {};
Object.keys( env ).forEach( name => { Object.keys( env ).forEach( name => {
clone[ name ] = env[ name ]; clone[ name ] = env[ name ];
} ); } );
return clone; return clone;
} }
function checkExpressionWithClonedEnv( node, env ) { function checkExpressionWithClonedEnv( node, env ) {
check( node.expression, cloneEnv( env ) ); check( node.expression, cloneEnv( env ) );
} }
let check = visitor.build({ check = visitor.build( {
rule( node ) { rule( node ) {
check( node.expression, {} ); check( node.expression, {} );
}, },
choice( node, env ) { choice( node, env ) {
node.alternatives.forEach( alternative => { node.alternatives.forEach( alternative => {
check( alternative, cloneEnv( env ) ); check( alternative, cloneEnv( env ) );
} ); } );
}, },
action: checkExpressionWithClonedEnv, action: checkExpressionWithClonedEnv,
labeled( node, env ) { labeled( node, env ) {
if (Object.prototype.hasOwnProperty.call(env, node.label)) {
const label = node.label;
if ( Object.prototype.hasOwnProperty.call( env, label ) ) {
const start = env[ label ].start;
throw new GrammarError( throw new GrammarError(
"Label \"" + node.label + "\" is already defined " `Label "${ label }" is already defined at line ${ start.line }, column ${ start.column }.`,
+ "at line " + env[node.label].start.line + ", "
+ "column " + env[node.label].start.column + ".",
node.location node.location
); );
} }
check( node.expression, env ); check( node.expression, env );
env[ label ] = node.location;
env[node.label] = node.location;
}, },
text: checkExpressionWithClonedEnv, text: checkExpressionWithClonedEnv,
@ -57,6 +77,7 @@ function reportDuplicateLabels(ast) {
} ); } );
check( ast ); check( ast );
} }
module.exports = reportDuplicateLabels; module.exports = reportDuplicateLabels;

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

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

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

@ -1,32 +1,43 @@
"use strict"; "use strict";
let GrammarError = require("../../grammar-error"); const GrammarError = require( "../../grammar-error" );
let asts = require("../asts"); const asts = require( "../asts" );
let visitor = require("../visitor"); const visitor = require( "../visitor" );
// Checks that all referenced rules exist. // Checks that all referenced rules exist.
function reportUndefinedRules( ast, options ) { function reportUndefinedRules( ast, options ) {
let check = visitor.build({
const check = visitor.build( {
rule_ref( node ) { rule_ref( node ) {
if ( ! asts.findRule( ast, node.name ) ) { if ( ! asts.findRule( ast, node.name ) ) {
throw new GrammarError( throw new GrammarError(
"Rule \"" + node.name + "\" is not defined.", `Rule "${ node.name }" is not defined.`,
node.location node.location
); );
} }
} }
} ); } );
check( ast ); check( ast );
if ( options.allowedStartRules ) { if ( options.allowedStartRules ) {
options.allowedStartRules.forEach( rule => { options.allowedStartRules.forEach( rule => {
if ( ! asts.findRule( ast, rule ) ) { if ( ! asts.findRule( ast, rule ) ) {
throw new GrammarError(
"Start rule \"" + rule + "\" is not defined."); throw new GrammarError( `Start rule "${ rule }" is not defined.` );
} }
} ); } );
} }
} }
module.exports = reportUndefinedRules; module.exports = reportUndefinedRules;

@ -1,10 +1,13 @@
"use strict"; "use strict";
// Simple AST node visitor builder. // Simple AST node visitor builder.
let visitor = { const visitor = {
build( functions ) { build( functions ) {
function visit( node ) { function visit( node ) {
return functions[ node.type ].apply( null, arguments ); return functions[ node.type ].apply( null, arguments );
} }
function visitNop() { function visitNop() {
@ -12,32 +15,46 @@ let visitor = {
} }
function visitExpression( node ) { function visitExpression( node ) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
visit.apply(null, [node.expression].concat(extraArgs)); const extraArgs = Array.prototype.slice.call( arguments, 1 );
visit( ...[ node.expression ].concat( extraArgs ) );
} }
function visitChildren( property ) { function visitChildren( property ) {
return function(node) {
let extraArgs = Array.prototype.slice.call(arguments, 1); return function visitProperty( node ) {
const extraArgs = Array.prototype.slice.call( arguments, 1 );
node[ property ].forEach( child => { node[ property ].forEach( child => {
visit.apply(null, [child].concat(extraArgs));
visit( ...[ child ].concat( extraArgs ) );
} ); } );
}; };
} }
const DEFAULT_FUNCTIONS = { const DEFAULT_FUNCTIONS = {
grammar( node ) { grammar( node ) {
let extraArgs = Array.prototype.slice.call(arguments, 1);
const extraArgs = Array.prototype.slice.call( arguments, 1 );
if ( node.initializer ) { if ( node.initializer ) {
visit.apply(null, [node.initializer].concat(extraArgs));
visit( ...[ node.initializer ].concat( extraArgs ) );
} }
node.rules.forEach( rule => { node.rules.forEach( rule => {
visit.apply(null, [rule].concat(extraArgs));
visit( ...[ rule ].concat( extraArgs ) );
} ); } );
}, },
initializer: visitNop, initializer: visitNop,
@ -63,12 +80,17 @@ let visitor = {
}; };
Object.keys( DEFAULT_FUNCTIONS ).forEach( type => { Object.keys( DEFAULT_FUNCTIONS ).forEach( type => {
if ( ! Object.prototype.hasOwnProperty.call( functions, type ) ) { if ( ! Object.prototype.hasOwnProperty.call( functions, type ) ) {
functions[ type ] = DEFAULT_FUNCTIONS[ type ]; functions[ type ] = DEFAULT_FUNCTIONS[ type ];
} }
} ); } );
return visit; return visit;
} }
}; };

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

@ -1,10 +1,10 @@
"use strict"; "use strict";
let GrammarError = require("./grammar-error"); const GrammarError = require( "./grammar-error" );
let compiler = require("./compiler"); const compiler = require( "./compiler" );
let parser = require("./parser"); const parser = require( "./parser" );
let peg = { const peg = {
// PEG.js version (uses semantic versioning). // PEG.js version (uses semantic versioning).
VERSION: "0.10.0", VERSION: "0.10.0",
@ -22,32 +22,42 @@ let peg = {
// errors are detected during the generation and some may protrude to the // errors are detected during the generation and some may protrude to the
// generated parser and cause its malfunction. // generated parser and cause its malfunction.
generate( grammar, options ) { generate( grammar, options ) {
options = options !== undefined ? options : {};
options = typeof options !== "undefined" ? options : {};
function convertPasses( passes ) { function convertPasses( passes ) {
let converted = {};
const converted = {};
Object.keys( passes ).forEach( stage => { Object.keys( passes ).forEach( stage => {
converted[ stage ] = Object.keys( passes[ stage ] ) converted[ stage ] = Object.keys( passes[ stage ] )
.map( name => passes[ stage ][ name ] ); .map( name => passes[ stage ][ name ] );
} ); } );
return converted; return converted;
} }
let plugins = "plugins" in options ? options.plugins : []; const plugins = "plugins" in options ? options.plugins : [];
let config = { const config = {
parser: peg.parser, parser: peg.parser,
passes: convertPasses( peg.compiler.passes ) passes: convertPasses( peg.compiler.passes )
}; };
plugins.forEach(p => { p.use(config, options); }); plugins.forEach( p => {
p.use( config, options );
} );
return peg.compiler.compile( return peg.compiler.compile(
config.parser.parse( grammar ), config.parser.parse( grammar ),
config.passes, config.passes,
options options
); );
} }
}; };

@ -59,6 +59,7 @@
"devDependencies": { "devDependencies": {
"babel-preset-es2015": "6.24.1", "babel-preset-es2015": "6.24.1",
"babel-core": "6.26.0", "babel-core": "6.26.0",
"dedent": "0.7.0",
"babelify": "8.0.0", "babelify": "8.0.0",
"browserify": "14.5.0", "browserify": "14.5.0",
"chai": "4.1.2", "chai": "4.1.2",

@ -5,8 +5,10 @@ module.exports = {
"extends": "futagozaryuu/test", "extends": "futagozaryuu/test",
"rules": { "rules": {
"node/shebang": 0 "node/shebang": 0,
"func-names": 0,
"no-mixed-operators": 0,
} },
}; };

@ -1,6 +1,6 @@
"use strict"; "use strict";
let benchmarks = [ const benchmarks = [
{ {
id: "json", id: "json",
title: "JSON", title: "JSON",

@ -2,15 +2,17 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
let Runner = require("./runner.js"); const Runner = require( "./runner.js" );
let benchmarks = require("./benchmarks.js"); const benchmarks = require( "./benchmarks.js" );
$( "#run" ).click( () => { $( "#run" ).click( () => {
// Results Table Manipulation // Results Table Manipulation
let resultsTable = $("#results-table"); const resultsTable = $( "#results-table" );
function appendResult( klass, title, url, inputSize, parseTime ) { function appendResult( klass, title, url, inputSize, parseTime ) {
const KB = 1024; const KB = 1024;
const MS_IN_S = 1000; const MS_IN_S = 1000;
@ -41,6 +43,7 @@ $("#run").click(() => {
+ "</td>" + "</td>"
+ "</tr>" + "</tr>"
); );
} }
// Main // Main
@ -54,26 +57,30 @@ $("#run").click(() => {
// //
// 2. To minimize random errors. // 2. To minimize random errors.
let runCount = parseInt($("#run-count").val(), 10); const runCount = parseInt( $( "#run-count" ).val(), 10 );
let options = { const options = {
cache: $( "#cache" ).is( ":checked" ), cache: $( "#cache" ).is( ":checked" ),
optimize: $( "#optimize" ).val() optimize: $( "#optimize" ).val()
}; };
if ( isNaN( runCount ) || runCount <= 0 ) { if ( isNaN( runCount ) || runCount <= 0 ) {
alert( "Number of runs must be a positive integer." ); alert( "Number of runs must be a positive integer." );
return; return;
} }
Runner.run( benchmarks, runCount, options, { Runner.run( benchmarks, runCount, options, {
readFile( file ) { readFile( file ) {
return $.ajax( { return $.ajax( {
type: "GET", type: "GET",
url: "benchmark/" + file, url: "benchmark/" + file,
dataType: "text", dataType: "text",
async: false async: false
} ).responseText; } ).responseText;
}, },
testStart() { testStart() {
@ -81,6 +88,7 @@ $("#run").click(() => {
}, },
testFinish( benchmark, test, inputSize, parseTime ) { testFinish( benchmark, test, inputSize, parseTime ) {
appendResult( appendResult(
"individual", "individual",
test.title, test.title,
@ -88,9 +96,11 @@ $("#run").click(() => {
inputSize, inputSize,
parseTime parseTime
); );
}, },
benchmarkStart( benchmark ) { benchmarkStart( benchmark ) {
resultsTable.append( resultsTable.append(
"<tr class='heading'><th colspan='4'>" "<tr class='heading'><th colspan='4'>"
+ "<a href='../../examples/" + benchmark.id + ".pegjs'>" + "<a href='../../examples/" + benchmark.id + ".pegjs'>"
@ -98,9 +108,11 @@ $("#run").click(() => {
+ "</a>" + "</a>"
+ "</th></tr>" + "</th></tr>"
); );
}, },
benchmarkFinish( benchmark, inputSize, parseTime ) { benchmarkFinish( benchmark, inputSize, parseTime ) {
appendResult( appendResult(
"benchmark-total", "benchmark-total",
benchmark.title + " total", benchmark.title + " total",
@ -108,16 +120,20 @@ $("#run").click(() => {
inputSize, inputSize,
parseTime parseTime
); );
}, },
start() { start() {
$( "#run-count, #cache, #run" ).attr( "disabled", "disabled" ); $( "#run-count, #cache, #run" ).attr( "disabled", "disabled" );
resultsTable.show(); resultsTable.show();
$( "#results-table tr" ).slice( 1 ).remove(); $( "#results-table tr" ).slice( 1 ).remove();
}, },
finish( inputSize, parseTime ) { finish( inputSize, parseTime ) {
appendResult( appendResult(
"total", "total",
"Total", "Total",
@ -127,12 +143,11 @@ $("#run").click(() => {
); );
$.scrollTo( "max", { axis: "y", duration: 500 } ); $.scrollTo( "max", { axis: "y", duration: 500 } );
$( "#run-count, #cache, #run" ).removeAttr( "disabled" ); $( "#run-count, #cache, #run" ).removeAttr( "disabled" );
} }
} ); } );
});
$(document).ready(() => {
$("#run").focus();
} ); } );
$( document ).ready( () => $( "#run" ).focus() );

@ -2,76 +2,95 @@
"use strict"; "use strict";
let Runner = require("./runner.js"); const Runner = require( "./runner.js" );
let benchmarks = require("./benchmarks.js"); const benchmarks = require( "./benchmarks.js" );
let fs = require("fs"); const fs = require( "fs" );
const path = require( "path" );
// Results Table Manipulation // Results Table Manipulation
function dup( text, count ) { function dup( text, count ) {
let result = ""; let result = "";
for (let i = 1; i <= count; i++) { for ( let i = 1; i <= count; i++ ) result += text;
result += text;
}
return result; return result;
} }
function padLeft( text, length ) { function padLeft( text, length ) {
return dup( " ", length - text.length ) + text; return dup( " ", length - text.length ) + text;
} }
function padRight( text, length ) { function padRight( text, length ) {
return text + dup( " ", length - text.length ); return text + dup( " ", length - text.length );
} }
function center( text, length ) { function center( text, length ) {
let padLength = (length - text.length) / 2;
const padLength = ( length - text.length ) / 2;
return dup( " ", Math.floor( padLength ) ) return dup( " ", Math.floor( padLength ) )
+ text + text
+ dup( " ", Math.ceil( padLength ) ); + dup( " ", Math.ceil( padLength ) );
} }
function writeTableHeader() { function writeTableHeader() {
console.log( "┌─────────────────────────────────────┬───────────┬────────────┬──────────────┐" ); console.log( "┌─────────────────────────────────────┬───────────┬────────────┬──────────────┐" );
console.log( "│ Test │ Inp. size │ Avg. time │ Avg. speed │" ); console.log( "│ Test │ Inp. size │ Avg. time │ Avg. speed │" );
} }
function writeHeading( heading ) { function writeHeading( heading ) {
console.log( "├─────────────────────────────────────┴───────────┴────────────┴──────────────┤" ); console.log( "├─────────────────────────────────────┴───────────┴────────────┴──────────────┤" );
console.log( "│ " + center( heading, 75 ) + " │" ); console.log( "│ " + center( heading, 75 ) + " │" );
console.log( "├─────────────────────────────────────┬───────────┬────────────┬──────────────┤" ); console.log( "├─────────────────────────────────────┬───────────┬────────────┬──────────────┤" );
} }
function writeResult( title, inputSize, parseTime ) { function writeResult( title, inputSize, parseTime ) {
const KB = 1024; const KB = 1024;
const MS_IN_S = 1000; const MS_IN_S = 1000;
console.log("│ " console.log(
+ padRight(title, 35) "│ " +
+ " │ " padRight( title, 35 ) +
+ padLeft((inputSize / KB).toFixed(2), 6) " │ " +
+ " kB │ " padLeft( ( inputSize / KB ).toFixed( 2 ), 6 ) +
+ padLeft(parseTime.toFixed(2), 7) " kB │ " +
+ " ms │ " padLeft( parseTime.toFixed( 2 ), 7 ) +
+ padLeft(((inputSize / KB) / (parseTime / MS_IN_S)).toFixed(2), 7) " ms │ " +
+ " kB/s │" padLeft( ( ( inputSize / KB ) / ( parseTime / MS_IN_S ) ).toFixed( 2 ), 7 ) +
" kB/s │"
); );
} }
function writeSeparator() { function writeSeparator() {
console.log( "├─────────────────────────────────────┼───────────┼────────────┼──────────────┤" ); console.log( "├─────────────────────────────────────┼───────────┼────────────┼──────────────┤" );
} }
function writeTableFooter() { function writeTableFooter() {
console.log( "└─────────────────────────────────────┴───────────┴────────────┴──────────────┘" ); console.log( "└─────────────────────────────────────┴───────────┴────────────┴──────────────┘" );
} }
// Helpers // Helpers
function printHelp() { function printHelp() {
console.log( "Usage: run [options]" ); console.log( "Usage: run [options]" );
console.log( "" ); console.log( "" );
console.log( "Runs PEG.js benchmark suite." ); console.log( "Runs PEG.js benchmark suite." );
@ -81,52 +100,69 @@ function printHelp() {
console.log( " --cache make tested parsers cache results" ); console.log( " --cache make tested parsers cache results" );
console.log( " -o, --optimize <goal> select optimization for speed or size (default:" ); console.log( " -o, --optimize <goal> select optimization for speed or size (default:" );
console.log( " speed)" ); console.log( " speed)" );
} }
function exitSuccess() { function exitSuccess() {
process.exit( 0 ); process.exit( 0 );
} }
function exitFailure() { function exitFailure() {
process.exit( 1 ); process.exit( 1 );
} }
function abort( message ) { function abort( message ) {
console.error( message ); console.error( message );
exitFailure(); exitFailure();
} }
// Arguments // 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 ) { function isOption( arg ) {
return ( /^-/ ).test( arg ); return ( /^-/ ).test( arg );
} }
function nextArg() { function nextArg() {
args.shift(); args.shift();
} }
// Main // Main
let runCount = 10; let runCount = 10;
let options = { const options = {
cache: false, cache: false,
optimize: "speed" optimize: "speed"
}; };
while ( args.length > 0 && isOption( args[ 0 ] ) ) { while ( args.length > 0 && isOption( args[ 0 ] ) ) {
switch ( args[ 0 ] ) { switch ( args[ 0 ] ) {
case "-n": case "-n":
case "--run-count": case "--run-count":
nextArg(); nextArg();
if ( args.length === 0 ) { if ( args.length === 0 ) {
abort( "Missing parameter of the -n/--run-count option." ); abort( "Missing parameter of the -n/--run-count option." );
} }
runCount = parseInt( args[ 0 ], 10 ); runCount = parseInt( args[ 0 ], 10 );
if ( isNaN( runCount ) || runCount <= 0 ) { if ( isNaN( runCount ) || runCount <= 0 ) {
abort( "Number of runs must be a positive integer." ); abort( "Number of runs must be a positive integer." );
} }
break; break;
@ -138,10 +174,14 @@ while (args.length > 0 && isOption(args[0])) {
case "--optimize": case "--optimize":
nextArg(); nextArg();
if ( args.length === 0 ) { if ( args.length === 0 ) {
abort( "Missing parameter of the -o/--optimize option." ); abort( "Missing parameter of the -o/--optimize option." );
} }
if ( args[ 0 ] !== "speed" && args[ 0 ] !== "size" ) { if ( args[ 0 ] !== "speed" && args[ 0 ] !== "size" ) {
abort( "Optimization goal must be either \"speed\" or \"size\"." ); abort( "Optimization goal must be either \"speed\" or \"size\"." );
} }
options.optimize = args[ 0 ]; options.optimize = args[ 0 ];
break; break;
@ -154,17 +194,23 @@ while (args.length > 0 && isOption(args[0])) {
default: default:
abort( "Unknown option: " + args[ 0 ] + "." ); abort( "Unknown option: " + args[ 0 ] + "." );
} }
nextArg(); nextArg();
} }
if ( args.length > 0 ) { if ( args.length > 0 ) {
abort( "No arguments are allowed." ); abort( "No arguments are allowed." );
} }
Runner.run( benchmarks, runCount, options, { Runner.run( benchmarks, runCount, options, {
readFile( file ) { readFile( file ) {
return fs.readFileSync(__dirname + "/" + file, "utf8");
return fs.readFileSync( path.join( __dirname, file ), "utf8" );
}, },
testStart() { testStart() {
@ -172,25 +218,35 @@ Runner.run(benchmarks, runCount, options, {
}, },
testFinish( benchmark, test, inputSize, parseTime ) { testFinish( benchmark, test, inputSize, parseTime ) {
writeResult( test.title, inputSize, parseTime ); writeResult( test.title, inputSize, parseTime );
}, },
benchmarkStart( benchmark ) { benchmarkStart( benchmark ) {
writeHeading( benchmark.title ); writeHeading( benchmark.title );
}, },
benchmarkFinish( benchmark, inputSize, parseTime ) { benchmarkFinish( benchmark, inputSize, parseTime ) {
writeSeparator(); writeSeparator();
writeResult( benchmark.title + " total", inputSize, parseTime ); writeResult( benchmark.title + " total", inputSize, parseTime );
}, },
start() { start() {
writeTableHeader(); writeTableHeader();
}, },
finish( inputSize, parseTime ) { finish( inputSize, parseTime ) {
writeSeparator(); writeSeparator();
writeResult( "Total", inputSize, parseTime ); writeResult( "Total", inputSize, parseTime );
writeTableFooter(); writeTableFooter();
} }
} ); } );

@ -1,28 +1,37 @@
"use strict"; "use strict";
/* global setTimeout */ const peg = require( "../../lib/peg" );
let peg = require("../../lib/peg"); const Runner = {
let Runner = {
run( benchmarks, runCount, options, callbacks ) { run( benchmarks, runCount, options, callbacks ) {
// Queue // Queue
let Q = { const Q = {
functions: [], functions: [],
add( f ) { add( f ) {
this.functions.push( f ); this.functions.push( f );
}, },
run() { run() {
if ( this.functions.length > 0 ) { if ( this.functions.length > 0 ) {
this.functions.shift()(); this.functions.shift()();
// We can't use |arguments.callee| here because |this| would get // We can't use |arguments.callee| here because |this| would get
// messed-up in that case. // messed-up in that case.
setTimeout(() => { Q.run(); }, 0); setTimeout( () => {
Q.run();
}, 0 );
} }
} }
}; };
@ -39,17 +48,21 @@ let Runner = {
// The enqueued functions share state, which is all stored in the properties // The enqueued functions share state, which is all stored in the properties
// of the |state| object. // of the |state| object.
let state = {}; const state = {};
function initialize() { function initialize() {
callbacks.start(); callbacks.start();
state.totalInputSize = 0; state.totalInputSize = 0;
state.totalParseTime = 0; state.totalParseTime = 0;
} }
function benchmarkInitializer( benchmark ) { function benchmarkInitializer( benchmark ) {
return function () { return function () {
callbacks.benchmarkStart( benchmark ); callbacks.benchmarkStart( benchmark );
state.parser = peg.generate( state.parser = peg.generate(
@ -58,32 +71,42 @@ let Runner = {
); );
state.benchmarkInputSize = 0; state.benchmarkInputSize = 0;
state.benchmarkParseTime = 0; state.benchmarkParseTime = 0;
}; };
} }
function testRunner( benchmark, test ) { function testRunner( benchmark, test ) {
return function () { return function () {
callbacks.testStart( benchmark, test ); callbacks.testStart( benchmark, test );
let input = callbacks.readFile(benchmark.id + "/" + test.file); const input = callbacks.readFile( benchmark.id + "/" + test.file );
let parseTime = 0; let parseTime = 0;
for ( let i = 0; i < runCount; i++ ) { for ( let i = 0; i < runCount; i++ ) {
let t = (new Date()).getTime();
const t = ( new Date() ).getTime();
state.parser.parse( input ); state.parser.parse( input );
parseTime += ( new Date() ).getTime() - t; parseTime += ( new Date() ).getTime() - t;
} }
let averageParseTime = parseTime / runCount; const averageParseTime = parseTime / runCount;
callbacks.testFinish( benchmark, test, input.length, averageParseTime ); callbacks.testFinish( benchmark, test, input.length, averageParseTime );
state.benchmarkInputSize += input.length; state.benchmarkInputSize += input.length;
state.benchmarkParseTime += averageParseTime; state.benchmarkParseTime += averageParseTime;
}; };
} }
function benchmarkFinalizer( benchmark ) { function benchmarkFinalizer( benchmark ) {
return function () { return function () {
callbacks.benchmarkFinish( callbacks.benchmarkFinish(
benchmark, benchmark,
state.benchmarkInputSize, state.benchmarkInputSize,
@ -92,26 +115,35 @@ let Runner = {
state.totalInputSize += state.benchmarkInputSize; state.totalInputSize += state.benchmarkInputSize;
state.totalParseTime += state.benchmarkParseTime; state.totalParseTime += state.benchmarkParseTime;
}; };
} }
function finalize() { function finalize() {
callbacks.finish( state.totalInputSize, state.totalParseTime ); callbacks.finish( state.totalInputSize, state.totalParseTime );
} }
// Main // Main
Q.add( initialize ); Q.add( initialize );
benchmarks.forEach( benchmark => { benchmarks.forEach( benchmark => {
Q.add( benchmarkInitializer( benchmark ) ); Q.add( benchmarkInitializer( benchmark ) );
benchmark.tests.forEach( test => { benchmark.tests.forEach( test => {
Q.add( testRunner( benchmark, test ) ); Q.add( testRunner( benchmark, test ) );
} ); } );
Q.add( benchmarkFinalizer( benchmark ) ); Q.add( benchmarkFinalizer( benchmark ) );
} ); } );
Q.add( finalize ); Q.add( finalize );
Q.run(); Q.run();
} }
}; };

@ -1,27 +1,25 @@
#!/usr/bin/env node #!/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 // 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. // speed and size. Makes sense to use only on PEG.js git repository checkout.
// //
/* eslint prefer-const: 0 */
"use strict"; "use strict";
let child_process = require("child_process"); const child_process = require( "child_process" );
let fs = require("fs"); const fs = require( "fs" );
let os = require("os"); const os = require( "os" );
let path = require("path"); const path = require( "path" );
let glob = require("glob"); const dedent = require( "dedent" );
const glob = require( "glob" );
// Current Working Directory // Current Working Directory
let cwd = path.join(__dirname, ".."); const cwd = path.join( __dirname, ".." );
if ( process.cwd() !== cwd ) process.chdir( cwd );
if (process.cwd() !== cwd) {
process.chdir(cwd);
}
// Execution Files // Execution Files
@ -29,89 +27,118 @@ let PEGJS_BIN = "bin/peg.js";
let BENCHMARK_BIN = "test/benchmark/run"; let BENCHMARK_BIN = "test/benchmark/run";
if ( ! fs.existsSync( PEGJS_BIN ) ) { if ( ! fs.existsSync( PEGJS_BIN ) ) {
PEGJS_BIN = "bin/pegjs"; PEGJS_BIN = "bin/pegjs";
} }
if ( ! fs.existsSync( BENCHMARK_BIN ) ) { if ( ! fs.existsSync( BENCHMARK_BIN ) ) {
BENCHMARK_BIN = "benchmark/run"; BENCHMARK_BIN = "benchmark/run";
} }
// Utils // Utils
let print = console.log;
function echo( message ) { function echo( message ) {
process.stdout.write( message ); process.stdout.write( message );
} }
function exec( command ) { function exec( command ) {
return child_process.execSync( command, { encoding: "utf8" } ); return child_process.execSync( command, { encoding: "utf8" } );
} }
function prepare( commit ) { function prepare( commit ) {
exec( `git checkout --quiet "${ commit }"` ); exec( `git checkout --quiet "${ commit }"` );
} }
function runBenchmark() { function runBenchmark() {
return parseFloat( return parseFloat(
exec( "node " + BENCHMARK_BIN ) exec( "node " + BENCHMARK_BIN )
// Split by table seprator, reverse and return the total bytes per second // Split by table seprator, reverse and return the total bytes per second
.split( "│" ) .split( "│" )
.reverse()[ 1 ] .reverse()[ 1 ]
// Trim the whitespaces and remove ` kB/s` from the end // Trim the whitespaces and remove ` kB/s` from the end
.trim() .trim()
.slice( 0, -5 ) .slice( 0, -5 )
); );
} }
function measureSpeed() { function measureSpeed() {
return ( runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() / 5 ).toFixed( 2 ); return ( runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() / 5 ).toFixed( 2 );
} }
function measureSize() { function measureSize() {
let size = 0; let size = 0;
glob.sync( "examples/*.pegjs" ) glob.sync( "examples/*.pegjs" )
.forEach( example => { .forEach( example => {
exec( `node ${ PEGJS_BIN } ${ example }` ); exec( `node ${ PEGJS_BIN } ${ example }` );
example = example.slice( 0, -5 ) + "js"; example = example.slice( 0, -5 ) + "js";
size += fs.statSync( example ).size; size += fs.statSync( example ).size;
fs.unlinkSync( example ); fs.unlinkSync( example );
} ); } );
return size; return size;
} }
function difference( $1, $2 ) { function difference( $1, $2 ) {
return ( ( $2 / $1 - 1 ) * 100 ).toFixed( 4 ); return ( ( $2 / $1 - 1 ) * 100 ).toFixed( 4 );
} }
// Prepare // Prepare
let argv = process.argv.slice(2); const argv = process.argv.slice( 2 );
let commit_before, commit_after; let commit_before, commit_after;
if ( argv.length === 1 ) { if ( argv.length === 1 ) {
commit_before = argv[ 0 ] + "~1"; commit_before = argv[ 0 ] + "~1";
commit_after = argv[ 0 ]; commit_after = argv[ 0 ];
} else if ( argv.length === 2 ) { } else if ( argv.length === 2 ) {
commit_before = argv[ 0 ]; commit_before = argv[ 0 ];
commit_after = argv[ 1 ]; commit_after = argv[ 1 ];
} else { } else {
print("Usage:");
print(""); console.log( dedent`
print(" test/impact <commit>");
print(" test/impact <commit_before> <commit_after>"); Usage:
print("");
print("Measures impact of a Git commit (or multiple commits) on generated parsers'"); test/impact <commit>
print("speed and size. Makes sense to use only on PEG.js Git repository checkout."); test/impact <commit_before> <commit_after>
print("");
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 ); process.exit( 1 );
} }
// Measure // Measure
let branch = exec("git rev-parse --abbrev-ref HEAD"); const branch = exec( "git rev-parse --abbrev-ref HEAD" );
let speed1, size1, speed2, size2; let speed1, size1, speed2, size2;
echo( `Measuring commit ${ commit_before }...` ); echo( `Measuring commit ${ commit_before }...` );
@ -130,7 +157,8 @@ echo(" OK" + os.EOL);
prepare( branch ); prepare( branch );
print(` console.log( dedent`
test/impact ${ commit_before } ${ commit_after } test/impact ${ commit_before } ${ commit_after }
Speed impact Speed impact
@ -147,4 +175,5 @@ test/impact ${commit_before} ${commit_after}
- Measured by /test/impact with Node.js ${ process.version } - Measured by /test/impact with Node.js ${ process.version }
- Your system: ${ os.type() } ${ os.release() } ${ os.arch() }. - Your system: ${ os.type() } ${ os.release() } ${ os.arch() }.
` ); ` );

@ -2,13 +2,13 @@
"use strict"; "use strict";
let babelify = require("babelify"); const babelify = require( "babelify" );
let browserify = require("browserify"); const browserify = require( "browserify" );
let express = require("express"); const express = require( "express" );
let glob = require("glob"); const glob = require( "glob" );
let logger = require("morgan"); const logger = require( "morgan" );
let app = express(); const app = express();
app.use( logger( "dev" ) ); app.use( logger( "dev" ) );
app.use( express.static( __dirname ) ); app.use( express.static( __dirname ) );
@ -16,6 +16,7 @@ app.use("/benchmark", express.static(`${__dirname}/../benchmark`));
app.use( "/examples", express.static( `${ __dirname }/../../examples` ) ); app.use( "/examples", express.static( `${ __dirname }/../../examples` ) );
app.get( "/:dir/bundle.js", ( req, res ) => { app.get( "/:dir/bundle.js", ( req, res ) => {
browserify( glob.sync( browserify( glob.sync(
`${ __dirname }/../${ req.params.dir }/**/*.js` `${ __dirname }/../${ req.params.dir }/**/*.js`
) ) ) )
@ -25,8 +26,11 @@ app.get("/:dir/bundle.js", (req, res) => {
} ) } )
.bundle() .bundle()
.pipe( res ); .pipe( res );
} ); } );
app.listen( 8000, () => { app.listen( 8000, () => {
console.log( "Test server running at: http://localhost:8000/" ); console.log( "Test server running at: http://localhost:8000/" );
} ); } );

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

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

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

File diff suppressed because it is too large Load Diff

@ -1,24 +1,33 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let helpers = require("./helpers"); const helpers = require( "./helpers" );
let pass = require("../../../../../lib/compiler/passes/generate-bytecode"); const pass = require( "../../../../../lib/compiler/passes/generate-bytecode" );
chai.use( helpers ); chai.use( helpers );
let expect = chai.expect; const expect = chai.expect;
describe( "compiler pass |generateBytecode|", function () { describe( "compiler pass |generateBytecode|", function () {
function bytecodeDetails( bytecode ) { function bytecodeDetails( bytecode ) {
return { return {
rules: [ { bytecode: bytecode } ] rules: [ { bytecode: bytecode } ]
}; };
} }
function constsDetails(consts) { return { consts: consts }; } function constsDetails( consts ) {
return { consts: consts };
}
describe( "for grammar", function () { describe( "for grammar", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( [ expect( pass ).to.changeAST( [
"a = 'a'", "a = 'a'",
"b = 'b'", "b = 'b'",
@ -30,9 +39,11 @@ describe("compiler pass |generateBytecode|", function() {
{ bytecode: [ 18, 4, 2, 2, 22, 4, 23, 5 ] } { bytecode: [ 18, 4, 2, 2, 22, 4, 23, 5 ] }
] ]
} ); } );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( [ expect( pass ).to.changeAST( [
"a = 'a'", "a = 'a'",
"b = 'b'", "b = 'b'",
@ -45,21 +56,29 @@ describe("compiler pass |generateBytecode|", function() {
"\"c\"", "\"c\"",
"peg$literalExpectation(\"c\", false)" "peg$literalExpectation(\"c\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for rule", function () { describe( "for rule", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = 'a'", bytecodeDetails( [ expect( pass ).to.changeAST( "start = 'a'", bytecodeDetails( [
18, 0, 2, 2, 22, 0, 23, 1 // <expression> 18, 0, 2, 2, 22, 0, 23, 1 // <expression>
] ) ); ] ) );
} ); } );
} ); } );
describe( "for named", function () { describe( "for named", function () {
let grammar = "start 'start' = 'a'";
const grammar = "start 'start' = 'a'";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
28, // SILENT_FAILS_ON 28, // SILENT_FAILS_ON
18, 1, 2, 2, 22, 1, 23, 2, // <expression> 18, 1, 2, 2, 22, 1, 23, 2, // <expression>
@ -67,19 +86,25 @@ describe("compiler pass |generateBytecode|", function() {
14, 2, 0, // IF_ERROR 14, 2, 0, // IF_ERROR
23, 0 // * FAIL 23, 0 // * FAIL
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"peg$otherExpectation(\"start\")", "peg$otherExpectation(\"start\")",
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)" "peg$literalExpectation(\"a\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for choice", function () { describe( "for choice", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = 'a' / 'b' / 'c'", bytecodeDetails( [ expect( pass ).to.changeAST( "start = 'a' / 'b' / 'c'", bytecodeDetails( [
18, 0, 2, 2, 22, 0, 23, 1, // <alternatives[0]> 18, 0, 2, 2, 22, 0, 23, 1, // <alternatives[0]>
14, 21, 0, // IF_ERROR 14, 21, 0, // IF_ERROR
@ -89,14 +114,19 @@ describe("compiler pass |generateBytecode|", function() {
6, // * POP 6, // * POP
18, 4, 2, 2, 22, 4, 23, 5 // <alternatives[2]> 18, 4, 2, 2, 22, 4, 23, 5 // <alternatives[2]>
] ) ); ] ) );
} ); } );
} ); } );
describe( "for action", function () { describe( "for action", function () {
describe( "without labels", function () { describe( "without labels", function () {
let grammar = "start = 'a' { code }";
const grammar = "start = 'a' { code }";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression> 18, 0, 2, 2, 22, 0, 23, 1, // <expression>
@ -105,21 +135,27 @@ describe("compiler pass |generateBytecode|", function() {
26, 2, 1, 0, // CALL 26, 2, 1, 0, // CALL
9 // NIP 9 // NIP
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)", "peg$literalExpectation(\"a\", false)",
"function() { code }" "function() { code }"
] ) ); ] ) );
} ); } );
} ); } );
describe( "with one label", function () { describe( "with one label", function () {
let grammar = "start = a:'a' { code }";
const grammar = "start = a:'a' { code }";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression> 18, 0, 2, 2, 22, 0, 23, 1, // <expression>
@ -128,21 +164,27 @@ describe("compiler pass |generateBytecode|", function() {
26, 2, 1, 1, 0, // CALL 26, 2, 1, 1, 0, // CALL
9 // NIP 9 // NIP
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)", "peg$literalExpectation(\"a\", false)",
"function(a) { code }" "function(a) { code }"
] ) ); ] ) );
} ); } );
} ); } );
describe( "with multiple labels", function () { describe( "with multiple labels", function () {
let grammar = "start = a:'a' b:'b' c:'c' { code }";
const grammar = "start = a:'a' b:'b' c:'c' { code }";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]> 18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
@ -163,9 +205,11 @@ describe("compiler pass |generateBytecode|", function() {
7, // POP_CURR_POS 7, // POP_CURR_POS
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)", "peg$literalExpectation(\"a\", false)",
@ -175,14 +219,19 @@ describe("compiler pass |generateBytecode|", function() {
"peg$literalExpectation(\"c\", false)", "peg$literalExpectation(\"c\", false)",
"function(a, b, c) { code }" "function(a, b, c) { code }"
] ) ); ] ) );
} ); } );
} ); } );
} ); } );
describe( "for sequence", function () { describe( "for sequence", function () {
let grammar = "start = 'a' 'b' 'c'";
const grammar = "start = 'a' 'b' 'c'";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]> 18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
@ -203,9 +252,11 @@ describe("compiler pass |generateBytecode|", function() {
7, // POP_CURR_POS 7, // POP_CURR_POS
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)", "peg$literalExpectation(\"a\", false)",
@ -214,19 +265,27 @@ describe("compiler pass |generateBytecode|", function() {
"\"c\"", "\"c\"",
"peg$literalExpectation(\"c\", false)" "peg$literalExpectation(\"c\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for labeled", function () { describe( "for labeled", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = a:'a'", bytecodeDetails( [ expect( pass ).to.changeAST( "start = a:'a'", bytecodeDetails( [
18, 0, 2, 2, 22, 0, 23, 1 // <expression> 18, 0, 2, 2, 22, 0, 23, 1 // <expression>
] ) ); ] ) );
} ); } );
} ); } );
describe( "for text", function () { describe( "for text", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = $'a'", bytecodeDetails( [ expect( pass ).to.changeAST( "start = $'a'", bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression> 18, 0, 2, 2, 22, 0, 23, 1, // <expression>
@ -235,13 +294,17 @@ describe("compiler pass |generateBytecode|", function() {
12, // TEXT 12, // TEXT
9 // * NIP 9 // * NIP
] ) ); ] ) );
} ); } );
} ); } );
describe( "for simple_and", function () { describe( "for simple_and", function () {
let grammar = "start = &'a'";
const grammar = "start = &'a'";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
28, // SILENT_FAILS_ON 28, // SILENT_FAILS_ON
@ -255,20 +318,26 @@ describe("compiler pass |generateBytecode|", function() {
6, // POP 6, // POP
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)" "peg$literalExpectation(\"a\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for simple_not", function () { describe( "for simple_not", function () {
let grammar = "start = !'a'";
const grammar = "start = !'a'";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
28, // SILENT_FAILS_ON 28, // SILENT_FAILS_ON
@ -282,40 +351,52 @@ describe("compiler pass |generateBytecode|", function() {
7, // POP_CURR_POS 7, // POP_CURR_POS
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)" "peg$literalExpectation(\"a\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for optional", function () { describe( "for optional", function () {
let grammar = "start = 'a'?";
const grammar = "start = 'a'?";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
18, 0, 2, 2, 22, 0, 23, 1, // <expression> 18, 0, 2, 2, 22, 0, 23, 1, // <expression>
14, 2, 0, // IF_ERROR 14, 2, 0, // IF_ERROR
6, // * POP 6, // * POP
2 // PUSH_NULL 2 // PUSH_NULL
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)" "peg$literalExpectation(\"a\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for zero_or_more", function () { describe( "for zero_or_more", function () {
let grammar = "start = 'a'*";
const grammar = "start = 'a'*";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
4, // PUSH_EMPTY_ARRAY 4, // PUSH_EMPTY_ARRAY
18, 0, 2, 2, 22, 0, 23, 1, // <expression> 18, 0, 2, 2, 22, 0, 23, 1, // <expression>
@ -324,20 +405,26 @@ describe("compiler pass |generateBytecode|", function() {
18, 0, 2, 2, 22, 0, 23, 1, // <expression> 18, 0, 2, 2, 22, 0, 23, 1, // <expression>
6 // POP 6 // POP
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)" "peg$literalExpectation(\"a\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for one_or_more", function () { describe( "for one_or_more", function () {
let grammar = "start = 'a'+";
const grammar = "start = 'a'+";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
4, // PUSH_EMPTY_ARRAY 4, // PUSH_EMPTY_ARRAY
18, 0, 2, 2, 22, 0, 23, 1, // <expression> 18, 0, 2, 2, 22, 0, 23, 1, // <expression>
@ -350,29 +437,40 @@ describe("compiler pass |generateBytecode|", function() {
6, // POP 6, // POP
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)" "peg$literalExpectation(\"a\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "for group", function () { describe( "for group", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = ('a')", bytecodeDetails( [ expect( pass ).to.changeAST( "start = ('a')", bytecodeDetails( [
18, 0, 2, 2, 22, 0, 23, 1 // <expression> 18, 0, 2, 2, 22, 0, 23, 1 // <expression>
] ) ); ] ) );
} ); } );
} ); } );
describe( "for semantic_and", function () { describe( "for semantic_and", function () {
describe( "without labels", function () { describe( "without labels", function () {
let grammar = "start = &{ code }";
const grammar = "start = &{ code }";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
25, // UPDATE_SAVED_POS 25, // UPDATE_SAVED_POS
26, 0, 0, 0, // CALL 26, 0, 0, 0, // CALL
@ -382,20 +480,26 @@ describe("compiler pass |generateBytecode|", function() {
6, // * POP 6, // * POP
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( expect( pass ).to.changeAST(
grammar, grammar,
constsDetails( [ "function() { code }" ] ) constsDetails( [ "function() { code }" ] )
); );
} ); } );
} ); } );
describe( "with labels", function () { describe( "with labels", function () {
let grammar = "start = a:'a' b:'b' c:'c' &{ code }";
const grammar = "start = a:'a' b:'b' c:'c' &{ code }";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]> 18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
@ -427,9 +531,11 @@ describe("compiler pass |generateBytecode|", function() {
7, // POP_CURR_POS 7, // POP_CURR_POS
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)", "peg$literalExpectation(\"a\", false)",
@ -439,15 +545,21 @@ describe("compiler pass |generateBytecode|", function() {
"peg$literalExpectation(\"c\", false)", "peg$literalExpectation(\"c\", false)",
"function(a, b, c) { code }" "function(a, b, c) { code }"
] ) ); ] ) );
} ); } );
} ); } );
} ); } );
describe( "for semantic_not", function () { describe( "for semantic_not", function () {
describe( "without labels", function () { describe( "without labels", function () {
let grammar = "start = !{ code }";
const grammar = "start = !{ code }";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
25, // UPDATE_SAVED_POS 25, // UPDATE_SAVED_POS
26, 0, 0, 0, // CALL 26, 0, 0, 0, // CALL
@ -457,20 +569,26 @@ describe("compiler pass |generateBytecode|", function() {
6, // * POP 6, // * POP
1 // PUSH_UNDEFINED 1 // PUSH_UNDEFINED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( expect( pass ).to.changeAST(
grammar, grammar,
constsDetails( [ "function() { code }" ] ) constsDetails( [ "function() { code }" ] )
); );
} ); } );
} ); } );
describe( "with labels", function () { describe( "with labels", function () {
let grammar = "start = a:'a' b:'b' c:'c' !{ code }";
const grammar = "start = a:'a' b:'b' c:'c' !{ code }";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
5, // PUSH_CURR_POS 5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]> 18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
@ -502,9 +620,11 @@ describe("compiler pass |generateBytecode|", function() {
7, // POP_CURR_POS 7, // POP_CURR_POS
3 // PUSH_FAILED 3 // PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)", "peg$literalExpectation(\"a\", false)",
@ -514,12 +634,17 @@ describe("compiler pass |generateBytecode|", function() {
"peg$literalExpectation(\"c\", false)", "peg$literalExpectation(\"c\", false)",
"function(a, b, c) { code }" "function(a, b, c) { code }"
] ) ); ] ) );
} ); } );
} ); } );
} ); } );
describe( "for rule_ref", function () { describe( "for rule_ref", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( [ expect( pass ).to.changeAST( [
"start = other", "start = other",
"other = 'other'" "other = 'other'"
@ -531,125 +656,174 @@ describe("compiler pass |generateBytecode|", function() {
{ } { }
] ]
} ); } );
} ); } );
} ); } );
describe( "for literal", function () { describe( "for literal", function () {
describe( "empty", function () { describe( "empty", function () {
let grammar = "start = ''";
const grammar = "start = ''";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
0, 0 // PUSH 0, 0 // PUSH
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ "\"\"" ] ) ); expect( pass ).to.changeAST( grammar, constsDetails( [ "\"\"" ] ) );
} ); } );
} ); } );
describe( "non-empty case-sensitive", function () { describe( "non-empty case-sensitive", function () {
let grammar = "start = 'a'";
const grammar = "start = 'a'";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
18, 0, 2, 2, // MATCH_STRING 18, 0, 2, 2, // MATCH_STRING
22, 0, // * ACCEPT_STRING 22, 0, // * ACCEPT_STRING
23, 1 // * FAIL 23, 1 // * FAIL
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"a\", false)" "peg$literalExpectation(\"a\", false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "non-empty case-insensitive", function () { describe( "non-empty case-insensitive", function () {
let grammar = "start = 'A'i";
const grammar = "start = 'A'i";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
19, 0, 2, 2, // MATCH_STRING_IC 19, 0, 2, 2, // MATCH_STRING_IC
21, 1, // * ACCEPT_N 21, 1, // * ACCEPT_N
23, 1 // * FAIL 23, 1 // * FAIL
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"", "\"a\"",
"peg$literalExpectation(\"A\", true)" "peg$literalExpectation(\"A\", true)"
] ) ); ] ) );
} ); } );
} ); } );
} ); } );
describe( "for class", function () { describe( "for class", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = [a]", bytecodeDetails( [ expect( pass ).to.changeAST( "start = [a]", bytecodeDetails( [
20, 0, 2, 2, // MATCH_REGEXP 20, 0, 2, 2, // MATCH_REGEXP
21, 1, // * ACCEPT_N 21, 1, // * ACCEPT_N
23, 1 // * FAIL 23, 1 // * FAIL
] ) ); ] ) );
} ); } );
describe( "non-inverted case-sensitive", function () { describe( "non-inverted case-sensitive", function () {
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( "start = [a]", constsDetails( [ expect( pass ).to.changeAST( "start = [a]", constsDetails( [
"/^[a]/", "/^[a]/",
"peg$classExpectation([\"a\"], false, false)" "peg$classExpectation([\"a\"], false, false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "inverted case-sensitive", function () { describe( "inverted case-sensitive", function () {
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( "start = [^a]", constsDetails( [ expect( pass ).to.changeAST( "start = [^a]", constsDetails( [
"/^[^a]/", "/^[^a]/",
"peg$classExpectation([\"a\"], true, false)" "peg$classExpectation([\"a\"], true, false)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "non-inverted case-insensitive", function () { describe( "non-inverted case-insensitive", function () {
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( "start = [a]i", constsDetails( [ expect( pass ).to.changeAST( "start = [a]i", constsDetails( [
"/^[a]/i", "/^[a]/i",
"peg$classExpectation([\"a\"], false, true)" "peg$classExpectation([\"a\"], false, true)"
] ) ); ] ) );
} ); } );
} ); } );
describe( "complex", function () { describe( "complex", function () {
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( "start = [ab-def-hij-l]", constsDetails( [ expect( pass ).to.changeAST( "start = [ab-def-hij-l]", constsDetails( [
"/^[ab-def-hij-l]/", "/^[ab-def-hij-l]/",
"peg$classExpectation([\"a\", [\"b\", \"d\"], \"e\", [\"f\", \"h\"], \"i\", [\"j\", \"l\"]], false, false)" "peg$classExpectation([\"a\", [\"b\", \"d\"], \"e\", [\"f\", \"h\"], \"i\", [\"j\", \"l\"]], false, false)"
] ) ); ] ) );
} ); } );
} ); } );
} ); } );
describe( "for any", function () { describe( "for any", function () {
let grammar = "start = .";
const grammar = "start = .";
it( "generates bytecode", function () { it( "generates bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
17, 2, 2, // MATCH_ANY 17, 2, 2, // MATCH_ANY
21, 1, // * ACCEPT_N 21, 1, // * ACCEPT_N
23, 0 // * FAIL 23, 0 // * FAIL
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { it( "defines correct constants", function () {
expect( pass ).to.changeAST( expect( pass ).to.changeAST(
grammar, grammar,
constsDetails( [ "peg$anyExpectation()" ] ) constsDetails( [ "peg$anyExpectation()" ] )
); );
} ); } );
} ); } );
} ); } );

@ -1,50 +1,59 @@
"use strict"; "use strict";
let parser = require("../../../../../lib/parser"); const parser = require( "../../../../../lib/parser" );
module.exports = function ( chai, utils ) { module.exports = function ( chai, utils ) {
let Assertion = chai.Assertion;
const Assertion = chai.Assertion;
Assertion.addMethod( "changeAST", function ( grammar, props, options ) { Assertion.addMethod( "changeAST", function ( grammar, props, options ) {
options = options !== undefined ? options : {};
options = typeof options !== "undefined" ? options : {};
function matchProps( value, props ) { function matchProps( value, props ) {
function isArray(value) {
return Object.prototype.toString.apply(value) === "[object Array]";
}
function isObject( value ) { function isObject( value ) {
return value !== null && typeof value === "object"; return value !== null && typeof value === "object";
} }
if (isArray(props)) { if ( Array.isArray( props ) ) {
if (!isArray(value)) { return false; }
if ( ! Array.isArray( value ) ) return false;
if ( value.length !== props.length ) return false;
if (value.length !== props.length) { return false; }
for ( let i = 0; i < props.length; i++ ) { for ( let i = 0; i < props.length; i++ ) {
if (!matchProps(value[i], props[i])) { return false; }
if ( ! matchProps( value[ i ], props[ i ] ) ) return false;
} }
return true; return true;
} else if ( isObject( props ) ) { } else if ( isObject( props ) ) {
if (!isObject(value)) { return false; }
let keys = Object.keys(props); if ( ! isObject( value ) ) return false;
const keys = Object.keys( props );
for ( let i = 0; i < keys.length; i++ ) { for ( let i = 0; i < keys.length; i++ ) {
let key = keys[i];
if (!(key in value)) { return false; } const key = keys[ i ];
if ( ! ( key in value ) ) return false;
if ( ! matchProps( value[ key ], props[ key ] ) ) return false;
if (!matchProps(value[key], props[key])) { return false; }
} }
return true; return true;
} else {
return value === props;
} }
return value === props;
} }
let ast = parser.parse(grammar); const ast = parser.parse( grammar );
utils.flag( this, "object" )( ast, options ); utils.flag( this, "object" )( ast, options );
@ -55,21 +64,27 @@ module.exports = function(chai, utils) {
props, props,
ast ast
); );
} ); } );
Assertion.addMethod( "reportError", function ( grammar, props, options ) { Assertion.addMethod( "reportError", function ( grammar, props, options ) {
options = options !== undefined ? options : {};
let ast = parser.parse(grammar); options = typeof options !== "undefined" ? options : {};
const ast = parser.parse( grammar );
let passed, result; let passed, result;
try { try {
utils.flag( this, "object" )( ast, options ); utils.flag( this, "object" )( ast, options );
passed = true; passed = true;
} catch ( e ) { } catch ( e ) {
result = e; result = e;
passed = false; passed = false;
} }
this.assert( this.assert(
@ -80,11 +95,18 @@ module.exports = function(chai, utils) {
result result
); );
if (!passed && props !== undefined) { if ( ! passed && typeof props !== "undefined" ) {
Object.keys( props ).forEach( key => { Object.keys( props ).forEach( key => {
new Assertion(result).to.have.property(key)
new Assertion( result )
.to.have.property( key )
.that.is.deep.equal( props[ key ] ); .that.is.deep.equal( props[ key ] );
} ); } );
} }
} ); } );
}; };

@ -1,16 +1,19 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let helpers = require("./helpers"); const helpers = require( "./helpers" );
let pass = require("../../../../../lib/compiler/passes/remove-proxy-rules"); const pass = require( "../../../../../lib/compiler/passes/remove-proxy-rules" );
chai.use( helpers ); chai.use( helpers );
let expect = chai.expect; const expect = chai.expect;
describe( "compiler pass |removeProxyRules|", function () { describe( "compiler pass |removeProxyRules|", function () {
describe( "when a proxy rule isn't listed in |allowedStartRules|", function () { describe( "when a proxy rule isn't listed in |allowedStartRules|", function () {
it( "updates references and removes it", function () { it( "updates references and removes it", function () {
expect( pass ).to.changeAST( expect( pass ).to.changeAST(
[ [
"start = proxy", "start = proxy",
@ -28,11 +31,15 @@ describe("compiler pass |removeProxyRules|", function() {
}, },
{ allowedStartRules: [ "start" ] } { allowedStartRules: [ "start" ] }
); );
} ); } );
} ); } );
describe( "when a proxy rule is listed in |allowedStartRules|", function () { describe( "when a proxy rule is listed in |allowedStartRules|", function () {
it( "updates references but doesn't remove it", function () { it( "updates references but doesn't remove it", function () {
expect( pass ).to.changeAST( expect( pass ).to.changeAST(
[ [
"start = proxy", "start = proxy",
@ -54,6 +61,9 @@ describe("compiler pass |removeProxyRules|", function() {
}, },
{ allowedStartRules: [ "start", "proxy" ] } { allowedStartRules: [ "start", "proxy" ] }
); );
} ); } );
} ); } );
} ); } );

@ -1,16 +1,19 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let helpers = require("./helpers"); const helpers = require( "./helpers" );
let pass = require("../../../../../lib/compiler/passes/report-duplicate-labels"); const pass = require( "../../../../../lib/compiler/passes/report-duplicate-labels" );
chai.use( helpers ); chai.use( helpers );
let expect = chai.expect; const expect = chai.expect;
describe( "compiler pass |reportDuplicateLabels|", function () { describe( "compiler pass |reportDuplicateLabels|", function () {
describe( "in a sequence", function () { describe( "in a sequence", function () {
it( "reports labels duplicate with labels of preceding elements", function () { it( "reports labels duplicate with labels of preceding elements", function () {
expect( pass ).to.reportError( "start = a:'a' a:'a'", { expect( pass ).to.reportError( "start = a:'a' a:'a'", {
message: "Label \"a\" is already defined at line 1, column 9.", message: "Label \"a\" is already defined at line 1, column 9.",
location: { location: {
@ -18,9 +21,11 @@ describe("compiler pass |reportDuplicateLabels|", function() {
end: { offset: 19, line: 1, column: 20 } end: { offset: 19, line: 1, column: 20 }
} }
} ); } );
} ); } );
it( "doesn't report labels duplicate with labels in subexpressions", function () { it( "doesn't report labels duplicate with labels in subexpressions", function () {
expect( pass ).to.not.reportError( "start = ('a' / a:'a' / 'a') a:'a'" ); expect( pass ).to.not.reportError( "start = ('a' / a:'a' / 'a') a:'a'" );
expect( pass ).to.not.reportError( "start = (a:'a' { }) a:'a'" ); expect( pass ).to.not.reportError( "start = (a:'a' { }) a:'a'" );
expect( pass ).to.not.reportError( "start = ('a' a:'a' 'a') a:'a'" ); expect( pass ).to.not.reportError( "start = ('a' a:'a' 'a') a:'a'" );
@ -32,17 +37,25 @@ describe("compiler pass |reportDuplicateLabels|", function() {
expect( pass ).to.not.reportError( "start = (a:'a')* a:'a'" ); expect( pass ).to.not.reportError( "start = (a:'a')* a:'a'" );
expect( pass ).to.not.reportError( "start = (a:'a')+ a:'a'" ); expect( pass ).to.not.reportError( "start = (a:'a')+ a:'a'" );
expect( pass ).to.not.reportError( "start = (a:'a') a:'a'" ); expect( pass ).to.not.reportError( "start = (a:'a') a:'a'" );
} ); } );
} ); } );
describe( "in a choice", function () { describe( "in a choice", function () {
it( "doesn't report labels duplicate with labels of preceding alternatives", function () { it( "doesn't report labels duplicate with labels of preceding alternatives", function () {
expect( pass ).to.not.reportError( "start = a:'a' / a:'a'" ); expect( pass ).to.not.reportError( "start = a:'a' / a:'a'" );
} ); } );
} ); } );
describe( "in outer sequence", function () { describe( "in outer sequence", function () {
it( "reports labels duplicate with labels of preceding elements", function () { it( "reports labels duplicate with labels of preceding elements", function () {
expect( pass ).to.reportError( "start = a:'a' (a:'a')", { expect( pass ).to.reportError( "start = a:'a' (a:'a')", {
message: "Label \"a\" is already defined at line 1, column 9.", message: "Label \"a\" is already defined at line 1, column 9.",
location: { location: {
@ -50,14 +63,21 @@ describe("compiler pass |reportDuplicateLabels|", function() {
end: { offset: 20, line: 1, column: 21 } end: { offset: 20, line: 1, column: 21 }
} }
} ); } );
} ); } );
it( "doesn't report labels duplicate with the label of the current element", function () { it( "doesn't report labels duplicate with the label of the current element", function () {
expect( pass ).to.not.reportError( "start = a:(a:'a')" ); expect( pass ).to.not.reportError( "start = a:(a:'a')" );
} ); } );
it( "doesn't report labels duplicate with labels of following elements", function () { it( "doesn't report labels duplicate with labels of following elements", function () {
expect( pass ).to.not.reportError( "start = (a:'a') a:'a'" ); expect( pass ).to.not.reportError( "start = (a:'a') a:'a'" );
} ); } );
} ); } );
} ); } );

@ -1,15 +1,17 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let helpers = require("./helpers"); const helpers = require( "./helpers" );
let pass = require("../../../../../lib/compiler/passes/report-duplicate-rules"); const pass = require( "../../../../../lib/compiler/passes/report-duplicate-rules" );
chai.use( helpers ); chai.use( helpers );
let expect = chai.expect; const expect = chai.expect;
describe( "compiler pass |reportDuplicateRules|", function () { describe( "compiler pass |reportDuplicateRules|", function () {
it( "reports duplicate rules", function () { it( "reports duplicate rules", function () {
expect( pass ).to.reportError( [ expect( pass ).to.reportError( [
"start = 'a'", "start = 'a'",
"start = 'b'" "start = 'b'"
@ -20,5 +22,7 @@ describe("compiler pass |reportDuplicateRules|", function() {
end: { offset: 23, line: 2, column: 12 } end: { offset: 23, line: 2, column: 12 }
} }
} ); } );
} ); } );
} ); } );

@ -1,15 +1,17 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let helpers = require("./helpers"); const helpers = require( "./helpers" );
let pass = require("../../../../../lib/compiler/passes/report-infinite-recursion"); const pass = require( "../../../../../lib/compiler/passes/report-infinite-recursion" );
chai.use( helpers ); chai.use( helpers );
let expect = chai.expect; const expect = chai.expect;
describe( "compiler pass |reportInfiniteRecursion|", function () { describe( "compiler pass |reportInfiniteRecursion|", function () {
it( "reports direct left recursion", function () { it( "reports direct left recursion", function () {
expect( pass ).to.reportError( "start = start", { expect( pass ).to.reportError( "start = start", {
message: "Possible infinite loop when parsing (left recursion: start -> start).", message: "Possible infinite loop when parsing (left recursion: start -> start).",
location: { location: {
@ -17,9 +19,11 @@ describe("compiler pass |reportInfiniteRecursion|", function() {
end: { offset: 13, line: 1, column: 14 } end: { offset: 13, line: 1, column: 14 }
} }
} ); } );
} ); } );
it( "reports indirect left recursion", function () { it( "reports indirect left recursion", function () {
expect( pass ).to.reportError( [ expect( pass ).to.reportError( [
"start = stop", "start = stop",
"stop = start" "stop = start"
@ -30,25 +34,34 @@ describe("compiler pass |reportInfiniteRecursion|", function() {
end: { offset: 25, line: 2, column: 13 } end: { offset: 25, line: 2, column: 13 }
} }
} ); } );
} ); } );
describe( "in sequences", function () { describe( "in sequences", function () {
it( "reports left recursion if all preceding elements match empty string", function () { it( "reports left recursion if all preceding elements match empty string", function () {
expect( pass ).to.reportError( "start = '' '' '' start" ); expect( pass ).to.reportError( "start = '' '' '' start" );
} ); } );
it( "doesn't report left recursion if some preceding element doesn't match empty string", function () { it( "doesn't report left recursion if some preceding element doesn't match empty string", function () {
expect( pass ).to.not.reportError( "start = 'a' '' '' start" ); expect( pass ).to.not.reportError( "start = 'a' '' '' start" );
expect( pass ).to.not.reportError( "start = '' 'a' '' start" ); expect( pass ).to.not.reportError( "start = '' 'a' '' start" );
expect( pass ).to.not.reportError( "start = '' '' 'a' start" ); expect( pass ).to.not.reportError( "start = '' '' 'a' start" );
} ); } );
// Regression test for #359. // Regression test for #359.
it( "reports left recursion when rule reference is wrapped in an expression", function () { it( "reports left recursion when rule reference is wrapped in an expression", function () {
expect( pass ).to.reportError( "start = '' start?" ); expect( pass ).to.reportError( "start = '' start?" );
} ); } );
it( "computes expressions that always consume input on success correctly", function () { it( "computes expressions that always consume input on success correctly", function () {
expect( pass ).to.reportError( [ expect( pass ).to.reportError( [
"start = a start", "start = a start",
"a 'a' = ''" "a 'a' = ''"
@ -114,6 +127,9 @@ describe("compiler pass |reportInfiniteRecursion|", function() {
expect( pass ).to.not.reportError( "start = [a-d] start" ); expect( pass ).to.not.reportError( "start = [a-d] start" );
expect( pass ).to.not.reportError( "start = . start" ); expect( pass ).to.not.reportError( "start = . start" );
} ); } );
} ); } );
} ); } );

@ -1,15 +1,17 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let helpers = require("./helpers"); const helpers = require( "./helpers" );
let pass = require("../../../../../lib/compiler/passes/report-infinite-repetition"); const pass = require( "../../../../../lib/compiler/passes/report-infinite-repetition" );
chai.use( helpers ); chai.use( helpers );
let expect = chai.expect; const expect = chai.expect;
describe( "compiler pass |reportInfiniteRepetition|", function () { describe( "compiler pass |reportInfiniteRepetition|", function () {
it( "reports infinite loops for zero_or_more", function () { it( "reports infinite loops for zero_or_more", function () {
expect( pass ).to.reportError( "start = ('')*", { expect( pass ).to.reportError( "start = ('')*", {
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).", message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
location: { location: {
@ -17,9 +19,11 @@ describe("compiler pass |reportInfiniteRepetition|", function() {
end: { offset: 13, line: 1, column: 14 } end: { offset: 13, line: 1, column: 14 }
} }
} ); } );
} ); } );
it( "reports infinite loops for one_or_more", function () { it( "reports infinite loops for one_or_more", function () {
expect( pass ).to.reportError( "start = ('')+", { expect( pass ).to.reportError( "start = ('')+", {
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).", message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
location: { location: {
@ -27,9 +31,11 @@ describe("compiler pass |reportInfiniteRepetition|", function() {
end: { offset: 13, line: 1, column: 14 } end: { offset: 13, line: 1, column: 14 }
} }
} ); } );
} ); } );
it( "computes expressions that always consume input on success correctly", function () { it( "computes expressions that always consume input on success correctly", function () {
expect( pass ).to.reportError( [ expect( pass ).to.reportError( [
"start = a*", "start = a*",
"a 'a' = ''" "a 'a' = ''"
@ -95,5 +101,7 @@ describe("compiler pass |reportInfiniteRepetition|", function() {
expect( pass ).to.not.reportError( "start = [a-d]*" ); expect( pass ).to.not.reportError( "start = [a-d]*" );
expect( pass ).to.not.reportError( "start = .*" ); expect( pass ).to.not.reportError( "start = .*" );
} ); } );
} ); } );

@ -1,15 +1,17 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let helpers = require("./helpers"); const helpers = require( "./helpers" );
let pass = require("../../../../../lib/compiler/passes/report-undefined-rules"); const pass = require( "../../../../../lib/compiler/passes/report-undefined-rules" );
chai.use( helpers ); chai.use( helpers );
let expect = chai.expect; const expect = chai.expect;
describe( "compiler pass |reportUndefinedRules|", function () { describe( "compiler pass |reportUndefinedRules|", function () {
it( "reports undefined rules", function () { it( "reports undefined rules", function () {
expect( pass ).to.reportError( "start = undefined", { expect( pass ).to.reportError( "start = undefined", {
message: "Rule \"undefined\" is not defined.", message: "Rule \"undefined\" is not defined.",
location: { location: {
@ -17,13 +19,17 @@ describe("compiler pass |reportUndefinedRules|", function() {
end: { offset: 17, line: 1, column: 18 } end: { offset: 17, line: 1, column: 18 }
} }
} ); } );
} ); } );
it( "checks allowedStartRules", function () { it( "checks allowedStartRules", function () {
expect( pass ).to.reportError( "start = 'a'", { expect( pass ).to.reportError( "start = 'a'", {
message: "Start rule \"missing\" is not defined." message: "Start rule \"missing\" is not defined."
}, { }, {
allowedStartRules: [ "missing" ] allowedStartRules: [ "missing" ]
} ); } );
} ); } );
} ); } );

@ -1,147 +1,179 @@
"use strict"; "use strict";
let chai = require("chai"); const chai = require( "chai" );
let parser = require("../../../lib/parser"); const parser = require( "../../../lib/parser" );
let expect = chai.expect; const expect = chai.expect;
// better diagnostics for deep eq failure // better diagnostics for deep eq failure
chai.config.truncateThreshold = 0; chai.config.truncateThreshold = 0;
describe( "PEG.js grammar parser", function () { describe( "PEG.js grammar parser", function () {
let literalAbcd = { type: "literal", value: "abcd", ignoreCase: false };
let literalEfgh = { type: "literal", value: "efgh", ignoreCase: false }; const literalAbcd = { type: "literal", value: "abcd", ignoreCase: false };
let literalIjkl = { type: "literal", value: "ijkl", ignoreCase: false }; const literalEfgh = { type: "literal", value: "efgh", ignoreCase: false };
let literalMnop = { type: "literal", value: "mnop", ignoreCase: false }; const literalIjkl = { type: "literal", value: "ijkl", ignoreCase: false };
let semanticAnd = { type: "semantic_and", code: " code " }; const literalMnop = { type: "literal", value: "mnop", ignoreCase: false };
let semanticNot = { type: "semantic_not", code: " code " }; const semanticAnd = { type: "semantic_and", code: " code " };
let optional = { type: "optional", expression: literalAbcd }; const semanticNot = { type: "semantic_not", code: " code " };
let zeroOrMore = { type: "zero_or_more", expression: literalAbcd }; const optional = { type: "optional", expression: literalAbcd };
let oneOrMore = { type: "one_or_more", expression: literalAbcd }; const zeroOrMore = { type: "zero_or_more", expression: literalAbcd };
let textOptional = { type: "text", expression: optional }; const oneOrMore = { type: "one_or_more", expression: literalAbcd };
let simpleNotAbcd = { type: "simple_not", expression: literalAbcd }; const textOptional = { type: "text", expression: optional };
let simpleAndOptional = { type: "simple_and", expression: optional }; const simpleNotAbcd = { type: "simple_not", expression: literalAbcd };
let simpleNotOptional = { type: "simple_not", expression: optional }; const simpleAndOptional = { type: "simple_and", expression: optional };
let labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd }; const simpleNotOptional = { type: "simple_not", expression: optional };
let labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh }; const labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd };
let labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl }; const labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh };
let labeledMnop = { type: "labeled", label: "d", expression: literalMnop }; const labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl };
let labeledSimpleNot = { type: "labeled", label: "a", expression: simpleNotAbcd }; const labeledMnop = { type: "labeled", label: "d", expression: literalMnop };
let sequence = { const labeledSimpleNot = { type: "labeled", label: "a", expression: simpleNotAbcd };
const sequence = {
type: "sequence", type: "sequence",
elements: [ literalAbcd, literalEfgh, literalIjkl ] elements: [ literalAbcd, literalEfgh, literalIjkl ]
}; };
let sequence2 = { const sequence2 = {
type: "sequence", type: "sequence",
elements: [ labeledAbcd, labeledEfgh ] elements: [ labeledAbcd, labeledEfgh ]
}; };
let sequence4 = { const sequence4 = {
type: "sequence", type: "sequence",
elements: [ labeledAbcd, labeledEfgh, labeledIjkl, labeledMnop ] elements: [ labeledAbcd, labeledEfgh, labeledIjkl, labeledMnop ]
}; };
let groupLabeled = { type: "group", expression: labeledAbcd }; const groupLabeled = { type: "group", expression: labeledAbcd };
let groupSequence = { type: "group", expression: sequence }; const groupSequence = { type: "group", expression: sequence };
let actionAbcd = { type: "action", expression: literalAbcd, code: " code " }; const actionAbcd = { type: "action", expression: literalAbcd, code: " code " };
let actionEfgh = { type: "action", expression: literalEfgh, code: " code " }; const actionEfgh = { type: "action", expression: literalEfgh, code: " code " };
let actionIjkl = { type: "action", expression: literalIjkl, code: " code " }; const actionIjkl = { type: "action", expression: literalIjkl, code: " code " };
let actionMnop = { type: "action", expression: literalMnop, code: " code " }; const actionMnop = { type: "action", expression: literalMnop, code: " code " };
let actionSequence = { type: "action", expression: sequence, code: " code " }; const actionSequence = { type: "action", expression: sequence, code: " code " };
let choice = { const choice = {
type: "choice", type: "choice",
alternatives: [ literalAbcd, literalEfgh, literalIjkl ] alternatives: [ literalAbcd, literalEfgh, literalIjkl ]
}; };
let choice2 = { const choice2 = {
type: "choice", type: "choice",
alternatives: [ actionAbcd, actionEfgh ] alternatives: [ actionAbcd, actionEfgh ]
}; };
let choice4 = { const choice4 = {
type: "choice", type: "choice",
alternatives: [ actionAbcd, actionEfgh, actionIjkl, actionMnop ] alternatives: [ actionAbcd, actionEfgh, actionIjkl, actionMnop ]
}; };
let named = { type: "named", name: "start rule", expression: literalAbcd }; const named = { type: "named", name: "start rule", expression: literalAbcd };
let ruleA = { type: "rule", name: "a", expression: literalAbcd }; const ruleA = { type: "rule", name: "a", expression: literalAbcd };
let ruleB = { type: "rule", name: "b", expression: literalEfgh }; const ruleB = { type: "rule", name: "b", expression: literalEfgh };
let ruleC = { type: "rule", name: "c", expression: literalIjkl }; const ruleC = { type: "rule", name: "c", expression: literalIjkl };
let ruleStart = { type: "rule", name: "start", expression: literalAbcd }; const ruleStart = { type: "rule", name: "start", expression: literalAbcd };
let initializer = { type: "initializer", code: " code " }; const initializer = { type: "initializer", code: " code " };
function oneRuleGrammar( expression ) { function oneRuleGrammar( expression ) {
return { return {
type: "grammar", type: "grammar",
initializer: null, initializer: null,
rules: [ { type: "rule", name: "start", expression: expression } ] rules: [ { type: "rule", name: "start", expression: expression } ]
}; };
} }
function actionGrammar( code ) { function actionGrammar( code ) {
return oneRuleGrammar( return oneRuleGrammar(
{ type: "action", expression: literalAbcd, code: code } { type: "action", expression: literalAbcd, code: code }
); );
} }
function literalGrammar( value, ignoreCase ) { function literalGrammar( value, ignoreCase ) {
return oneRuleGrammar( return oneRuleGrammar(
{ type: "literal", value: value, ignoreCase: ignoreCase } { type: "literal", value: value, ignoreCase: ignoreCase }
); );
} }
function classGrammar( parts, inverted, ignoreCase ) { function classGrammar( parts, inverted, ignoreCase ) {
return oneRuleGrammar( { return oneRuleGrammar( {
type: "class", type: "class",
parts: parts, parts: parts,
inverted: inverted, inverted: inverted,
ignoreCase: ignoreCase ignoreCase: ignoreCase
} ); } );
} }
function anyGrammar() { function anyGrammar() {
return oneRuleGrammar( { type: "any" } ); return oneRuleGrammar( { type: "any" } );
} }
function ruleRefGrammar( name ) { function ruleRefGrammar( name ) {
return oneRuleGrammar( { type: "rule_ref", name: name } ); return oneRuleGrammar( { type: "rule_ref", name: name } );
} }
let trivialGrammar = literalGrammar("abcd", false); const trivialGrammar = literalGrammar( "abcd", false );
let twoRuleGrammar = { const twoRuleGrammar = {
type: "grammar", type: "grammar",
initializer: null, initializer: null,
rules: [ ruleA, ruleB ] rules: [ ruleA, ruleB ]
}; };
let stripLocation = (function() { const stripLocation = ( function () {
let strip;
function buildVisitor( functions ) { function buildVisitor( functions ) {
return function ( node ) { return function ( node ) {
return functions[ node.type ].apply( null, arguments ); return functions[ node.type ].apply( null, arguments );
}; };
} }
function stripLeaf( node ) { function stripLeaf( node ) {
delete node.location; delete node.location;
} }
function stripExpression( node ) { function stripExpression( node ) {
delete node.location; delete node.location;
strip( node.expression ); strip( node.expression );
} }
function stripChildren( property ) { function stripChildren( property ) {
return function ( node ) { return function ( node ) {
delete node.location; delete node.location;
node[ property ].forEach( strip ); node[ property ].forEach( strip );
}; };
} }
let strip = buildVisitor({ strip = buildVisitor( {
grammar( node ) { grammar( node ) {
delete node.location; delete node.location;
if ( node.initializer ) { if ( node.initializer ) {
strip( node.initializer ); strip( node.initializer );
} }
node.rules.forEach( strip ); node.rules.forEach( strip );
}, },
initializer: stripLeaf, initializer: stripLeaf,
@ -167,13 +199,16 @@ describe("PEG.js grammar parser", function() {
} ); } );
return strip; return strip;
} )(); } )();
function helpers( chai, utils ) { function helpers( chai, utils ) {
let Assertion = chai.Assertion;
const Assertion = chai.Assertion;
Assertion.addMethod( "parseAs", function ( expected ) { Assertion.addMethod( "parseAs", function ( expected ) {
let result = parser.parse(utils.flag(this, "object"));
const result = parser.parse( utils.flag( this, "object" ) );
stripLocation( result ); stripLocation( result );
@ -185,21 +220,29 @@ describe("PEG.js grammar parser", function() {
result, result,
! utils.flag( this, "negate" ) ! utils.flag( this, "negate" )
); );
} ); } );
Assertion.addMethod( "failToParse", function ( props ) { Assertion.addMethod( "failToParse", function ( props ) {
let passed, result; let passed, result;
try { try {
result = parser.parse( utils.flag( this, "object" ) ); result = parser.parse( utils.flag( this, "object" ) );
passed = true; passed = true;
} catch ( e ) { } catch ( e ) {
result = e; result = e;
passed = false; passed = false;
} }
if ( passed ) { if ( passed ) {
stripLocation( result ); stripLocation( result );
} }
this.assert( this.assert(
@ -210,26 +253,36 @@ describe("PEG.js grammar parser", function() {
result result
); );
if (!passed && props !== undefined) { if ( ! passed && typeof props !== "undefined" ) {
Object.keys( props ).forEach( key => { Object.keys( props ).forEach( key => {
new Assertion(result).to.have.property(key)
new Assertion( result )
.to.have.property( key )
.that.is.deep.equal( props[ key ] ); .that.is.deep.equal( props[ key ] );
} ); } );
} }
} ); } );
} }
// Helper activation needs to put inside a |beforeEach| block because the // Helper activation needs to put inside a |beforeEach| block because the
// helpers conflict with the ones in // helpers conflict with the ones in
// test/behavior/generated-parser-behavior.spec.js. // test/behavior/generated-parser-behavior.spec.js.
beforeEach( function () { beforeEach( function () {
chai.use( helpers ); chai.use( helpers );
} ); } );
// Grammars without any rules are not accepted. // Grammars without any rules are not accepted.
it( "parses Rule+", function () { it( "parses Rule+", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) ); expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
let grammar = ruleRefGrammar("a"); const grammar = ruleRefGrammar( "a" );
grammar.initializer = { grammar.initializer = {
"type": "initializer", "type": "initializer",
"code": "" "code": ""
@ -238,10 +291,12 @@ describe("PEG.js grammar parser", function() {
expect( "" ).to.failToParse(); expect( "" ).to.failToParse();
expect( "{}" ).to.failToParse(); expect( "{}" ).to.failToParse();
} ); } );
// Canonical Grammar is "a = 'abcd'; b = 'efgh'; c = 'ijkl';". // Canonical Grammar is "a = 'abcd'; b = 'efgh'; c = 'ijkl';".
it( "parses Grammar", function () { it( "parses Grammar", function () {
expect( "\na = 'abcd';\n" ).to.parseAs( expect( "\na = 'abcd';\n" ).to.parseAs(
{ type: "grammar", initializer: null, rules: [ ruleA ] } { type: "grammar", initializer: null, rules: [ ruleA ] }
); );
@ -251,34 +306,42 @@ describe("PEG.js grammar parser", function() {
expect( "\n{ code };\na = 'abcd';\n" ).to.parseAs( expect( "\n{ code };\na = 'abcd';\n" ).to.parseAs(
{ type: "grammar", initializer: initializer, rules: [ ruleA ] } { type: "grammar", initializer: initializer, rules: [ ruleA ] }
); );
} ); } );
// Canonical Initializer is "{ code }". // Canonical Initializer is "{ code }".
it( "parses Initializer", function () { it( "parses Initializer", function () {
expect( "{ code };start = 'abcd'" ).to.parseAs( expect( "{ code };start = 'abcd'" ).to.parseAs(
{ type: "grammar", initializer: initializer, rules: [ ruleStart ] } { type: "grammar", initializer: initializer, rules: [ ruleStart ] }
); );
} ); } );
// Canonical Rule is "a = 'abcd';". // Canonical Rule is "a = 'abcd';".
it( "parses Rule", function () { it( "parses Rule", function () {
expect( "start\n=\n'abcd';" ).to.parseAs( expect( "start\n=\n'abcd';" ).to.parseAs(
oneRuleGrammar( literalAbcd ) oneRuleGrammar( literalAbcd )
); );
expect( "start\n'start rule'\n=\n'abcd';" ).to.parseAs( expect( "start\n'start rule'\n=\n'abcd';" ).to.parseAs(
oneRuleGrammar( named ) oneRuleGrammar( named )
); );
} ); } );
// Canonical Expression is "'abcd'". // Canonical Expression is "'abcd'".
it( "parses Expression", function () { it( "parses Expression", function () {
expect( "start = 'abcd' / 'efgh' / 'ijkl'" ).to.parseAs( expect( "start = 'abcd' / 'efgh' / 'ijkl'" ).to.parseAs(
oneRuleGrammar( choice ) oneRuleGrammar( choice )
); );
} ); } );
// Canonical ChoiceExpression is "'abcd' / 'efgh' / 'ijkl'". // Canonical ChoiceExpression is "'abcd' / 'efgh' / 'ijkl'".
it( "parses ChoiceExpression", function () { it( "parses ChoiceExpression", function () {
expect( "start = 'abcd' { code }" ).to.parseAs( expect( "start = 'abcd' { code }" ).to.parseAs(
oneRuleGrammar( actionAbcd ) oneRuleGrammar( actionAbcd )
); );
@ -290,20 +353,24 @@ describe("PEG.js grammar parser", function() {
).to.parseAs( ).to.parseAs(
oneRuleGrammar( choice4 ) oneRuleGrammar( choice4 )
); );
} ); } );
// Canonical ActionExpression is "'abcd' { code }". // Canonical ActionExpression is "'abcd' { code }".
it( "parses ActionExpression", function () { it( "parses ActionExpression", function () {
expect( "start = 'abcd' 'efgh' 'ijkl'" ).to.parseAs( expect( "start = 'abcd' 'efgh' 'ijkl'" ).to.parseAs(
oneRuleGrammar( sequence ) oneRuleGrammar( sequence )
); );
expect( "start = 'abcd' 'efgh' 'ijkl'\n{ code }" ).to.parseAs( expect( "start = 'abcd' 'efgh' 'ijkl'\n{ code }" ).to.parseAs(
oneRuleGrammar( actionSequence ) oneRuleGrammar( actionSequence )
); );
} ); } );
// Canonical SequenceExpression is "'abcd' 'efgh' 'ijkl'". // Canonical SequenceExpression is "'abcd' 'efgh' 'ijkl'".
it( "parses SequenceExpression", function () { it( "parses SequenceExpression", function () {
expect( "start = a:'abcd'" ).to.parseAs( expect( "start = a:'abcd'" ).to.parseAs(
oneRuleGrammar( labeledAbcd ) oneRuleGrammar( labeledAbcd )
); );
@ -313,42 +380,54 @@ describe("PEG.js grammar parser", function() {
expect( "start = a:'abcd'\nb:'efgh'\nc:'ijkl'\nd:'mnop'" ).to.parseAs( expect( "start = a:'abcd'\nb:'efgh'\nc:'ijkl'\nd:'mnop'" ).to.parseAs(
oneRuleGrammar( sequence4 ) oneRuleGrammar( sequence4 )
); );
} ); } );
// Canonical LabeledExpression is "a:'abcd'". // Canonical LabeledExpression is "a:'abcd'".
it( "parses LabeledExpression", function () { it( "parses LabeledExpression", function () {
expect( "start = a\n:\n!'abcd'" ).to.parseAs( oneRuleGrammar( labeledSimpleNot ) ); expect( "start = a\n:\n!'abcd'" ).to.parseAs( oneRuleGrammar( labeledSimpleNot ) );
expect( "start = !'abcd'" ).to.parseAs( oneRuleGrammar( simpleNotAbcd ) ); expect( "start = !'abcd'" ).to.parseAs( oneRuleGrammar( simpleNotAbcd ) );
} ); } );
// Canonical PrefixedExpression is "!'abcd'". // Canonical PrefixedExpression is "!'abcd'".
it( "parses PrefixedExpression", function () { it( "parses PrefixedExpression", function () {
expect( "start = !\n'abcd'?" ).to.parseAs( oneRuleGrammar( simpleNotOptional ) ); expect( "start = !\n'abcd'?" ).to.parseAs( oneRuleGrammar( simpleNotOptional ) );
expect( "start = 'abcd'?" ).to.parseAs( oneRuleGrammar( optional ) ); expect( "start = 'abcd'?" ).to.parseAs( oneRuleGrammar( optional ) );
} ); } );
// Canonical PrefixedOperator is "!". // Canonical PrefixedOperator is "!".
it( "parses PrefixedOperator", function () { it( "parses PrefixedOperator", function () {
expect( "start = $'abcd'?" ).to.parseAs( oneRuleGrammar( textOptional ) ); expect( "start = $'abcd'?" ).to.parseAs( oneRuleGrammar( textOptional ) );
expect( "start = &'abcd'?" ).to.parseAs( oneRuleGrammar( simpleAndOptional ) ); expect( "start = &'abcd'?" ).to.parseAs( oneRuleGrammar( simpleAndOptional ) );
expect( "start = !'abcd'?" ).to.parseAs( oneRuleGrammar( simpleNotOptional ) ); expect( "start = !'abcd'?" ).to.parseAs( oneRuleGrammar( simpleNotOptional ) );
} ); } );
// Canonical SuffixedExpression is "'abcd'?". // Canonical SuffixedExpression is "'abcd'?".
it( "parses SuffixedExpression", function () { it( "parses SuffixedExpression", function () {
expect( "start = 'abcd'\n?" ).to.parseAs( oneRuleGrammar( optional ) ); expect( "start = 'abcd'\n?" ).to.parseAs( oneRuleGrammar( optional ) );
expect( "start = 'abcd'" ).to.parseAs( oneRuleGrammar( literalAbcd ) ); expect( "start = 'abcd'" ).to.parseAs( oneRuleGrammar( literalAbcd ) );
} ); } );
// Canonical SuffixedOperator is "?". // Canonical SuffixedOperator is "?".
it( "parses SuffixedOperator", function () { it( "parses SuffixedOperator", function () {
expect( "start = 'abcd'?" ).to.parseAs( oneRuleGrammar( optional ) ); expect( "start = 'abcd'?" ).to.parseAs( oneRuleGrammar( optional ) );
expect( "start = 'abcd'*" ).to.parseAs( oneRuleGrammar( zeroOrMore ) ); expect( "start = 'abcd'*" ).to.parseAs( oneRuleGrammar( zeroOrMore ) );
expect( "start = 'abcd'+" ).to.parseAs( oneRuleGrammar( oneOrMore ) ); expect( "start = 'abcd'+" ).to.parseAs( oneRuleGrammar( oneOrMore ) );
} ); } );
// Canonical PrimaryExpression is "'abcd'". // Canonical PrimaryExpression is "'abcd'".
it( "parses PrimaryExpression", function () { it( "parses PrimaryExpression", function () {
expect( "start = 'abcd'" ).to.parseAs( trivialGrammar ); expect( "start = 'abcd'" ).to.parseAs( trivialGrammar );
expect( "start = [a-d]" ).to.parseAs( classGrammar( [ [ "a", "d" ] ], false, false ) ); expect( "start = [a-d]" ).to.parseAs( classGrammar( [ [ "a", "d" ] ], false, false ) );
expect( "start = ." ).to.parseAs( anyGrammar() ); expect( "start = ." ).to.parseAs( anyGrammar() );
@ -358,31 +437,39 @@ describe("PEG.js grammar parser", function() {
expect( "start = (\na:'abcd'\n)" ).to.parseAs( oneRuleGrammar( groupLabeled ) ); expect( "start = (\na:'abcd'\n)" ).to.parseAs( oneRuleGrammar( groupLabeled ) );
expect( "start = (\n'abcd' 'efgh' 'ijkl'\n)" ).to.parseAs( oneRuleGrammar( groupSequence ) ); expect( "start = (\n'abcd' 'efgh' 'ijkl'\n)" ).to.parseAs( oneRuleGrammar( groupSequence ) );
expect( "start = (\n'abcd'\n)" ).to.parseAs( trivialGrammar ); expect( "start = (\n'abcd'\n)" ).to.parseAs( trivialGrammar );
} ); } );
// Canonical RuleReferenceExpression is "a". // Canonical RuleReferenceExpression is "a".
it( "parses RuleReferenceExpression", function () { it( "parses RuleReferenceExpression", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) ); expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
expect( "start = a\n=" ).to.failToParse(); expect( "start = a\n=" ).to.failToParse();
expect( "start = a\n'abcd'\n=" ).to.failToParse(); expect( "start = a\n'abcd'\n=" ).to.failToParse();
} ); } );
// Canonical SemanticPredicateExpression is "!{ code }". // Canonical SemanticPredicateExpression is "!{ code }".
it( "parses SemanticPredicateExpression", function () { it( "parses SemanticPredicateExpression", function () {
expect( "start = !\n{ code }" ).to.parseAs( oneRuleGrammar( semanticNot ) ); expect( "start = !\n{ code }" ).to.parseAs( oneRuleGrammar( semanticNot ) );
} ); } );
// Canonical SemanticPredicateOperator is "!". // Canonical SemanticPredicateOperator is "!".
it( "parses SemanticPredicateOperator", function () { it( "parses SemanticPredicateOperator", function () {
expect( "start = &{ code }" ).to.parseAs( oneRuleGrammar( semanticAnd ) ); expect( "start = &{ code }" ).to.parseAs( oneRuleGrammar( semanticAnd ) );
expect( "start = !{ code }" ).to.parseAs( oneRuleGrammar( semanticNot ) ); expect( "start = !{ code }" ).to.parseAs( oneRuleGrammar( semanticNot ) );
} ); } );
// The SourceCharacter rule is not tested. // The SourceCharacter rule is not tested.
// Canonical WhiteSpace is " ". // Canonical WhiteSpace is " ".
it( "parses WhiteSpace", function () { it( "parses WhiteSpace", function () {
expect( "start =\t'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\t'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\v'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\v'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\f'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\f'abcd'" ).to.parseAs( trivialGrammar );
@ -390,99 +477,123 @@ describe("PEG.js grammar parser", function() {
expect( "start =\u00A0'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\u00A0'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\uFEFF'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\uFEFF'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\u1680'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\u1680'abcd'" ).to.parseAs( trivialGrammar );
} ); } );
// Canonical LineTerminator is "\n". // Canonical LineTerminator is "\n".
it( "parses LineTerminator", function () { it( "parses LineTerminator", function () {
expect( "start = '\n'" ).to.failToParse(); expect( "start = '\n'" ).to.failToParse();
expect( "start = '\r'" ).to.failToParse(); expect( "start = '\r'" ).to.failToParse();
expect( "start = '\u2028'" ).to.failToParse(); expect( "start = '\u2028'" ).to.failToParse();
expect( "start = '\u2029'" ).to.failToParse(); expect( "start = '\u2029'" ).to.failToParse();
} ); } );
// Canonical LineTerminatorSequence is "\r\n". // Canonical LineTerminatorSequence is "\r\n".
it( "parses LineTerminatorSequence", function () { it( "parses LineTerminatorSequence", function () {
expect( "start =\n'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\r\n'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\r\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\r'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\r'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\u2028'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\u2028'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\u2029'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\u2029'abcd'" ).to.parseAs( trivialGrammar );
} ); } );
// Canonical Comment is "/* comment */". // Canonical Comment is "/* comment */".
it( "parses Comment", function () { it( "parses Comment", function () {
expect( "start =// comment\n'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =// comment\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =/* comment */'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =/* comment */'abcd'" ).to.parseAs( trivialGrammar );
} ); } );
// Canonical MultiLineComment is "/* comment */". // Canonical MultiLineComment is "/* comment */".
it( "parses MultiLineComment", function () { it( "parses MultiLineComment", function () {
expect( "start =/**/'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =/**/'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =/*a*/'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =/*a*/'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =/*abc*/'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =/*abc*/'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =/**/*/'abcd'" ).to.failToParse(); expect( "start =/**/*/'abcd'" ).to.failToParse();
} ); } );
// Canonical MultiLineCommentNoLineTerminator is "/* comment */". // Canonical MultiLineCommentNoLineTerminator is "/* comment */".
it( "parses MultiLineCommentNoLineTerminator", function () { it( "parses MultiLineCommentNoLineTerminator", function () {
expect( "a = 'abcd'/**/\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar ); expect( "a = 'abcd'/**/\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd'/*a*/\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'/*abc*/\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd'/**/*/\r\nb = 'efgh'" ).to.failToParse(); expect( "a = 'abcd'/**/*/\r\nb = 'efgh'" ).to.failToParse();
expect( "a = 'abcd'/*\n*/\r\nb = 'efgh'" ).to.failToParse(); expect( "a = 'abcd'/*\n*/\r\nb = 'efgh'" ).to.failToParse();
} ); } );
// Canonical SingleLineComment is "// comment". // Canonical SingleLineComment is "// comment".
it( "parses SingleLineComment", function () { it( "parses SingleLineComment", function () {
expect( "start =//\n'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =//\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =//a\n'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =//a\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =//abc\n'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =//abc\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =//\n@\n'abcd'" ).to.failToParse(); expect( "start =//\n@\n'abcd'" ).to.failToParse();
} ); } );
// Canonical Identifier is "a". // Canonical Identifier is "a".
it( "parses Identifier", function () { it( "parses Identifier", function () {
expect( "start = a:'abcd'" ).to.parseAs( oneRuleGrammar( labeledAbcd ) ); expect( "start = a:'abcd'" ).to.parseAs( oneRuleGrammar( labeledAbcd ) );
} ); } );
// Canonical IdentifierName is "a". // Canonical IdentifierName is "a".
it( "parses IdentifierName", function () { it( "parses IdentifierName", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) ); expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
expect( "start = ab" ).to.parseAs( ruleRefGrammar( "ab" ) ); expect( "start = ab" ).to.parseAs( ruleRefGrammar( "ab" ) );
expect( "start = abcd" ).to.parseAs( ruleRefGrammar( "abcd" ) ); expect( "start = abcd" ).to.parseAs( ruleRefGrammar( "abcd" ) );
} ); } );
// Canonical IdentifierStart is "a". // Canonical IdentifierStart is "a".
it( "parses IdentifierStart", function () { it( "parses IdentifierStart", function () {
expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) ); expect( "start = a" ).to.parseAs( ruleRefGrammar( "a" ) );
expect( "start = $" ).to.parseAs( ruleRefGrammar( "$" ) ); expect( "start = $" ).to.parseAs( ruleRefGrammar( "$" ) );
expect( "start = _" ).to.parseAs( ruleRefGrammar( "_" ) ); expect( "start = _" ).to.parseAs( ruleRefGrammar( "_" ) );
expect( "start = \\u0061" ).to.parseAs( ruleRefGrammar( "a" ) ); expect( "start = \\u0061" ).to.parseAs( ruleRefGrammar( "a" ) );
} ); } );
// Canonical IdentifierPart is "a". // Canonical IdentifierPart is "a".
it( "parses IdentifierPart", function () { it( "parses IdentifierPart", function () {
expect( "start = aa" ).to.parseAs( ruleRefGrammar( "aa" ) ); expect( "start = aa" ).to.parseAs( ruleRefGrammar( "aa" ) );
expect( "start = a\u0300" ).to.parseAs( ruleRefGrammar( "a\u0300" ) ); expect( "start = a\u0300" ).to.parseAs( ruleRefGrammar( "a\u0300" ) );
expect( "start = a0" ).to.parseAs( ruleRefGrammar( "a0" ) ); expect( "start = a0" ).to.parseAs( ruleRefGrammar( "a0" ) );
expect( "start = a\u203F" ).to.parseAs( ruleRefGrammar( "a\u203F" ) ); expect( "start = a\u203F" ).to.parseAs( ruleRefGrammar( "a\u203F" ) );
expect( "start = a\u200C" ).to.parseAs( ruleRefGrammar( "a\u200C" ) ); expect( "start = a\u200C" ).to.parseAs( ruleRefGrammar( "a\u200C" ) );
expect( "start = a\u200D" ).to.parseAs( ruleRefGrammar( "a\u200D" ) ); expect( "start = a\u200D" ).to.parseAs( ruleRefGrammar( "a\u200D" ) );
} ); } );
// Unicode rules and reserved word rules are not tested. // Unicode rules and reserved word rules are not tested.
// Canonical LiteralMatcher is "'abcd'". // Canonical LiteralMatcher is "'abcd'".
it( "parses LiteralMatcher", function () { it( "parses LiteralMatcher", function () {
expect( "start = 'abcd'" ).to.parseAs( literalGrammar( "abcd", false ) ); expect( "start = 'abcd'" ).to.parseAs( literalGrammar( "abcd", false ) );
expect( "start = 'abcd'i" ).to.parseAs( literalGrammar( "abcd", true ) ); expect( "start = 'abcd'i" ).to.parseAs( literalGrammar( "abcd", true ) );
} ); } );
// Canonical StringLiteral is "'abcd'". // Canonical StringLiteral is "'abcd'".
it( "parses StringLiteral", function () { it( "parses StringLiteral", function () {
expect( "start = \"\"" ).to.parseAs( literalGrammar( "", false ) ); expect( "start = \"\"" ).to.parseAs( literalGrammar( "", false ) );
expect( "start = \"a\"" ).to.parseAs( literalGrammar( "a", false ) ); expect( "start = \"a\"" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = \"abc\"" ).to.parseAs( literalGrammar( "abc", false ) ); expect( "start = \"abc\"" ).to.parseAs( literalGrammar( "abc", false ) );
@ -490,10 +601,12 @@ describe("PEG.js grammar parser", function() {
expect( "start = ''" ).to.parseAs( literalGrammar( "", false ) ); expect( "start = ''" ).to.parseAs( literalGrammar( "", false ) );
expect( "start = 'a'" ).to.parseAs( literalGrammar( "a", false ) ); expect( "start = 'a'" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = 'abc'" ).to.parseAs( literalGrammar( "abc", false ) ); expect( "start = 'abc'" ).to.parseAs( literalGrammar( "abc", false ) );
} ); } );
// Canonical DoubleStringCharacter is "a". // Canonical DoubleStringCharacter is "a".
it( "parses DoubleStringCharacter", function () { it( "parses DoubleStringCharacter", function () {
expect( "start = \"a\"" ).to.parseAs( literalGrammar( "a", false ) ); expect( "start = \"a\"" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = \"\\n\"" ).to.parseAs( literalGrammar( "\n", false ) ); expect( "start = \"\\n\"" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = \"\\\n\"" ).to.parseAs( literalGrammar( "", false ) ); expect( "start = \"\\\n\"" ).to.parseAs( literalGrammar( "", false ) );
@ -501,10 +614,12 @@ describe("PEG.js grammar parser", function() {
expect( "start = \"\"\"" ).to.failToParse(); expect( "start = \"\"\"" ).to.failToParse();
expect( "start = \"\\\"" ).to.failToParse(); expect( "start = \"\\\"" ).to.failToParse();
expect( "start = \"\n\"" ).to.failToParse(); expect( "start = \"\n\"" ).to.failToParse();
} ); } );
// Canonical SingleStringCharacter is "a". // Canonical SingleStringCharacter is "a".
it( "parses SingleStringCharacter", function () { it( "parses SingleStringCharacter", function () {
expect( "start = 'a'" ).to.parseAs( literalGrammar( "a", false ) ); expect( "start = 'a'" ).to.parseAs( literalGrammar( "a", false ) );
expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) ); expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = '\\\n'" ).to.parseAs( literalGrammar( "", false ) ); expect( "start = '\\\n'" ).to.parseAs( literalGrammar( "", false ) );
@ -512,10 +627,12 @@ describe("PEG.js grammar parser", function() {
expect( "start = '''" ).to.failToParse(); expect( "start = '''" ).to.failToParse();
expect( "start = '\\'" ).to.failToParse(); expect( "start = '\\'" ).to.failToParse();
expect( "start = '\n'" ).to.failToParse(); expect( "start = '\n'" ).to.failToParse();
} ); } );
// Canonical CharacterClassMatcher is "[a-d]". // Canonical CharacterClassMatcher is "[a-d]".
it( "parses CharacterClassMatcher", function () { it( "parses CharacterClassMatcher", function () {
expect( "start = []" ).to.parseAs( expect( "start = []" ).to.parseAs(
classGrammar( [], false, false ) classGrammar( [], false, false )
); );
@ -542,20 +659,24 @@ describe("PEG.js grammar parser", function() {
expect( "start = [\\\n]" ).to.parseAs( expect( "start = [\\\n]" ).to.parseAs(
classGrammar( [], false, false ) classGrammar( [], false, false )
); );
} ); } );
// Canonical ClassCharacterRange is "a-d". // Canonical ClassCharacterRange is "a-d".
it( "parses ClassCharacterRange", function () { it( "parses ClassCharacterRange", function () {
expect( "start = [a-d]" ).to.parseAs( classGrammar( [ [ "a", "d" ] ], false, false ) ); expect( "start = [a-d]" ).to.parseAs( classGrammar( [ [ "a", "d" ] ], false, false ) );
expect( "start = [a-a]" ).to.parseAs( classGrammar( [ [ "a", "a" ] ], false, false ) ); expect( "start = [a-a]" ).to.parseAs( classGrammar( [ [ "a", "a" ] ], false, false ) );
expect( "start = [b-a]" ).to.failToParse( { expect( "start = [b-a]" ).to.failToParse( {
message: "Invalid character range: b-a." message: "Invalid character range: b-a."
} ); } );
} ); } );
// Canonical ClassCharacter is "a". // Canonical ClassCharacter is "a".
it( "parses ClassCharacter", function () { it( "parses ClassCharacter", function () {
expect( "start = [a]" ).to.parseAs( classGrammar( [ "a" ], false, false ) ); expect( "start = [a]" ).to.parseAs( classGrammar( [ "a" ], false, false ) );
expect( "start = [\\n]" ).to.parseAs( classGrammar( [ "\n" ], false, false ) ); expect( "start = [\\n]" ).to.parseAs( classGrammar( [ "\n" ], false, false ) );
expect( "start = [\\\n]" ).to.parseAs( classGrammar( [], false, false ) ); expect( "start = [\\\n]" ).to.parseAs( classGrammar( [], false, false ) );
@ -563,31 +684,39 @@ describe("PEG.js grammar parser", function() {
expect( "start = []]" ).to.failToParse(); expect( "start = []]" ).to.failToParse();
expect( "start = [\\]" ).to.failToParse(); expect( "start = [\\]" ).to.failToParse();
expect( "start = [\n]" ).to.failToParse(); expect( "start = [\n]" ).to.failToParse();
} ); } );
// Canonical LineContinuation is "\\\n". // Canonical LineContinuation is "\\\n".
it( "parses LineContinuation", function () { it( "parses LineContinuation", function () {
expect( "start = '\\\r\n'" ).to.parseAs( literalGrammar( "", false ) ); expect( "start = '\\\r\n'" ).to.parseAs( literalGrammar( "", false ) );
} ); } );
// Canonical EscapeSequence is "n". // Canonical EscapeSequence is "n".
it( "parses EscapeSequence", function () { it( "parses EscapeSequence", function () {
expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) ); expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = '\\0'" ).to.parseAs( literalGrammar( "\x00", false ) ); expect( "start = '\\0'" ).to.parseAs( literalGrammar( "\x00", false ) );
expect( "start = '\\xFF'" ).to.parseAs( literalGrammar( "\xFF", false ) ); expect( "start = '\\xFF'" ).to.parseAs( literalGrammar( "\xFF", false ) );
expect( "start = '\\uFFFF'" ).to.parseAs( literalGrammar( "\uFFFF", false ) ); expect( "start = '\\uFFFF'" ).to.parseAs( literalGrammar( "\uFFFF", false ) );
expect( "start = '\\09'" ).to.failToParse(); expect( "start = '\\09'" ).to.failToParse();
} ); } );
// Canonical CharacterEscapeSequence is "n". // Canonical CharacterEscapeSequence is "n".
it( "parses CharacterEscapeSequence", function () { it( "parses CharacterEscapeSequence", function () {
expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) ); expect( "start = '\\n'" ).to.parseAs( literalGrammar( "\n", false ) );
expect( "start = '\\a'" ).to.parseAs( literalGrammar( "a", false ) ); expect( "start = '\\a'" ).to.parseAs( literalGrammar( "a", false ) );
} ); } );
// Canonical SingleEscapeCharacter is "n". // Canonical SingleEscapeCharacter is "n".
it( "parses SingleEscapeCharacter", function () { it( "parses SingleEscapeCharacter", function () {
expect( "start = '\\''" ).to.parseAs( literalGrammar( "'", false ) ); expect( "start = '\\''" ).to.parseAs( literalGrammar( "'", false ) );
expect( "start = '\\\"'" ).to.parseAs( literalGrammar( "\"", false ) ); expect( "start = '\\\"'" ).to.parseAs( literalGrammar( "\"", false ) );
expect( "start = '\\\\'" ).to.parseAs( literalGrammar( "\\", false ) ); expect( "start = '\\\\'" ).to.parseAs( literalGrammar( "\\", false ) );
@ -597,14 +726,17 @@ describe("PEG.js grammar parser", function() {
expect( "start = '\\r'" ).to.parseAs( literalGrammar( "\r", false ) ); expect( "start = '\\r'" ).to.parseAs( literalGrammar( "\r", false ) );
expect( "start = '\\t'" ).to.parseAs( literalGrammar( "\t", false ) ); expect( "start = '\\t'" ).to.parseAs( literalGrammar( "\t", false ) );
expect( "start = '\\v'" ).to.parseAs( literalGrammar( "\v", false ) ); expect( "start = '\\v'" ).to.parseAs( literalGrammar( "\v", false ) );
} ); } );
// Canonical NonEscapeCharacter is "a". // Canonical NonEscapeCharacter is "a".
it( "parses NonEscapeCharacter", function () { it( "parses NonEscapeCharacter", function () {
expect( "start = '\\a'" ).to.parseAs( literalGrammar( "a", false ) ); expect( "start = '\\a'" ).to.parseAs( literalGrammar( "a", false ) );
// The negative predicate is impossible to test with PEG.js grammar // The negative predicate is impossible to test with PEG.js grammar
// structure. // structure.
} ); } );
// The EscapeCharacter rule is impossible to test with PEG.js grammar // The EscapeCharacter rule is impossible to test with PEG.js grammar
@ -612,28 +744,37 @@ describe("PEG.js grammar parser", function() {
// Canonical HexEscapeSequence is "xFF". // Canonical HexEscapeSequence is "xFF".
it( "parses HexEscapeSequence", function () { it( "parses HexEscapeSequence", function () {
expect( "start = '\\xFF'" ).to.parseAs( literalGrammar( "\xFF", false ) ); expect( "start = '\\xFF'" ).to.parseAs( literalGrammar( "\xFF", false ) );
} ); } );
// Canonical UnicodeEscapeSequence is "uFFFF". // Canonical UnicodeEscapeSequence is "uFFFF".
it( "parses UnicodeEscapeSequence", function () { it( "parses UnicodeEscapeSequence", function () {
expect( "start = '\\uFFFF'" ).to.parseAs( literalGrammar( "\uFFFF", false ) ); expect( "start = '\\uFFFF'" ).to.parseAs( literalGrammar( "\uFFFF", false ) );
} ); } );
// Digit rules are not tested. // Digit rules are not tested.
// Canonical AnyMatcher is ".". // Canonical AnyMatcher is ".".
it( "parses AnyMatcher", function () { it( "parses AnyMatcher", function () {
expect( "start = ." ).to.parseAs( anyGrammar() ); expect( "start = ." ).to.parseAs( anyGrammar() );
} ); } );
// Canonical CodeBlock is "{ code }". // Canonical CodeBlock is "{ code }".
it( "parses CodeBlock", function () { it( "parses CodeBlock", function () {
expect( "start = 'abcd' { code }" ).to.parseAs( actionGrammar( " code " ) ); expect( "start = 'abcd' { code }" ).to.parseAs( actionGrammar( " code " ) );
} ); } );
// Canonical Code is " code ". // Canonical Code is " code ".
it( "parses Code", function () { it( "parses Code", function () {
expect( "start = 'abcd' {a}" ).to.parseAs( actionGrammar( "a" ) ); expect( "start = 'abcd' {a}" ).to.parseAs( actionGrammar( "a" ) );
expect( "start = 'abcd' {abc}" ).to.parseAs( actionGrammar( "abc" ) ); expect( "start = 'abcd' {abc}" ).to.parseAs( actionGrammar( "abc" ) );
expect( "start = 'abcd' {{a}}" ).to.parseAs( actionGrammar( "{a}" ) ); expect( "start = 'abcd' {{a}}" ).to.parseAs( actionGrammar( "{a}" ) );
@ -641,41 +782,51 @@ describe("PEG.js grammar parser", function() {
expect( "start = 'abcd' {{}" ).to.failToParse(); expect( "start = 'abcd' {{}" ).to.failToParse();
expect( "start = 'abcd' {}}" ).to.failToParse(); expect( "start = 'abcd' {}}" ).to.failToParse();
} ); } );
// Unicode character category rules and token rules are not tested. // Unicode character category rules and token rules are not tested.
// Canonical __ is "\n". // Canonical __ is "\n".
it( "parses __", function () { it( "parses __", function () {
expect( "start ='abcd'" ).to.parseAs( trivialGrammar ); expect( "start ='abcd'" ).to.parseAs( trivialGrammar );
expect( "start = 'abcd'" ).to.parseAs( trivialGrammar ); expect( "start = 'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =\r\n'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =\r\n'abcd'" ).to.parseAs( trivialGrammar );
expect( "start =/* comment */'abcd'" ).to.parseAs( trivialGrammar ); expect( "start =/* comment */'abcd'" ).to.parseAs( trivialGrammar );
expect( "start = 'abcd'" ).to.parseAs( trivialGrammar ); expect( "start = 'abcd'" ).to.parseAs( trivialGrammar );
} ); } );
// Canonical _ is " ". // Canonical _ is " ".
it( "parses _", function () { it( "parses _", function () {
expect( "a = 'abcd'\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar ); expect( "a = 'abcd'\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
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'/* comment */\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd' \r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar ); expect( "a = 'abcd' \r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
} ); } );
// Canonical EOS is ";". // Canonical EOS is ";".
it( "parses EOS", function () { it( "parses EOS", function () {
expect( "a = 'abcd'\n;b = 'efgh'" ).to.parseAs( twoRuleGrammar ); expect( "a = 'abcd'\n;b = 'efgh'" ).to.parseAs( twoRuleGrammar );
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' // comment\r\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
expect( "a = 'abcd'\nb = 'efgh'" ).to.parseAs( twoRuleGrammar ); expect( "a = 'abcd'\nb = 'efgh'" ).to.parseAs( twoRuleGrammar );
} ); } );
// Canonical EOF is the end of input. // Canonical EOF is the end of input.
it( "parses EOF", function () { it( "parses EOF", function () {
expect( "start = 'abcd'\n" ).to.parseAs( trivialGrammar ); expect( "start = 'abcd'\n" ).to.parseAs( trivialGrammar );
} ); } );
it( "reports unmatched brace", function () { it( "reports unmatched brace", function () {
const text = "rule = \n 'x' { y \n z"; const text = "rule = \n 'x' { y \n z";
const errorLocation = { const errorLocation = {
start: { offset: 13, line: 2, column: 6 }, start: { offset: 13, line: 2, column: 6 },
@ -685,5 +836,7 @@ describe("PEG.js grammar parser", function() {
.to.throw( "Unbalanced brace." ) .to.throw( "Unbalanced brace." )
.with.property( "location" ) .with.property( "location" )
.that.deep.equals( errorLocation ); .that.deep.equals( errorLocation );
} ); } );
} ); } );

Loading…
Cancel
Save