Skip to content

Commit

Permalink
cli can be called from script
Browse files Browse the repository at this point in the history
I keep finding myself reimplementing the cli script and it would be much
easier if I could simply require it and use it in scripts. The basic
change is that main accepts an options argument. The rest of the
changes: add documentation to the source code, expose a method for
processing grammar files in any combination the cli accepts, and expose
a method for generating a parser as a string. It's all pretty much the
same code as was there, just shifted around so I could expose things to
users and wrapped up so things don't go haywire.
  • Loading branch information
matthewkastor committed Sep 14, 2013
1 parent 74b9a34 commit cf9346c
Showing 1 changed file with 269 additions and 98 deletions.
367 changes: 269 additions & 98 deletions lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,207 @@
#!/usr/bin/env node
/*jslint
white: true,
vars: true,
stupid: true,
node: true
*/

var jison = require('./jison.js');
var nomnom = require('nomnom');
var fs = require('fs');
var path = require('path');
var ebnfParser = require('ebnf-parser');
var lexParser = require('lex-parser');
var cjson = require('cjson');
/**
* jison's cli
* @fileoverview The cli for jison
*/

var version = require('../package.json').version;

var opts = require("nomnom")
.script('jison')
.option('file', {
flag: true,
position: 0,
help: 'file containing a grammar'
})
.option('lexfile', {
flag: true,
position: 1,
help: 'file containing a lexical grammar'
})
.option('json', {
abbr: 'j',
flag: true,
help: 'force jison to expect a grammar in JSON format'
})
.option('outfile', {
abbr: 'o',
metavar: 'FILE',
help: 'Filename and base module name of the generated parser'
})
.option('debug', {
abbr: 't',
flag: true,
default: false,
help: 'Debug mode'
})
.option('module-type', {
abbr: 'm',
default: 'commonjs',
metavar: 'TYPE',
help: 'The type of module to generate (commonjs, amd, js)'
})
.option('parser-type', {
abbr: 'p',
default: 'lalr',
metavar: 'TYPE',
help: 'The type of algorithm to use for the parser (lr0, slr, lalr, lr)'
})
.option('version', {
abbr: 'V',
flag: true,
help: 'print version and exit',
callback: function() {
return version;
/**
* Container for cli related functions.
* @namespace Container for cli related functions.
*/
var cli = exports;
/**
* Generates a parser and writes it to a file.
* @param {Object} opts Options object.
*
* Options:
* file: {String} Optional. Path to a file containing a grammar. If no file is
* specified input will be read from stdin.
*
* lexfile: {String} Optional. Path to a file containing a lexical grammar.
*
* json: {Boolean} Optional. Set to true if `file` is in json format.
*
* outfile: {String} Optional. The path and filename where the parser should be
* written to. Defaults to the path and filename given for `file` with the file
* extension replaced by `js`.
*
* debug: {Boolean} Optional. Debug mode. Defaults to false.
*
* module-type: {String} Optional. The module type of the generated parser.
* Options are: commonjs, amd, and js. Defaults to commonjs.
*
* parser-type: {String} Optional. The type of parser to generate. Options are:
* lr0, slr, lalr, and lr. Defaults to lalr.
*
* @example
* // grammar to process is not json and contains grammars for lexer and
* // parser.
* var jisonCli = require('./node_modules/jison/lib/cli.js');
* var options = {
* file : "myfile.jison",
* moduleName : "myModule"
* };
* jisonCli.main(options);
* @example
* // grammar to process is not json and is divided into two files containing
* // the grammars for the lexer and parser seperately.
* var jisonCli = require('./node_modules/jison/lib/cli.js');
* var options = {
* file : "myfile.y",
* lexfile : "myfile.l",
* moduleName : "myModule"
* };
* jisonCli.main(options);
* @example
* // grammar to process is in json format, desired module type is amd, and
* // desired parser type is lr0.
* var jisonCli = require('./node_modules/jison/lib/cli.js');
* var options = {
* file : "myfile.json",
* moduleName : "myModule",
* json : true,
* "module-type" : "amd",
* "parser-type" : "lr0"
* };
* jisonCli.main(options);
*/
cli.main = function cli_main(opts) {
"use strict";
opts = opts || {};
/**
* Generates a parser as a string.
* @private
*/
function processGrammar(raw, lex, opts) {
var grammar,
parser;
if (!opts.json) {

This comment has been minimized.

Copy link
@syrnick

syrnick Sep 9, 2014

Contributor

looks like a bug. if opts.json is set, grammar is undefined. I couldn't generate the parser using cli and snippets from github manual.

This comment has been minimized.

Copy link
@matthewkastor

matthewkastor Oct 27, 2014

Author Contributor

Hmmm... weird. I see the bug, "if opts.json isn't defined then set the grammar to the result of processing the grammar with undefined as the third parameter"... I really don't remember anything about this though. I thought jison had addressed this and made my branch obsolete anyway. I'll look into it if you still can't call it from scripts, but I'm pretty sure you can.

grammar = cli.processGrammars(raw, lex, opts.json);
}
parser = cli.generateParserString(opts, grammar);
return parser;
}
})
.parse();

/**
* Processes input from a file.
* @private
* @requires <a href="http://nodejs.org/api/fs.html">fs</a>
* @requires <a href="http://nodejs.org/api/path.html">path</a>
*/
function processInputFile () {
var fs = require('fs');
var path = require('path');

exports.main = function () {
if (opts.file) {
var raw = fs.readFileSync(path.normalize(opts.file), 'utf8');
var jsonMode = path.extname(opts.file) === '.json' || opts.json;
var name = path.basename((opts.outfile||opts.file)).replace(/\..*$/g,'');
// getting raw files
var lex;

if (opts.lexfile) {
lex = fs.readFileSync(path.normalize(opts.lexfile), 'utf8');
}
var raw = fs.readFileSync(path.normalize(opts.file), 'utf8');

fs.writeFileSync(opts.outfile||(name + '.js'), processGrammar(raw, lex, name, jsonMode));
} else {
// making best guess at json mode
opts.json = path.extname(opts.file) === '.json' || opts.json;

// setting output file name and module name based on input file name
// if they aren't specified.
var name = path.basename((opts.outfile || opts.file));
/*jslint regexp: true */
name = name.replace(/\..*$/g, '');
/*jslint regexp: false */
opts.outfile = opts.outfile || (name + '.js');
if (!opts.moduleName && name) {
opts.moduleName = name.replace(/-\w/g,
function (match) {
return match.charAt(1).toUpperCase();
});
}

var parser = processGrammar(raw, lex, opts);
fs.writeFileSync(opts.outfile, parser);
}
/**
* Reads from stdin and calls the callback on the data received.
* @param {Function} cb The callback function to execute on the received
* data.
* @private
*/
function readin(cb) {
var stdin = process.openStdin(),
data = '';

stdin.setEncoding('utf8');
stdin.addListener('data', function (chunk) {
data += chunk;
});
stdin.addListener('end', function () {
cb(data);
});
}
/**
* Processes input from stdin.
* @private
*/
function processStdin () {
readin(function (raw) {
console.log(processGrammar(raw, null, null, opts.json));
console.log(processGrammar(raw, null, opts));
});
}

// if an input file wasn't given, assume input on stdin
if (opts.file) {
processInputFile();
} else {
processStdin();
}
};
/**
* Generates a parser and returns it as a string.
* @param {Object} opts Optional. An options object. Options are parser-type,
* module-type, and debug. Defaults to {}; see the description and examples for
* these optons in {@link Jison.cli.main}
* @param {String|Object} grammar The grammar to generate a parser from.
* @returns {String} Returns the generated parser as a string.
* @requires <a href="https://npmjs.org/package/jison/">jison</a>
*/
cli.generateParserString = function generateParserString(opts, grammar) {
"use strict";
opts = opts || {};
var jison = require('./jison.js');

var settings = grammar.options || {};

if (opts['parser-type']) {
settings.type = opts['parser-type'];
}
settings.debug = opts.debug;
if (!settings.moduleType) {
settings.moduleType = opts['module-type'];
}

var generator = new jison.Generator(grammar, settings);

This comment has been minimized.

Copy link
@geagle9

geagle9 Mar 26, 2015

The other settings on opts are lost here, such as moduleName
So, when I run the command with "-o abced.js", the generated file still use "parser" as the module name.

This comment has been minimized.

Copy link
@millerdev

millerdev Jul 8, 2015

This should be fixed by #290

return generator.generate(settings);
};
/**
* Processes grammar files of various format.
* @param {String} file Contents of a jison grammar file.
* @param {String} lexFile Contents of a lexer grammar file.
* @param {Boolean} jsonMode Set to true if `file` is in json format.
* @returns {Object} Returns the parsed grammar object.
* @requires <a href="https://npmjs.org/package/ebnf-parser">ebnf-parser</a>
* @requires <a href="https://npmjs.org/package/cjson">cjson</a>
* @requires <a href="https://npmjs.org/package/lex-parser">lex-parser</a>
*/
cli.processGrammars = function processGrammars(file, lexFile, jsonMode) {
"use strict";
var ebnfParser = require('ebnf-parser');
var cjson = require('cjson');

function processGrammar (file, lexFile, name, jsonMode) {
var grammar;
if (jsonMode) {
grammar = cjson.parse(file);
Expand All @@ -97,37 +218,87 @@ function processGrammar (file, lexFile, name, jsonMode) {
}
}
}

var settings = grammar.options || {};

if (opts['parser-type']) settings.type = opts['parser-type'];
if (lexFile) grammar.lex = lexParser.parse(lexFile);
settings.debug = opts.debug;
if (!settings.moduleType) settings.moduleType = opts['module-type'];
if (!settings.moduleName && name) {
settings.moduleName = name.replace(/-\w/g,
function (match){
return match.charAt(1).toUpperCase();
});
if (lexFile) {
grammar.lex = require('lex-parser').parse(lexFile);
}
return grammar;
};
/**
* Initialization function, grabs commandline arguments and passes them to
* `cli.main` if this script was called from the commandline.
* @private
* @methodOf cli
* @requires <a href="https://npmjs.org/package/nomnom">nomnom</a>
*/
function cli_init () {
"use strict";
/**
* Gets options from the commandline.
* @private
* @requires <a href="https://npmjs.org/package/nomnom">nomnom</a>
*/
function getCommandlineOptions () {
var version = require('../package.json').version;
var opts = require("nomnom")
.script('jison')
.option('file', {
flag : true,
position : 0,
help : 'file containing a grammar'
})
.option('lexfile', {
flag : true,
position : 1,
help : 'file containing a lexical grammar'
})
.option('json', {
abbr : 'j',
flag : true,
help : 'force jison to expect a grammar in JSON format'
})
.option('outfile', {
abbr : 'o',
metavar : 'FILE',
help : 'Filename and base module name of the generated parser'
})
.option('debug', {
abbr : 't',
flag : true,
default:
false,
help : 'Debug mode'
})
.option('module-type', {
abbr : 'm',
default:
'commonjs',
metavar : 'TYPE',
help : 'The type of module to generate (commonjs, amd, js)'
})
.option('parser-type', {
abbr : 'p',
default:
'lalr',
metavar : 'TYPE',
help : 'The type of algorithm to use for the parser (lr0, slr,' +
'lalr, lr)'
})
.option('version', {
abbr : 'V',
flag : true,
help : 'print version and exit',
callback : function () {
return version;
}
}).parse();

return opts;
} // end of getCommandlineOptions

var generator = new jison.Generator(grammar, settings);
return generator.generate(settings);
}

function readin (cb) {
var stdin = process.openStdin(),
data = '';

stdin.setEncoding('utf8');
stdin.addListener('data', function (chunk) {
data += chunk;
});
stdin.addListener('end', function () {
cb(data);
});
if (require.main === module) {
var opts = getCommandlineOptions();
cli.main(opts);
}
}

if (require.main === module)
exports.main();

cli_init();

0 comments on commit cf9346c

Please sign in to comment.