-
Notifications
You must be signed in to change notification settings - Fork 452
Commit
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
There are no files selected for viewing
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.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
matthewkastor
Author
Contributor
|
||
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.
Sorry, something went wrong.
geagle9
|
||
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); | ||
|
@@ -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(); |
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.