#!/usr/bin/env node

var util = require(“util”); var fs = require(“fs”); var path = require(“path”); var PEG = require(“../lib/peg”);

/* Helpers */

function printVersion() {

util.puts("PEG.js " + PEG.VERSION);

}

function printHelp() {

util.puts("Usage: pegjs [options] [--] [<input_file>] [<output_file>]");
util.puts("");
util.puts("Generates a parser from the PEG grammar specified in the <input_file> and writes");
util.puts("it to the <output_file>.");
util.puts("");
util.puts("If the <output_file> is omitted, its name is generated by changing the");
util.puts("<input_file> extension to \".js\". If both <input_file> and <output_file> are");
util.puts("omitted, standard input and output are used.");
util.puts("");
util.puts("Options:");
util.puts("  -e, --export-var <variable>        name of the variable where the parser");
util.puts("                                     object will be stored (default:");
util.puts("                                     \"module.exports\")");
util.puts("      --cache                        make generated parser cache results");
util.puts("      --allowed-start-rules <rules>  comma-separated list of rules the generated");
util.puts("                                     parser will be allowed to start parsing");
util.puts("                                     from (default: the first rule in the");
util.puts("                                     grammar)");
util.puts("  -o, --optimize <goal>              select optimization for speed or size");
util.puts("                                     (default: speed)");
util.puts("      --plugin <plugin>              use a specified plugin (can be specified");
util.puts("                                     multiple times)");
util.puts("      --extra-options <options>      additional options (in JSON format) to pass");
util.puts("                                     to PEG.buildParser");
util.puts("      --extra-options-file <file>    file with additional options (in JSON");
util.puts("                                     format) to pass to PEG.buildParser");
util.puts("  -v, --version                      print version information and exit");
util.puts("  -h, --help                         print help and exit");

}

function exitSuccess() {

process.exit(0);

}

function exitFailure() {

process.exit(1);

}

function abort(message) {

util.error(message);
exitFailure();

}

function addExtraOptions(options, json) {

var 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.");
}

for (var key in extraOptions) {
  if (extraOptions.hasOwnProperty(key)) {
    options[key] = extraOptions[key];
  }
}

}

/*

* 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 */

/* This makes the generated parser a CommonJS module by default. */ var exportVar = “module.exports”; var options = {

cache:    false,
output:   "source",
optimize: "speed",
plugins:  []

};

while (args.length > 0 && isOption(args)) {

switch (args[0]) {
  case "-e":
  case "--export-var":
    nextArg();
    if (args.length === 0) {
      abort("Missing parameter of the -e/--export-var option.");
    }
    exportVar = args[0];
    break;

  case "--cache":
    options.cache = true;
    break;

  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 "-o":
  case "--optimize":
    nextArg();
    if (args.length === 0) {
      abort("Missing parameter of the -o/--optimize option.");
    }
    if (args[0] !== "speed" && args[0] !== "size") {
      abort("Optimization goal must be either \"speed\" or \"size\".");
    }
    options.optimize = args[0];
    break;

  case "--plugin":
    nextArg();
    if (args.length === 0) {
      abort("Missing parameter of the --plugin option.");
    }
    var id = /^(\.\/|\.\.\/)/.test(args[0]) ? path.resolve(args[0]) : args[0];
    var mod;
    try {
      mod = require(id);
    } catch (e) {
      if (e.code !== "MODULE_NOT_FOUND") { throw e; }

      abort("Can't load module \"" + id + "\".");
    }
    options.plugins.push(mod);
    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 "-v":
  case "--version":
    printVersion();
    exitSuccess();
    break;

  case "-h":
  case "--help":
    printHelp();
    exitSuccess();
    break;

  case "--":
    nextArg();
    break;

  default:
    abort("Unknown option: " + args[0] + ".");
}
nextArg();

}

switch (args.length) {

case 0:
  process.stdin.resume();
  var inputStream = process.stdin;
  var outputStream = process.stdout;
  break;

case 1:
case 2:
  var inputFile = args[0];
  var inputStream = fs.createReadStream(inputFile);
  inputStream.on("error", function() {
    abort("Can't read from file \"" + inputFile + "\".");
  });

  var outputFile = args.length === 1
    ? args[0].replace(/\.[^.]*$/, ".js")
    : args[1];
  var outputStream = fs.createWriteStream(outputFile);
  outputStream.on("error", function() {
    abort("Can't write to file \"" + outputFile + "\".");
  });

  break;

default:
  abort("Too many arguments.");

}

readStream(inputStream, function(input) {

var source;

try {
  source = PEG.buildParser(input, options);
} catch (e) {
  if (e.line !== undefined && e.column !== undefined) {
    abort(e.line + ":" + e.column + ": " + e.message);
  } else {
    abort(e.message);
  }
}

outputStream.write(
  exportVar !== "" ? exportVar + " = " + source + ";\n" : source + "\n"
);
if (outputStream !== process.stdout) {
  outputStream.end();
}

});