|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var fs = require("fs");
|
|
|
|
var path = require("path");
|
|
|
|
var peg = require("../lib/peg");
|
|
|
|
|
|
|
|
/* Helpers */
|
|
|
|
|
|
|
|
function printVersion() {
|
|
|
|
console.log("PEG.js " + peg.VERSION);
|
|
|
|
}
|
|
|
|
|
|
|
|
function printHelp() {
|
|
|
|
console.log("Usage: pegjs [options] [--] [<input_file>]");
|
|
|
|
console.log("");
|
|
|
|
console.log("Options:");
|
|
|
|
console.log(" --allowed-start-rules <rules> comma-separated list of rules the generated");
|
|
|
|
console.log(" parser will be allowed to start parsing");
|
|
|
|
console.log(" from (default: the first rule in the");
|
|
|
|
console.log(" grammar)");
|
|
|
|
console.log(" --cache make generated parser cache results");
|
|
|
|
console.log(" -d, --dependency <dependency> use specified dependency (can be specified");
|
|
|
|
console.log(" multiple times)");
|
|
|
|
console.log(" -e, --export-var <variable> name of a global variable into which the");
|
|
|
|
console.log(" parser object is assigned to when no module");
|
|
|
|
console.log(" loader is detected");
|
|
|
|
console.log(" --extra-options <options> additional options (in JSON format) to pass");
|
|
|
|
console.log(" to peg.generate");
|
|
|
|
console.log(" --extra-options-file <file> file with additional options (in JSON");
|
|
|
|
console.log(" format) to pass to peg.generate");
|
|
|
|
console.log(" --format <format> format of the generated parser: amd,");
|
|
|
|
console.log(" commonjs, globals, umd (default: commonjs)");
|
|
|
|
console.log(" -h, --help print help and exit");
|
|
|
|
console.log(" -O, --optimize <goal> select optimization for speed or size");
|
|
|
|
console.log(" (default: speed)");
|
|
|
|
console.log(" -o, --output <file> output file");
|
|
|
|
console.log(" --plugin <plugin> use a specified plugin (can be specified");
|
|
|
|
console.log(" multiple times)");
|
|
|
|
console.log(" --trace enable tracing in generated parser");
|
|
|
|
console.log(" -v, --version print version information and exit");
|
|
|
|
}
|
|
|
|
|
|
|
|
function exitSuccess() {
|
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
function exitFailure() {
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function abort(message) {
|
|
|
|
console.error(message);
|
|
|
|
exitFailure();
|
|
|
|
}
|
|
|
|
|
|
|
|
function addExtraOptions(options, json) {
|
Fix JSHint errors in bin/pegjs
Fixes the following JSHint errors:
bin/pegjs: line 66, col 14, 'extraOptions' used out of scope.
bin/pegjs: line 70, col 19, 'extraOptions' used out of scope.
bin/pegjs: line 71, col 20, 'extraOptions' used out of scope.
bin/pegjs: line 80, col 10, Wrap the /regexp/ literal in parens to disambiguate the slash operator.
bin/pegjs: line 128, col 43, Missing semicolon.
bin/pegjs: line 128, col 45, Don't make functions within a loop.
bin/pegjs: line 150, col 13, Redefinition of 'module'.
bin/pegjs: line 217, col 34, Expected '===' and instead saw '=='.
bin/pegjs: line 243, col 44, 'source' used out of scope.
bin/pegjs: line 243, col 61, 'source' used out of scope.
11 years ago
|
|
|
var extraOptions;
|
|
|
|
|
|
|
|
try {
|
Fix JSHint errors in bin/pegjs
Fixes the following JSHint errors:
bin/pegjs: line 66, col 14, 'extraOptions' used out of scope.
bin/pegjs: line 70, col 19, 'extraOptions' used out of scope.
bin/pegjs: line 71, col 20, 'extraOptions' used out of scope.
bin/pegjs: line 80, col 10, Wrap the /regexp/ literal in parens to disambiguate the slash operator.
bin/pegjs: line 128, col 43, Missing semicolon.
bin/pegjs: line 128, col 45, Don't make functions within a loop.
bin/pegjs: line 150, col 13, Redefinition of 'module'.
bin/pegjs: line 217, col 34, Expected '===' and instead saw '=='.
bin/pegjs: line 243, col 44, 'source' used out of scope.
bin/pegjs: line 243, col 61, 'source' used out of scope.
11 years ago
|
|
|
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.");
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var key in extraOptions) {
|
|
|
|
if (extraOptions.hasOwnProperty(key)) {
|
|
|
|
options[key] = extraOptions[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fix JSHint errors in bin/pegjs
Fixes the following JSHint errors:
bin/pegjs: line 66, col 14, 'extraOptions' used out of scope.
bin/pegjs: line 70, col 19, 'extraOptions' used out of scope.
bin/pegjs: line 71, col 20, 'extraOptions' used out of scope.
bin/pegjs: line 80, col 10, Wrap the /regexp/ literal in parens to disambiguate the slash operator.
bin/pegjs: line 128, col 43, Missing semicolon.
bin/pegjs: line 128, col 45, Don't make functions within a loop.
bin/pegjs: line 150, col 13, Redefinition of 'module'.
bin/pegjs: line 217, col 34, Expected '===' and instead saw '=='.
bin/pegjs: line 243, col 44, 'source' used out of scope.
bin/pegjs: line 243, col 61, 'source' used out of scope.
11 years ago
|
|
|
/*
|
|
|
|
* Extracted into a function just to silence JSHint complaining about creating
|
|
|
|
* functions in a loop.
|
|
|
|
*/
|
|
|
|
function trim(s) {
|
|
|
|
return s.trim();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Arguments */
|
|
|
|
|
|
|
|
var args = process.argv.slice(2); // Trim "node" and the script path.
|
|
|
|
|
|
|
|
function isOption(arg) {
|
|
|
|
return (/^-.+/).test(arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
function nextArg() {
|
|
|
|
args.shift();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Files */
|
|
|
|
|
|
|
|
function readStream(inputStream, callback) {
|
|
|
|
var input = "";
|
|
|
|
inputStream.on("data", function(data) { input += data; });
|
|
|
|
inputStream.on("end", function() { callback(input); });
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Main */
|
|
|
|
|
|
|
|
var inputFile = null;
|
|
|
|
var outputFile = null;
|
|
|
|
|
|
|
|
var options = {
|
|
|
|
cache: false,
|
|
|
|
dependencies: {},
|
|
|
|
exportVar: null,
|
|
|
|
format: "commonjs",
|
|
|
|
optimize: "speed",
|
|
|
|
output: "source",
|
|
|
|
plugins: [],
|
|
|
|
trace: false
|
|
|
|
};
|
|
|
|
|
|
|
|
while (args.length > 0 && isOption(args[0])) {
|
|
|
|
switch (args[0]) {
|
|
|
|
case "--allowed-start-rules":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the -e/--allowed-start-rules option.");
|
|
|
|
}
|
|
|
|
options.allowedStartRules = args[0]
|
|
|
|
.split(",")
|
|
|
|
.map(trim);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "--cache":
|
|
|
|
options.cache = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "-d":
|
|
|
|
case "--dependency":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the -d/--dependency option.");
|
|
|
|
}
|
|
|
|
if (args[0].indexOf(":") !== -1) {
|
|
|
|
var parts = args[0].split(":");
|
|
|
|
options.dependencies[parts[0]] = parts[1];
|
|
|
|
} else {
|
|
|
|
options.dependencies[args[0]] = args[0];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "-e":
|
|
|
|
case "--export-var":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the -e/--export-var option.");
|
|
|
|
}
|
|
|
|
options.exportVar = args[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "--extra-options":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the --extra-options option.");
|
|
|
|
}
|
|
|
|
addExtraOptions(options, args[0]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "--extra-options-file":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the --extra-options-file option.");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
var json = fs.readFileSync(args[0]);
|
|
|
|
} catch(e) {
|
|
|
|
abort("Can't read from file \"" + args[0] + "\".");
|
|
|
|
}
|
|
|
|
addExtraOptions(options, json);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "--format":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the --format option.");
|
|
|
|
}
|
|
|
|
if (args[0] !== "amd" && args[0] !== "commonjs" && args[0] !== "globals" && args[0] !== "umd") {
|
|
|
|
abort("Module format must be one of \"amd\", \"commonjs\", \"globals\", and \"umd\".");
|
|
|
|
}
|
|
|
|
options.format = args[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "-h":
|
|
|
|
case "--help":
|
|
|
|
printHelp();
|
|
|
|
exitSuccess();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "-O":
|
Code generator rewrite
This is a complete rewrite of the PEG.js code generator. Its goals are:
1. Allow optimizing the generated parser code for code size as well as
for parsing speed.
2. Prepare ground for future optimizations and big features (like
incremental parsing).
2. Replace the old template-based code-generation system with
something more lightweight and flexible.
4. General code cleanup (structure, style, variable names, ...).
New Architecture
----------------
The new code generator consists of two steps:
* Bytecode generator -- produces bytecode for an abstract virtual
machine
* JavaScript generator -- produces JavaScript code based on the
bytecode
The abstract virtual machine is stack-based. Originally I wanted to make
it register-based, but it turned out that all the code related to it
would be more complex and the bytecode itself would be longer (because
of explicit register specifications in instructions). The only downsides
of the stack-based approach seem to be few small inefficiencies (see
e.g. the |NIP| instruction), which seem to be insignificant.
The new generator allows optimizing for parsing speed or code size (you
can choose using the |optimize| option of the |PEG.buildParser| method
or the --optimize/-o option on the command-line).
When optimizing for size, the JavaScript generator emits the bytecode
together with its constant table and a generic bytecode interpreter.
Because the interpreter is small and the bytecode and constant table
grow only slowly with size of the grammar, the resulting parser is also
small.
When optimizing for speed, the JavaScript generator just compiles the
bytecode into JavaScript. The generated code is relatively efficient, so
the resulting parser is fast.
Internal Identifiers
--------------------
As a small bonus, all internal identifiers visible to user code in the
initializer, actions and predicates are prefixed by |peg$|. This lowers
the chance that identifiers in user code will conflict with the ones
from PEG.js. It also makes using any internals in user code ugly, which
is a good thing. This solves GH-92.
Performance
-----------
The new code generator improved parsing speed and parser code size
significantly. The generated parsers are now:
* 39% faster when optimizing for speed
* 69% smaller when optimizing for size (without minification)
* 31% smaller when optimizing for size (with minification)
(Parsing speed was measured using the |benchmark/run| script. Code size
was measured by generating parsers for examples in the |examples|
directory and adding up the file sizes. Minification was done by |uglify
--ascii| in version 1.3.4.)
Final Note
----------
This is just a beginning! The new code generator lays a foundation upon
which many optimizations and improvements can (and will) be made.
Stay tuned :-)
12 years ago
|
|
|
case "--optimize":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the -O/--optimize option.");
|
Code generator rewrite
This is a complete rewrite of the PEG.js code generator. Its goals are:
1. Allow optimizing the generated parser code for code size as well as
for parsing speed.
2. Prepare ground for future optimizations and big features (like
incremental parsing).
2. Replace the old template-based code-generation system with
something more lightweight and flexible.
4. General code cleanup (structure, style, variable names, ...).
New Architecture
----------------
The new code generator consists of two steps:
* Bytecode generator -- produces bytecode for an abstract virtual
machine
* JavaScript generator -- produces JavaScript code based on the
bytecode
The abstract virtual machine is stack-based. Originally I wanted to make
it register-based, but it turned out that all the code related to it
would be more complex and the bytecode itself would be longer (because
of explicit register specifications in instructions). The only downsides
of the stack-based approach seem to be few small inefficiencies (see
e.g. the |NIP| instruction), which seem to be insignificant.
The new generator allows optimizing for parsing speed or code size (you
can choose using the |optimize| option of the |PEG.buildParser| method
or the --optimize/-o option on the command-line).
When optimizing for size, the JavaScript generator emits the bytecode
together with its constant table and a generic bytecode interpreter.
Because the interpreter is small and the bytecode and constant table
grow only slowly with size of the grammar, the resulting parser is also
small.
When optimizing for speed, the JavaScript generator just compiles the
bytecode into JavaScript. The generated code is relatively efficient, so
the resulting parser is fast.
Internal Identifiers
--------------------
As a small bonus, all internal identifiers visible to user code in the
initializer, actions and predicates are prefixed by |peg$|. This lowers
the chance that identifiers in user code will conflict with the ones
from PEG.js. It also makes using any internals in user code ugly, which
is a good thing. This solves GH-92.
Performance
-----------
The new code generator improved parsing speed and parser code size
significantly. The generated parsers are now:
* 39% faster when optimizing for speed
* 69% smaller when optimizing for size (without minification)
* 31% smaller when optimizing for size (with minification)
(Parsing speed was measured using the |benchmark/run| script. Code size
was measured by generating parsers for examples in the |examples|
directory and adding up the file sizes. Minification was done by |uglify
--ascii| in version 1.3.4.)
Final Note
----------
This is just a beginning! The new code generator lays a foundation upon
which many optimizations and improvements can (and will) be made.
Stay tuned :-)
12 years ago
|
|
|
}
|
|
|
|
if (args[0] !== "speed" && args[0] !== "size") {
|
|
|
|
abort("Optimization goal must be either \"speed\" or \"size\".");
|
|
|
|
}
|
|
|
|
options.optimize = args[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "-o":
|
|
|
|
case "--output":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the -o/--output option.");
|
|
|
|
}
|
|
|
|
outputFile = args[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "--plugin":
|
|
|
|
nextArg();
|
|
|
|
if (args.length === 0) {
|
|
|
|
abort("Missing parameter of the --plugin option.");
|
|
|
|
}
|
|
|
|
var id = /^(\.\/|\.\.\/)/.test(args[0]) ? path.resolve(args[0]) : args[0];
|
Fix JSHint errors in bin/pegjs
Fixes the following JSHint errors:
bin/pegjs: line 66, col 14, 'extraOptions' used out of scope.
bin/pegjs: line 70, col 19, 'extraOptions' used out of scope.
bin/pegjs: line 71, col 20, 'extraOptions' used out of scope.
bin/pegjs: line 80, col 10, Wrap the /regexp/ literal in parens to disambiguate the slash operator.
bin/pegjs: line 128, col 43, Missing semicolon.
bin/pegjs: line 128, col 45, Don't make functions within a loop.
bin/pegjs: line 150, col 13, Redefinition of 'module'.
bin/pegjs: line 217, col 34, Expected '===' and instead saw '=='.
bin/pegjs: line 243, col 44, 'source' used out of scope.
bin/pegjs: line 243, col 61, 'source' used out of scope.
11 years ago
|
|
|
var mod;
|
|
|
|
try {
|
Fix JSHint errors in bin/pegjs
Fixes the following JSHint errors:
bin/pegjs: line 66, col 14, 'extraOptions' used out of scope.
bin/pegjs: line 70, col 19, 'extraOptions' used out of scope.
bin/pegjs: line 71, col 20, 'extraOptions' used out of scope.
bin/pegjs: line 80, col 10, Wrap the /regexp/ literal in parens to disambiguate the slash operator.
bin/pegjs: line 128, col 43, Missing semicolon.
bin/pegjs: line 128, col 45, Don't make functions within a loop.
bin/pegjs: line 150, col 13, Redefinition of 'module'.
bin/pegjs: line 217, col 34, Expected '===' and instead saw '=='.
bin/pegjs: line 243, col 44, 'source' used out of scope.
bin/pegjs: line 243, col 61, 'source' used out of scope.
11 years ago
|
|
|
mod = require(id);
|
|
|
|
} catch (e) {
|
|
|
|
if (e.code !== "MODULE_NOT_FOUND") { throw e; }
|
|
|
|
|
|
|
|
abort("Can't load module \"" + id + "\".");
|
|
|
|
}
|
Fix JSHint errors in bin/pegjs
Fixes the following JSHint errors:
bin/pegjs: line 66, col 14, 'extraOptions' used out of scope.
bin/pegjs: line 70, col 19, 'extraOptions' used out of scope.
bin/pegjs: line 71, col 20, 'extraOptions' used out of scope.
bin/pegjs: line 80, col 10, Wrap the /regexp/ literal in parens to disambiguate the slash operator.
bin/pegjs: line 128, col 43, Missing semicolon.
bin/pegjs: line 128, col 45, Don't make functions within a loop.
bin/pegjs: line 150, col 13, Redefinition of 'module'.
bin/pegjs: line 217, col 34, Expected '===' and instead saw '=='.
bin/pegjs: line 243, col 44, 'source' used out of scope.
bin/pegjs: line 243, col 61, 'source' used out of scope.
11 years ago
|
|
|
options.plugins.push(mod);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "--trace":
|
|
|
|
options.trace = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "-v":
|
|
|
|
case "--version":
|
|
|
|
printVersion();
|
|
|
|
exitSuccess();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "--":
|
|
|
|
nextArg();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
abort("Unknown option: " + args[0] + ".");
|
|
|
|
}
|
|
|
|
nextArg();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(options.dependencies).length > 0) {
|
|
|
|
if (options.format !== "amd" && options.format !== "commonjs" && options.format !== "umd") {
|
|
|
|
abort("Can't use the -d/--dependency option with the \"" + options.format + "\" module format.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.exportVar !== null) {
|
|
|
|
if (options.format !== "globals" && options.format !== "umd") {
|
|
|
|
abort("Can't use the -e/--export-var option with the \"" + options.format + "\" module format.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var inputStream;
|
|
|
|
var outputStream;
|
|
|
|
|
|
|
|
switch (args.length) {
|
|
|
|
case 0:
|
|
|
|
inputFile = "-";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
inputFile = args[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
abort("Too many arguments.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (outputFile === null) {
|
|
|
|
if (inputFile === "-") {
|
|
|
|
outputFile = "-";
|
|
|
|
} else {
|
|
|
|
outputFile = inputFile.substr(0, inputFile.length - path.extname(inputFile).length) + ".js";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inputFile === "-") {
|
|
|
|
process.stdin.resume();
|
|
|
|
inputStream = process.stdin;
|
|
|
|
inputStream.on("error", function() {
|
|
|
|
abort("Can't read from file \"" + inputFile + "\".");
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
inputStream = fs.createReadStream(inputFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (outputFile === "-") {
|
|
|
|
outputStream = process.stdout;
|
|
|
|
} else {
|
|
|
|
outputStream = fs.createWriteStream(outputFile);
|
|
|
|
outputStream.on("error", function() {
|
|
|
|
abort("Can't write to file \"" + outputFile + "\".");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
readStream(inputStream, function(input) {
|
Fix JSHint errors in bin/pegjs
Fixes the following JSHint errors:
bin/pegjs: line 66, col 14, 'extraOptions' used out of scope.
bin/pegjs: line 70, col 19, 'extraOptions' used out of scope.
bin/pegjs: line 71, col 20, 'extraOptions' used out of scope.
bin/pegjs: line 80, col 10, Wrap the /regexp/ literal in parens to disambiguate the slash operator.
bin/pegjs: line 128, col 43, Missing semicolon.
bin/pegjs: line 128, col 45, Don't make functions within a loop.
bin/pegjs: line 150, col 13, Redefinition of 'module'.
bin/pegjs: line 217, col 34, Expected '===' and instead saw '=='.
bin/pegjs: line 243, col 44, 'source' used out of scope.
bin/pegjs: line 243, col 61, 'source' used out of scope.
11 years ago
|
|
|
var source;
|
|
|
|
|
|
|
|
try {
|
|
|
|
source = peg.generate(input, options);
|
|
|
|
} catch (e) {
|
|
|
|
if (e.location !== undefined) {
|
|
|
|
abort(e.location.start.line + ":" + e.location.start.column + ": " + e.message);
|
|
|
|
} else {
|
|
|
|
abort(e.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
outputStream.write(source);
|
|
|
|
if (outputStream !== process.stdout) {
|
|
|
|
outputStream.end();
|
|
|
|
}
|
|
|
|
});
|