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