Source: EBNFP/EBNFP.js

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