Source: EBNF/Rep.js

"use strict";

/** @fileOverview Implements `class page.EBNF.Rep`.
  * @author Axel T. Schreiner <ats@cs.rit.edu>
  * @version 1.5.0
  */

/** Creates a new representation of an iteration for EBNF.
  * @class Represents an iteration for EBNF.
  *
  * @example
  * some: item +;
  * many: item *;
  * optional: item ?;
  *
  * @private
  *
  * @param {eNode} _node body; null if called during unmarshalling.
  * @param {number} _min minimum number of occurences, 0 or 1.
  * @param {number} _max maximum number of occurences, 1 or undefined.
  *
  * @property {eNode} node body.
  * @property {number} min minimum number of occurences, 0 or 1.
  * @property {number} max maximum number of occurences, 1 or undefined.
  * @property {page.BNF.NT} nt corresponding non-terminal for BNF.
  * @property {boolean} empty true if no input can be accepted.
  * @property {Map} first terminals at front, maps ord to {@link page.BNF.T}.
  * @property {Map} follow terminals following, maps ord to {@link page.BNF.T}.
  */
page.EBNF.Rep = function (_node, _min, _max) {
  this.node = _node;
  this.min = _min;
  this.max = _max;
  this.nt = undefined;
  this.empty = undefined;
  this.first = undefined;
  this.follow = undefined;
};

page.baseclass(page.EBNF.Rep, 'page.EBNF.Rep');

/** Displays iteration in EBNF notation.
  * @returns {string}
  */
page.EBNF.Rep.prototype.toString = function () {
  var result;

  if (this.node instanceof page.EBNF.Alt || this.node instanceof page.EBNF.Seq 
      || this.node instanceof page.EBNF.Lst)
    result = '(' + this.node + ')';
  else
    result = this.node.toString();

  if (this.min == 1) result += '+';
  else if (this.max) result += '?';
  else               result += '*';

  return result;
};

/** Called once by {@link page.EBNF#init} to recursively translate into BNF.
  *
  *     a+
  *     a*
  *     a?
  *
  * where `.min` and `.max` are `1:undefined`, `0:undefined`, or `0:1`, respectively, are translated to
  *
  *     $#: a $##;   # .min == 1
  *     $##: ;
  *     $##: a $##;
  *
  *     $#: $##;     # if left-hand side is known
  *     $##: ;       # .min == 0
  *     $##: a $##;
  *
  *     $#: ;        # .min == 0, .max == 1
  *     $#: a;
  *
  * @private
  *
  * @param {page.EBNF} _grammar object to which the construct belongs.
  * @param {page.EBNF.NT} [_nt] left-hand side, if known; avoids the need for a fresh non-terminal.
  *
  * @returns  {page.BNF.NT} non-terminal representing the translated construct.
  */
page.EBNF.Rep.prototype.toBNF = function (_grammar, _nt) {
  _grammar.assert('__WHERE__', !this.nt);

  var nt1, result = this.nt = _nt ? _nt : _grammar.bnf.nt('$');     // $#
  var at = _grammar.bnf.rules.length;                               // placeholder
  if (this.min == 1 || (!this.max && _nt)) _grammar.bnf.rules.push(null);
  _grammar.bnf.rules.push(null, null);
  
  var node = this.node instanceof page.BNF.T ? this.node : this.node.toBNF(_grammar);

  if (this.min == 0 && this.max == 1) {                                     // opt
    _grammar.bnf.rules[at++] = _grammar.bnf.rule(result, [ ]);              // $#:
    _grammar.bnf.rules[at] = _grammar.bnf.rule(result, [ node ]);           // $#: node
  } else {
    if (this.min == 1) {                                                    // some
      nt1 = _grammar.bnf.nt('$');
      _grammar.bnf.rules[at++] = _grammar.bnf.rule(result, [ node, nt1 ]);  // $#: node $##
    
    } else if (_nt) {                                                       // many, explicit nt
      nt1 = _grammar.bnf.nt('$');
      _grammar.bnf.rules[at++] = _grammar.bnf.rule(result, [ nt1 ]);        // $#: $##

    } else                                                                  // many
      nt1 = result;

    _grammar.bnf.rules[at++] = _grammar.bnf.rule(nt1, [ ]);                 // $##:
    _grammar.bnf.rules[at] = _grammar.bnf.rule(nt1, [ node, nt1 ]);         // $##: node $##
  }

  return result;
};

/** Called once by {@link page.EBNF#init} to complete EBNF grammar initialization.
  * Imports {@link page.BNF.NT}`.empty`, {@link page.BNF.NT}`.first`, and
  * {@link page.BNF.NT}`.follow` from {@link page.EBNF.Rep}`.nt`
  * and then delegates to the descendant.
  * @private
  */
page.EBNF.Rep.prototype.init = function () {
  this.assert('__WHERE__', !this.first);

  this.empty = this.nt.empty;
  this.first = this.nt.first;
  this.follow = this.nt.follow;

  if (this.node.init) this.node.init();
};