"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);
};