"use strict"; /** @fileOverview Implements `class page.EBNFP extends page.EBNF`. * @author Axel T. Schreiner <ats@cs.rit.edu> * @version 1.5.1 */ /** Creates a new grammar representation. * @class Represents a grammar with precedences as a tree; supports choices and iterations. * @extends page.EBNF * * @param {boolean} [_recover] if `true` add `error` rules to BNF; * this is implied by `%right error` in a grammar. * * @property {page.BNFP} bnf grammar represented in BNF with precedences, terminals are shared. * @property {page.BNFP.Precedence[]} levels list of lists of terminals * with equal precedence and associativity in order of increasing precedence, shared with `.bnf`. * @property {boolean} recover if `true` add `error` rules to BNF. * * @see page.BNFP */ page.EBNFP = function (_recover) { page.EBNF.call(this); this.recover = _recover ? true : false; // create bnf object here this.bnf = new page.BNFP(); // share levels this.levels = this.bnf.levels; // share terminals here (does include $error and $eof) this.bnf.lits = this.lits; this.bnf.litsByValue = this.litsByValue; this.bnf.tokens = this.tokens; this.bnf.tokensByName = this.tokensByName; }; page.subclass(page.EBNFP, 'page.EBNFP', page.EBNF); /** Factory method: Represent a grammar described in EBNFP. * * This is bootstrapped using {@link page.BNFP.from} and {@link page.EBNFP.grammar} * to create an SLR(1) compiler for EBNFP which uses {@link page.EBNFP.builder}. * @static * * @param {string} _grammar description in EBNFP; * comments extend from `#` to the end of a line. * @param {boolean} [_recover] if `true` add `error` rules to BNF; * this is implied by `%right error` in a grammar. * * @returns {page.EBNFP} a new grammar representation; check `.errors`. */ page.EBNFP.from = function (_grammar, _recover) { page.assert('__WHERE__', typeof _grammar == 'string'); // create a compiler which recognizes EBNFP var ebnfpCompiler = new page.SLR1(page.BNFP.from(page.EBNFP.grammar)); page.assert('__WHERE__', ebnfpCompiler.sr == 0 && ebnfpCompiler.rr == 0 && ebnfpCompiler.errors == 0); // build the new grammar var ebnfp = new page.EBNFP(_recover); try { ebnfpCompiler.parse(ebnfpCompiler.tokenizer(/\s+|#.*/)(_grammar).concat([null]), ebnfpCompiler.reflect(ebnfp, page.EBNFP.builder)); } catch (e) { page.assert('__WHERE__', e instanceof Error); ebnfpCompiler.error(e); } ebnfp.errors += ebnfpCompiler.errors; return ebnfp; }; /** Nodes in an EBNFP grammar. * @typedef epNode * @type page.BNF.Lit|page.BNF.Token|page.EBNF.Alt|page.EBNFP.Lst|page.EBNF.NT|page.EBNFP.Rep|page.EBNFP.Seq */ /** Asserts that a node has a type suitable for EBNF. * @private * @override * @variation EBNFP * * @param {string} _marker to identify position across a re-throw. * @param {epNode} _node to test. * * @throws {Error} if type is not suitable. * @see page.assert */ page.EBNFP.prototype.eNode = function (_marker, _node) { this.assert(_marker, _node instanceof page.EBNF.Alt || _node instanceof page.EBNFP.Lst || _node instanceof page.EBNF.NT || _node instanceof page.EBNFP.Rep || _node instanceof page.EBNFP.Seq || _node instanceof page.BNF.Lit || _node instanceof page.BNF.Token); }; { /** The left-recursive BNFP SLR(1) grammar for EBNFP. * * `error` causes this grammar to have two shift-reduce conflicts * which can be hidden through precedence in BNFP but not EBNFP. * @static */ page.EBNFP.grammar = " %token Error 'error'; \n" + " %token Name '[a-zA-Z][-a-zA-Z0-9_]*'; \n" + " %token Literal '\\'(?:[^\\'\\n\\\\\\\\]|\\\\\\\\[\\'n\\\\\\\\])+\\''; \n" + " %right error; \n" + " grammar: token-s level-s rule-s; \n" + " token-s: ; \n" + " token-s: token-s token; \n" + " token-s: token-s error; \n" + " token: '%token' Name Literal ';'; \n" + " level-s: %prec error; \n" + " level-s: level-s left; \n" + " level-s: level-s right; \n" + " level-s: level-s nonassoc; \n" + " level-s: level-s error; \n" + " left: '%left' lit-or-tokens ';'; \n" + " right: '%right' lit-or-tokens ';'; \n" + " nonassoc: '%nonassoc' lit-or-tokens ';'; \n" + " lit-or-tokens: lit-or-token; \n" + " lit-or-tokens: lit-or-tokens lit-or-token; \n" + " lit-or-tokens: lit-or-tokens error; \n" + " lit-or-token: lit; \n" + " lit-or-token: tok; \n" + " lit-or-token: err; \n" + " lit: Literal; \n" + " tok: Name; \n" + " err: Error; \n" + " rule-s: rule; \n" + " rule-s: rule-s rule; \n" + " rule-s: rule-s error; \n" + " rule: nt ':' alt ';'; \n" + " nt: Name; \n" + " alt: seq more-alt; \n" + " more-alt: ; \n" + " more-alt: more-alt '|' seq; \n" + " more-alt: more-alt error seq; \n" + " more-alt: more-alt '|' error; \n" + " more-alt: more-alt error; \n" + " seq: item-s %prec error; \n" + " seq: item-s precedence; \n" + " item-s: item; \n" + " item-s: err; \n" + " item-s: item-s item; \n" + " item-s: item-s err; \n" + " item-s: item-s error; \n" + " item: term-; \n" + " item: term- opt; \n" + " item: term- some; \n" + " item: term- many; \n" + " opt: '?'; \n" + " some: '+'; \n" + " some: '+' '/' term-; \n" + " many: '*'; \n" + " many: '*' '/' term-; \n" + " term-: token_or_nt; \n" + " term-: lit; \n" + " term-: '(' alt ')'; \n" + " token_or_nt: Name; \n" + " precedence: '%prec' lit-or-token; \n"; } // /** The EBNF SLR(1) grammar for EBNFP. // * @static // */ // page.EBNFP.grammar = // " %token Error 'error'; \n" + // " %token Name '[a-zA-Z][-a-zA-Z0-9_]*'; \n" + // " %token Literal '\\'(?:[^\\'\\n\\\\\\\\]|\\\\\\\\[\\'n\\\\\\\\])+\\''; \n" + // // " grammar: token* level* rule+; \n" + // // " token: '%token' Name Literal ';'; \n" + // // " level: (left | right | nonassoc); \n" + // " left: '%left' (lit | tok)+ ';'; \n" + // " right: '%right' (lit | tok)+ ';'; \n" + // " nonassoc: '%nonassoc' (lit | tok)+ ';'; \n" + // " lit: Literal; \n" + // " tok: Name; \n" + // // " rule: nt ':' alt ';'; \n" + // " nt: Name; \n" + // " alt: seq +/ '|'; \n" + // " seq: item+ precedence?; \n" + // " item: term ( opt | some | many )? | err; \n" + // " precedence: '%prec' (lit | tok); \n" + // " term: token_or_nt | lit | '(' alt ')'; \n" + // " opt: '?'; \n" + // " some: '+' ( '/' term )?; \n" + // " many: '*' ( '/' term )?; \n" + // " err: Error; \n" + // " token_or_nt: Name; \n"; // } /** An object with action functions corresponding to * {@link page.EBNFP.grammar} for {@link page.SLR1#reflect} * which require a new {@link page.EBNFP} as environment to represent * a grammar in EBNF notation with precedences and `error` using this environment. * @static */ page.EBNFP.builder = { // grammar: token* level* rule+ // token: '%token' Name Literal ';' // rule: nt ':' alt ';' // nt: Name // alt: seq +/ '|' // item: term ( opt | some | many )? | err // term: token_or_nt | lit | '(' alt ')' // opt: '?' // some: '+' ( '/' term )? // many: '*' ( '/' term )? // token_or_nt: Name '{}': page.EBNF.builder, // inherited // level: (left | right | nonassoc) left: // '%left' (lit | tok)+ ';' page.BNFP.builder.left, right: // '%right' (lit | tok)+ ';' page.BNFP.builder.right, nonassoc: // '%nonassoc' (lit | tok)+ ';' page.BNFP.builder.nonassoc, // lit: Literal seq: // item+ precedence? function (_values, _ebnfp) { return typeof _values[_values.length - 1] == 'function' ? _ebnfp.seq(_values, _values.pop()()) : _ebnfp.seq(_values); }, precedence: // '%prec' (lit | tok) function (_values, _ebnfp) { return function () { return _values[0] }; }, tok: // Name page.BNFP.builder.tok, err: // Error page.BNFP.builder.err, }; /** Displays description of grammar and number of errors if any. * @function * @override * @variation EBNFP * @returns {string} */ page.EBNFP.prototype.toString = page.BNFP.prototype.toString; /** Displays a grammar and all non-terminals with name and contents of all sets. * @function * @override * @variation EBNFP * @returns {string} */ page.EBNFP.prototype.dump = page.BNFP.prototype.dump; /** 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.EBNFP}`.levels`, * adds `.prec.level` and `.prec.assoc` to all terminals in the list, * and checks for duplicates. * @function * * @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.EBNFP.prototype.precedence = page.BNFP.prototype.precedence; /** Factory method to create a new representation of a delimited iteration for EBNFP; * represents 1 or more occurrences. * @override * @variation EBNFP * * @param {epNode} _node body. * @param {epNode} _delim delimiter. * * @returns {page.EBNFP.Lst} a new representation of a delimited iteration for EBNFP. */ page.EBNFP.prototype.someList = function (_node, _delim) { this.eNode('__WHERE__', _node); this.eNode('__WHERE__', _delim); return new page.EBNFP.Lst(_node, _delim, 1); }; /** Factory method to create a new representation of a delimited iteration for EBNFP; * represents 0 or more occurrences. * @override * @variation EBNFP * * @param {epNode} _node body. * @param {epNode} _delim delimiter. * * @returns {page.EBNFP.Lst} a new representation of a delimited iteration for EBNFP. */ page.EBNFP.prototype.manyList = function (_node, _delim) { this.eNode('__WHERE__', _node); this.eNode('__WHERE__', _delim); return new page.EBNFP.Lst(_node, _delim, 0); }; /** Factory method to create a new representation of an iteration for EBNFP; * represents 0 to 1 occurence. * @override * @variation EBNFP * * @param {epNode} _node body. * * @returns {page.EBNFP.Rep} a new representation of an iteration for EBNFP. */ page.EBNFP.prototype.opt = function (_node) { this.eNode('__WHERE__', _node); return new page.EBNFP.Rep(_node, 0, 1); }; /** Factory method to create a new representation of an iteration for EBNFP; * represents 1 or more occurrences. * @override * @variation EBNFP * * @param {epNode} _node body. * * @returns {page.EBNFP.Rep} a new representation of an iteration for EBNFP. */ page.EBNFP.prototype.some = function (_node) { this.eNode('__WHERE__', _node); return new page.EBNFP.Rep(_node, 1, undefined); }; /** Factory method to create a new representation of an iteration for EBNFP; * represents 0 or more occurrences. * @override * @variation EBNFP * * @param {epNode} _node body. * * @returns {page.EBNFP.Rep} a new representation of an iteration for EBNFP. */ page.EBNFP.prototype.many = function (_node) { this.eNode('__WHERE__', _node); return new page.EBNFP.Rep(_node, 0, undefined); }; /** Factory method to create a new representation of a sequence with optional precedence for EBNFP. * @override * @variation EBNFP * * @param {epNode[]} _nodes sequence, not empty. * @param {page.BNF.T} [_prec] terminal defining precedence explicitly; * by default last terminal in sequence, if any. * * @returns {epNode} a new representation of a sequence for EBNFP; * flattened if there is a single descendant and no precedence. * @throws {string} an error message if an explicit precedence cannot be resolved. */ page.EBNFP.prototype.seq = function (_nodes, _prec) { var self = this; self.assert('__WHERE__', _nodes instanceof Array && _nodes.length > 0); _nodes.forEach(function (node) { self.eNode('__WHERE__', node); }); if (_prec) { self.assert('__WHERE__', _prec instanceof page.BNF.T); if (!('prec' in _prec)) throw _prec + ' has no precedence level'; } return _nodes.length == 1 && !_prec ? _nodes[0] : new page.EBNFP.Seq(_nodes, _prec); };