Source: BNFP/BNFP.js

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