diff --git a/lib/jison.js b/lib/jison.js index b1d68732d..afd142359 100755 --- a/lib/jison.js +++ b/lib/jison.js @@ -88,6 +88,7 @@ var Production = typal.construct({ this.id = id; this.first = []; this.precedence = 0; + this.reachable = false; }, toString: function Production_toString() { var str = this.symbol; @@ -100,6 +101,9 @@ var Production = typal.construct({ if (this.precedence) { attr_str.push('@' + this.precedence); } + if (!this.reachable) { + attr_str.push('*RIP*'); + } if (attr_str.length) { str += '[' + attr_str.join(' ') + ']'; @@ -266,6 +270,9 @@ generator.processGrammar = function processGrammarDef(grammar) { // augment the grammar this.augmentGrammar(grammar); + + // detect unused productions and flag/report them + this.signalUnusedProductions(); }; generator.augmentGrammar = function augmentGrammar(grammar) { @@ -320,6 +327,80 @@ generator.augmentGrammar = function augmentGrammar(grammar) { this.grammar = grammar; }; +// Mark & report unused productions +generator.signalUnusedProductions = function () { + var mark = {}; + + var productions = this.productions; + var nonterminals = this.nonterminals; + var i, p, len, nt, sym; + + for (i = 0, len = nonterminals.length; i < len; i++) { + nt = nonterminals[i]; + assert(nt.symbol); + mark[nt.symbol] = false; + } + + // scan & mark all visited productions + function traverseGrammar(nt) { + assert(nt); + assert(nt.symbol); + mark[nt.symbol] = true; + + var prods = nt.productions; + assert(prods); + prods.forEach(function (p) { + assert(p.symbol === nt.symbol); + assert(p.handle); + var rhs = p.handle; + console.log('traverse / mark: ', nt.symbol, ' --> ', rhs); + + for (var j = 0, len = rhs.length; j < len; j++) { + var sym = rhs[j]; + assert(!sym ? !nonterminals[sym] : true); + if (nonterminals[sym] && !mark[sym]) { + traverseGrammar(nonterminals[sym]); + } + } + }); + } + + traverseGrammar(nonterminals['$accept' /* this.startSymbol */ ]); + + // now any production which is not yet marked is *unused*: + var unused_prods = []; + for (var sym in mark) { + nt = nonterminals[sym]; + assert(nt); + var prods = nt.productions; + assert(prods); + var in_use = mark[sym]; + prods.forEach(function (p) { + assert(p); + if (in_use) { + p.reachable = true; + } else { + p.reachable = false; + unused_prods.push(p.toString()); + } + }); + + if (!in_use) { + // and kill the unused nonterminals: + delete this.nonterminals[sym]; + } + } + if (unused_prods.length) { + console.warn('\nUnused productions in your grammar:\n ' + unused_prods.join('\n ') + '\n\n'); + } + + // and kill the unused productions: + this.productions = productions.filter(function (p) { + if (!p.reachable) console.warn('KILL PRODUCTION: ' + p); + return p.reachable; + }); +}; + // set precedence and associativity of operators function processOperators(ops) { if (!ops) return {};