"use strict"; /** @fileOverview Implements `class page.BNFP`. * @author Axel T. Schreiner <ats@cs.rit.edu> * @version 1.5.1 */ /** Creates a new grammar representation. * @class Represents a grammar with precedences as ordered pairs. * @extends page.BNF * * @property {page.BNFP.Precedence[]} levels list of lists of terminals * with equal precedence and associativity in order of increasing precedence. * This list needs to be complete before the first call to {@link page.BNFP#rule} * with an explicit precedence. */ page.BNFP = function () { page.BNF.call(this); this.levels = []; }; page.subclass(page.BNFP, 'page.BNFP', page.BNF); /** Factory method: Represent a grammar described in BNFP. * * This is bootstrapped using {@link page.BNF.from} and {@link page.BNFP.grammar} * to create an SLR(1) compiler for BNFP which uses {@link page.BNFP.builder}. * @static * * @param {string} _grammar description in BNFP; * comments extend from `#` to the end of a line. * * @returns {page.BNFP} a new grammar representation; check `.errors`. */ page.BNFP.from = function (_grammar) { page.assert('__WHERE__', typeof _grammar == 'string'); // create a compiler which recognizes BNFP var bnfpCompiler = new page.SLR1(page.BNF.from(page.BNFP.grammar)); page.assert('__WHERE__', bnfpCompiler.sr == 0 && bnfpCompiler.rr == 0 && bnfpCompiler.errors == 0); // build the new grammar var bnfp = new page.BNFP(); try { bnfpCompiler.parse(bnfpCompiler.tokenizer(/\s+|#.*/)(_grammar).concat([null]), bnfpCompiler.reflect(bnfp, page.BNFP.builder)); } catch (e) { page.assert('__WHERE__', e instanceof Error); bnfpCompiler.error(e); } bnfp.errors += bnfpCompiler.errors; return bnfp; }; { /** The left-recursive BNF SLR(1) grammar for BNFP. * @static */ page.BNFP.grammar = " %token Error 'error'; \n" + " %token Name '[a-zA-Z][-a-zA-Z0-9_]*'; \n" + " %token Literal '\\'(?:[^\\'\\n\\\\\\\\]|\\\\\\\\[\\'n\\\\\\\\])+\\''; \n" + " grammar: tokens levels rules; \n" + " tokens: ; \n" + " tokens: tokens token; \n" + " token: '%token' Name Literal ';'; \n" + " levels: ; \n" + " levels: levels left; \n" + " levels: levels right; \n" + " levels: levels nonassoc; \n" + " left: '%left' lit_or_tokens ';'; \n" + " right: '%right' lit_or_tokens ';'; \n" + " nonassoc: '%nonassoc' lit_or_tokens ';'; \n" + " lit_or_tokens: lit; \n" + " lit_or_tokens: tok; \n" + " lit_or_tokens: err; \n" + " lit_or_tokens: lit_or_tokens lit; \n" + " lit_or_tokens: lit_or_tokens tok; \n" + " lit_or_tokens: lit_or_tokens err; \n" + " lit: Literal; \n" + " tok: Name; \n" + " err: Error; \n" + " rules: rule; \n" + " rules: rules rule; \n" + " rule: nt ':' rhs precedence ';'; \n" + " nt: Name; \n" + " rhs: ; \n" + " rhs: rhs token_or_nt; \n" + " rhs: rhs lit; \n" + " rhs: rhs err; \n" + " token_or_nt: Name; \n" + " precedence: ; \n" + " precedence: '%prec' lit; \n" + " precedence: '%prec' tok; \n" + " precedence: '%prec' err; \n"; } /** An object with action functions corresponding to * {@link page.BNFP.grammar} for {@link page.SLR1#reflect} * which require a new {@link page.BNFP} as environment to represent * a grammar in BNF notation with precedences and `error` using this environment. * @static */ page.BNFP.builder = { // grammar: tokens levels rules // tokens: // tokens: token tokens // token: '%token' Name Literal ';' // lit: Literal // rules: rule // rules: rules rule // nt: Name // nt: Name; // rhs: ; // rhs: rhs token_or_nt; // rhs: rhs lit; // rhs: rhs err; // token_or_nt: Name; '{}': page.BNF.builder, // inherited // levels: // levels: levels left // levels: levels right // levels: levels nonassoc left: // '%left' lit_or_tokens ';' function (_values, _bnfp, _tuple) { try { _bnfp.precedence('left', _values); } catch (e) { _bnfp.error('('+_tuple.lineno+')', e); } return []; }, right: // '%right' lit_or_tokens ';' function (_values, _bnfp, _tuple) { try { _bnfp.precedence('right', _values); } catch (e) { _bnfp.error('('+_tuple.lineno+')', e); } return []; }, nonassoc: // '%nonassoc' lit_or_tokens ';' function (_values, _bnfp, _tuple) { try { _bnfp.precedence('nonassoc', _values); } catch (e) { _bnfp.error('('+_tuple.lineno+')', e); } return []; }, // lit_or_tokens: lit // lit_or_tokens: tok // lit_or_tokens: lit_or_tokens lit // lit_or_tokens: lit_or_tokens tok tok: // Name function (_values, _bnfp, _tuple) { if (_values[0] in _bnfp.tokensByName) return _bnfp.token(_values[0]); _bnfp.error('('+_tuple.lineno+')', _values[0], _values[0] in _bnfp.ntsByName ? 'is a non-terminal' : 'is undefined'); return []; }, rule: // nt ':' rhs precedence ';' function (_values, _bnfp, _tuple) { var precedence = _values.pop(); try { return _bnfp.rule(_values[0], _values.slice(1), precedence); } catch (e) { _bnfp.error('('+_tuple.lineno+')', e); return _bnfp.rule(_values[0], _values.slice(1)); } }, err: // Error function (_values, _bnfp) { return _bnfp.token(); }, precedence: // // '%prec' lit // '%prec' tok function (_values, _bnfp) { return _values.length > 0 ? _values[0] : false; } }; /** Displays description of grammar and number of errors if any. * @override * @variation BNFP * @returns {string} */ page.BNFP.prototype.toString = function () { var result = this.levels.join('\n'); if (result.length) result += '\n\n'; return result + page.BNF.prototype.toString.call(this); }; /** Displays a grammar and all non-terminals with name and contents of all sets. * @override * @variation BNFP * @returns {string} */ page.BNFP.prototype.dump = function () { var result = this.levels.join('\n'); if (result.length) result += '\n\n'; return result + page.BNF.prototype.dump.call(this); }; /** Factory method to represent a list of terminals with equal precedence level and equal associativity. * * Creates a new {@link page.BNFP.Precedence} object, * adds it to {@link page.BNFP}`.levels`, * adds `.prec.level` and `.prec.assoc` to all terminals in the list, * and checks for duplicates. * * @param {string} _assoc associativity: `'left'`, `'right'`, or `'nonassoc'`. * @param {page.BNF.T[]} _terminals list of terminals. * * @returns {page.BNFP.Precedence} representing the set. * @throws {string} an error message if there is duplication. */ page.BNFP.prototype.precedence = function (_assoc, _terminals) { var self = this; self.assert('__WHERE__', _assoc == 'left' || _assoc == 'right' || _assoc == 'nonassoc'); self.assert('__WHERE__', _terminals instanceof Array); _terminals.forEach(function (t) { self.assert('__WHERE__', t instanceof page.BNF.T); }); _terminals.forEach(function (t, n) { if ('prec' in t) throw t + ' is a duplicate precedence definition'; t.prec = { level: self.levels.length, assoc: _assoc }; }); var result = new page.BNFP.Precedence(_assoc, _terminals); self.levels.push(result); return result; }; /** Factory method to create a rule representation for BNFP. * Adds to rule's non-terminal's {@link page.BNF.NT}`.rules`. * @override * @variation BNFP * * @param {page.BNF.NT} _nt left-hand side, non-terminal. * @param {Node[]} [_symbols] right-hand side, may be empty array; * if omitted, creates empty rule 0 for `$accept`. * @param {page.BNF.T} [_prec] terminal defining precedence explicitly; * by default last terminal in right-hand side, if any. * {@link page.BNFP}`.levels` needs to be complete before * this parameter is specified. * * @returns {page.BNFP.Rule} a new rule representation. * @throws {string} an error message if an explicit precedence cannot be resolved. */ page.BNFP.prototype.rule = function (_nt, _symbols, _prec) { var self = this; if (arguments.length == 1) { self.assert('__WHERE__', self.rules.length == 0 && _nt == self.nt()); _symbols = []; // filled in during init } else { self.assert('__WHERE__', _nt instanceof page.BNF.NT); self.assert('__WHERE__', _symbols instanceof Array); _symbols.forEach(function (symbol) { self.assert('__WHERE__', symbol instanceof page.BNF.NT || symbol instanceof page.BNF.Lit || symbol instanceof page.BNF.Token); }); } // convert precedence terminal to { level:, assoc:, t: } // returns false if not found function level (t) { return 'prec' in t ? { level: t.prec.level, assoc: t.prec.assoc, t: t } : false; } // compute precedence if any var p = false; if (_prec) { self.assert('__WHERE__', _prec instanceof page.BNF.T); if (!(p = level(_prec))) throw _prec + ' has no precedence level'; else p.explicit = true; } else for (var s = _symbols.length; s-- > 0; ) if (_symbols[s] instanceof page.BNF.T) { p = level(_symbols[s]); break; } var result = new page.BNFP.Rule(_nt, _symbols, p); // add new rule to rule's nt's rules result.nt.rules.push(result); return result; };