Browse Source

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 4 years ago
parent
commit
e6d018a88d
  1. 7
      .eslintrc.js
  2. 475
      bin/options.js
  3. 117
      bin/peg.js
  4. 143
      gulpfile.js
  5. 12
      lib/.eslintrc.js
  6. 152
      lib/compiler/asts.js
  7. 180
      lib/compiler/index.js
  8. 102
      lib/compiler/js.js
  9. 98
      lib/compiler/opcodes.js
  10. 895
      lib/compiler/passes/generate-bytecode.js
  11. 2874
      lib/compiler/passes/generate-js.js
  12. 72
      lib/compiler/passes/remove-proxy-rules.js
  13. 131
      lib/compiler/passes/report-duplicate-labels.js
  14. 50
      lib/compiler/passes/report-duplicate-rules.js
  15. 87
      lib/compiler/passes/report-infinite-recursion.js
  16. 60
      lib/compiler/passes/report-infinite-repetition.js
  17. 59
      lib/compiler/passes/report-undefined-rules.js
  18. 142
      lib/compiler/visitor.js
  19. 20
      lib/grammar-error.js
  20. 102
      lib/peg.js
  21. 1
      package.json
  22. 6
      test/.eslintrc.js
  23. 74
      test/benchmark/benchmarks.js
  24. 279
      test/benchmark/index.js
  25. 330
      test/benchmark/run
  26. 220
      test/benchmark/runner.js
  27. 195
      test/impact
  28. 60
      test/server/run
  29. 380
      test/spec/api/generated-parser-api.spec.js
  30. 517
      test/spec/api/pegjs-api.spec.js
  31. 309
      test/spec/api/plugin-api.spec.js
  32. 3516
      test/spec/behavior/generated-parser-behavior.spec.js
  33. 1480
      test/spec/unit/compiler/passes/generate-bytecode.spec.js
  34. 170
      test/spec/unit/compiler/passes/helpers.js
  35. 124
      test/spec/unit/compiler/passes/remove-proxy-rules.spec.js
  36. 142
      test/spec/unit/compiler/passes/report-duplicate-labels.spec.js
  37. 48
      test/spec/unit/compiler/passes/report-duplicate-rules.spec.js
  38. 250
      test/spec/unit/compiler/passes/report-infinite-recursion.spec.js
  39. 202
      test/spec/unit/compiler/passes/report-infinite-repetition.spec.js
  40. 60
      test/spec/unit/compiler/passes/report-undefined-rules.spec.js
  41. 1485
      test/spec/unit/parser.spec.js

7
.eslintrc.js

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

475
bin/options.js

@ -1,255 +1,304 @@
"use strict";
let fs = require("fs");
let path = require("path");
let peg = require("../");
const fs = require( "fs" );
const path = require( "path" );
const peg = require( "../" );
// Options
let inputFile = null;
let outputFile = null;
let options = {
"--": [],
"cache": false,
"dependencies": {},
"exportVar": null,
"format": "commonjs",
"optimize": "speed",
"output": "source",
"plugins": [],
"trace": false
const options = {
"--": [],
"cache": false,
"dependencies": {},
"exportVar": null,
"format": "commonjs",
"optimize": "speed",
"output": "source",
"plugins": [],
"trace": false
};
const EXPORT_VAR_FORMATS = ["globals", "umd"];
const DEPENDENCY_FORMATS = ["amd", "commonjs", "es", "umd"];
const MODULE_FORMATS = ["amd", "bare", "commonjs", "es", "globals", "umd"];
const OPTIMIZATION_GOALS = ["size", "speed"];
const EXPORT_VAR_FORMATS = [ "globals", "umd" ];
const DEPENDENCY_FORMATS = [ "amd", "commonjs", "es", "umd" ];
const MODULE_FORMATS = [ "amd", "bare", "commonjs", "es", "globals", "umd" ];
const OPTIMIZATION_GOALS = [ "size", "speed" ];
// Helpers
function abort(message) {
console.error(message);
process.exit(1);
function abort( message ) {
console.error( message );
process.exit( 1 );
}
function addExtraOptions(json) {
let extraOptions;
try {
extraOptions = JSON.parse(json);
} catch (e) {
if (!(e instanceof SyntaxError)) { throw e; }
abort("Error parsing JSON: " + e.message);
}
if (typeof extraOptions !== "object") {
abort("The JSON with extra options has to represent an object.");
}
Object
.keys(extraOptions)
.forEach(key => {
options[key] = extraOptions[key];
});
function addExtraOptions( json ) {
let extraOptions;
try {
extraOptions = JSON.parse( json );
} catch ( e ) {
if ( ! ( e instanceof SyntaxError ) ) throw e;
abort( "Error parsing JSON: " + e.message );
}
if ( typeof extraOptions !== "object" ) {
abort( "The JSON with extra options has to represent an object." );
}
Object
.keys( extraOptions )
.forEach( key => {
options[ key ] = extraOptions[ key ];
} );
}
function formatChoicesList(list) {
list = list.map(entry => `"${entry}"`);
let lastOption = list.pop();
function formatChoicesList( list ) {
list = list.map( entry => `"${ entry }"` );
const lastOption = list.pop();
return list.length === 0
? lastOption
: list.join( ", " ) + " or " + lastOption;
return list.length === 0
? lastOption
: list.join(", ") + " or " + lastOption;
}
function updateList(list, string) {
string
.split(",")
.forEach(entry => {
entry = entry.trim();
if (list.indexOf(entry) === -1) {
list.push(entry);
}
});
function updateList( list, string ) {
string
.split( "," )
.forEach( entry => {
entry = entry.trim();
if ( list.indexOf( entry ) === -1 ) {
list.push( entry );
}
} );
}
// Arguments
let args = process.argv.slice(2);
let args = process.argv.slice( 2 );
function nextArg( option ) {
if ( args.length === 0 ) {
function nextArg(option) {
if (args.length === 0) {
abort(`Missing parameter of the ${option} option.`);
}
abort( `Missing parameter of the ${ option } option.` );
}
return args.shift();
return args.shift();
}
// Parse Arguments
while (args.length > 0) {
let json, mod;
let argument = args.shift();
if (argument.indexOf("-") === 0 && argument.indexOf("=") > 1) {
argument = argument.split("=");
args.unshift(argument.length > 2 ? argument.slice(1) : argument[1]);
argument = argument[0];
}
switch (argument) {
case "--":
options["--"] = args;
args = [];
break;
case "-a":
case "--allowed-start-rules":
if (!options.allowedStartRules) {
options.allowedStartRules = [];
}
updateList(options.allowedStartRules, nextArg("--allowed-start-rules"));
break;
case "--cache":
options.cache = true;
break;
case "--no-cache":
options.cache = false;
break;
case "-d":
case "--dependency":
argument = nextArg("-d/--dependency");
if (argument.indexOf(":") === -1) {
mod = [argument, argument];
} else {
mod = argument.split(":");
if (mod.length > 2) {
mod[1] = mod.slice(1);
}
}
options.dependencies[mod[0]] = mod[1];
break;
case "-e":
case "--export-var":
options.exportVar = nextArg("-e/--export-var");
break;
case "--extra-options":
addExtraOptions(nextArg("--extra-options"));
break;
case "-c":
case "--config":
case "--extra-options-file":
argument = nextArg("-c/--config/--extra-options-file");
try {
json = fs.readFileSync(argument, "utf8");
} catch (e) {
abort(`Can't read from file "${argument}".`);
}
addExtraOptions(json);
break;
case "-f":
case "--format":
argument = nextArg("-f/--format");
if (MODULE_FORMATS.indexOf(argument) === -1) {
abort(`Module format must be either ${formatChoicesList(MODULE_FORMATS)}.`);
}
options.format = argument;
break;
case "-h":
case "--help":
console.log(fs.readFileSync(path.join(__dirname, "usage.txt"), "utf8").trim());
process.exit();
break;
case "-O":
case "--optimize":
argument = nextArg("-O/--optimize");
if (OPTIMIZATION_GOALS.indexOf(argument) === -1) {
abort(`Optimization goal must be either ${formatChoicesList(OPTIMIZATION_GOALS)}.`);
}
options.optimize = argument;
break;
case "-o":
case "--output":
outputFile = nextArg("-o/--output");
break;
case "-p":
case "--plugin":
argument = nextArg("-p/--plugin");
try {
mod = require(argument);
} catch (ex1) {
if (ex1.code !== "MODULE_NOT_FOUND") { throw ex1; }
try {
mod = require(path.resolve(argument));
} catch (ex2) {
if (ex2.code !== "MODULE_NOT_FOUND") { throw ex2; }
abort(`Can't load module "${argument}".`);
}
}
options.plugins.push(mod);
break;
case "--trace":
options.trace = true;
break;
case "--no-trace":
options.trace = false;
break;
case "-v":
case "--version":
console.log("PEG.js v" + peg.VERSION);
process.exit();
break;
default:
if (inputFile !== null) {
abort(`Unknown option: "${argument}".`);
}
inputFile = argument;
}
while ( args.length > 0 ) {
let json, mod;
let argument = args.shift();
if ( argument.indexOf( "-" ) === 0 && argument.indexOf( "=" ) > 1 ) {
argument = argument.split( "=" );
args.unshift( argument.length > 2 ? argument.slice( 1 ) : argument[ 1 ] );
argument = argument[ 0 ];
}
switch ( argument ) {
case "--":
options[ "--" ] = args;
args = [];
break;
case "-a":
case "--allowed-start-rules":
if ( ! options.allowedStartRules ) options.allowedStartRules = [];
updateList( options.allowedStartRules, nextArg( "--allowed-start-rules" ) );
break;
case "--cache":
options.cache = true;
break;
case "--no-cache":
options.cache = false;
break;
case "-d":
case "--dependency":
argument = nextArg( "-d/--dependency" );
mod = argument.split( ":" );
if ( mod.length === 1 ) mod = [ argument, argument ];
else if ( mod.length > 2 ) mod[ 1 ] = mod.slice( 1 );
options.dependencies[ mod[ 0 ] ] = mod[ 1 ];
break;
case "-e":
case "--export-var":
options.exportVar = nextArg( "-e/--export-var" );
break;
case "--extra-options":
addExtraOptions( nextArg( "--extra-options" ) );
break;
case "-c":
case "--config":
case "--extra-options-file":
argument = nextArg( "-c/--config/--extra-options-file" );
try {
json = fs.readFileSync( argument, "utf8" );
} catch ( e ) {
abort( `Can't read from file "${ argument }".` );
}
addExtraOptions( json );
break;
case "-f":
case "--format":
argument = nextArg( "-f/--format" );
if ( MODULE_FORMATS.indexOf( argument ) === -1 ) {
abort( `Module format must be either ${ formatChoicesList( MODULE_FORMATS ) }.` );
}
options.format = argument;
break;
case "-h":
case "--help":
console.log( fs.readFileSync( path.join( __dirname, "usage.txt" ), "utf8" ).trim() );
process.exit();
break;
case "-O":
case "--optimize":
argument = nextArg( "-O/--optimize" );
if ( OPTIMIZATION_GOALS.indexOf( argument ) === -1 ) {
abort( `Optimization goal must be either ${ formatChoicesList( OPTIMIZATION_GOALS ) }.` );
}
options.optimize = argument;
break;
case "-o":
case "--output":
outputFile = nextArg( "-o/--output" );
break;
case "-p":
case "--plugin":
argument = nextArg( "-p/--plugin" );
try {
mod = require( argument );
} catch ( ex1 ) {
if ( ex1.code !== "MODULE_NOT_FOUND" ) throw ex1;
try {
mod = require( path.resolve( argument ) );
} catch ( ex2 ) {
if ( ex2.code !== "MODULE_NOT_FOUND" ) throw ex2;
abort( `Can't load module "${ argument }".` );
}
}
options.plugins.push( mod );
break;
case "--trace":
options.trace = true;
break;
case "--no-trace":
options.trace = false;
break;
case "-v":
case "--version":
console.log( "PEG.js v" + peg.VERSION );
process.exit();
break;
default:
if ( inputFile !== null ) {
abort( `Unknown option: "${ argument }".` );
}
inputFile = argument;
}
}
// Validation and defaults
if (Object.keys(options.dependencies).length > 0) {
if (DEPENDENCY_FORMATS.indexOf(options.format) === -1) {
abort(`Can't use the -d/--dependency option with the "${options.format}" module format.`);
}
}
if ( Object.keys( options.dependencies ).length > 0 ) {
if ( DEPENDENCY_FORMATS.indexOf( options.format ) === -1 ) {
abort( `Can't use the -d/--dependency option with the "${ options.format }" module format.` );
}
if (options.exportVar !== null) {
if (EXPORT_VAR_FORMATS.indexOf(options.format) === -1) {
abort(`Can't use the -e/--export-var option with the "${options.format}" module format.`);
}
}
if (inputFile === null) {
inputFile = "-";
if ( options.exportVar !== null ) {
if ( EXPORT_VAR_FORMATS.indexOf( options.format ) === -1 ) {
abort( `Can't use the -e/--export-var option with the "${ options.format }" module format.` );
}
}
if (outputFile === null) {
if (inputFile === "-") {
outputFile = "-";
} else if (inputFile) {
outputFile = inputFile.substr(0, inputFile.length - path.extname(inputFile).length) + ".js";
}
if ( inputFile === null ) inputFile = "-";
if ( outputFile === null ) {
if ( inputFile === "-" ) outputFile = "-";
else if ( inputFile ) {
outputFile = inputFile
.substr( 0, inputFile.length - path.extname( inputFile ).length )
+ ".js";
}
}
// Export

117
bin/peg.js

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

143
gulpfile.js

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

12
lib/.eslintrc.js

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

152
lib/compiler/asts.js

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

180
lib/compiler/index.js

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

102
lib/compiler/js.js

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

98
lib/compiler/opcodes.js

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

895
lib/compiler/passes/generate-bytecode.js

@ -1,9 +1,9 @@
"use strict";
let asts = require("../asts");
let js = require("../js");
let op = require("../opcodes");
let visitor = require("../visitor");
const asts = require( "../asts" );
const js = require( "../js" );
const op = require( "../opcodes" );
const visitor = require( "../visitor" );
// Generates bytecode.
//
@ -187,431 +187,494 @@ let visitor = require("../visitor");
// [29] SILENT_FAILS_OFF
//
// silentFails--;
function generateBytecode(ast) {
let consts = [];
function addConst(value) {
let index = consts.indexOf(value);
return index === -1 ? consts.push(value) - 1 : index;
}
function addFunctionConst(params, code) {
return addConst(
"function(" + params.join(", ") + ") {" + code + "}"
);
}
function cloneEnv(env) {
let clone = {};
Object.keys(env).forEach(name => {
clone[name] = env[name];
});
return clone;
}
function buildSequence() {
return Array.prototype.concat.apply([], arguments);
}
function buildCondition(condCode, thenCode, elseCode) {
return condCode.concat(
[thenCode.length, elseCode.length],
thenCode,
elseCode
);
}
function buildLoop(condCode, bodyCode) {
return condCode.concat([bodyCode.length], bodyCode);
}
function buildCall(functionIndex, delta, env, sp) {
let params = Object.keys(env).map(name => sp - env[name]);
return [op.CALL, functionIndex, delta, params.length].concat(params);
}
function buildSimplePredicate(expression, negative, context) {
return buildSequence(
[op.PUSH_CURR_POS],
[op.SILENT_FAILS_ON],
generate(expression, {
sp: context.sp + 1,
env: cloneEnv(context.env),
action: null
}),
[op.SILENT_FAILS_OFF],
buildCondition(
[negative ? op.IF_ERROR : op.IF_NOT_ERROR],
buildSequence(
[op.POP],
[negative ? op.POP : op.POP_CURR_POS],
[op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
[negative ? op.POP_CURR_POS : op.POP],
[op.PUSH_FAILED]
)
)
);
}
function buildSemanticPredicate(code, negative, context) {
let functionIndex = addFunctionConst(Object.keys(context.env), code);
return buildSequence(
[op.UPDATE_SAVED_POS],
buildCall(functionIndex, 0, context.env, context.sp),
buildCondition(
[op.IF],
buildSequence(
[op.POP],
negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]
)
)
);
}
function buildAppendLoop(expressionCode) {
return buildLoop(
[op.WHILE_NOT_ERROR],
buildSequence([op.APPEND], expressionCode)
);
}
let generate = visitor.build({
grammar(node) {
node.rules.forEach(generate);
node.consts = consts;
},
rule(node) {
node.bytecode = generate(node.expression, {
sp: -1, // stack pointer
env: { }, // mapping of label names to stack positions
action: null // action nodes pass themselves to children here
});
},
named(node, context) {
let nameIndex = addConst(
"peg$otherExpectation(\"" + js.stringEscape(node.name) + "\")"
);
// The code generated below is slightly suboptimal because |FAIL| pushes
// to the stack, so we need to stick a |POP| in front of it. We lack a
// dedicated instruction that would just report the failure and not touch
// the stack.
return buildSequence(
[op.SILENT_FAILS_ON],
generate(node.expression, context),
[op.SILENT_FAILS_OFF],
buildCondition([op.IF_ERROR], [op.FAIL, nameIndex], [])
);
},
choice(node, context) {
function buildAlternativesCode(alternatives, context