Pre-release
Adventure.js Docs Downloads
Score: 0 Moves: 0
// Parser.js
(function () {
  /*global adventurejs A*/
  "use strict";

  /**
   * @class adventurejs.Parser
   * @ajsnavheading FrameworkReference
   * @param {Game} game A reference to the game instance.
   * @summary Interprets player input.
   * @classdesc
   * <p>
   * The <strong>Parser</strong> class interprets
   * player input. In short, it takes a string, sanitizes it
   * of undesirable characters, performs several operations to
   * strip away extraneous grammar such as definite/indefinite
   * articles, performs a number of
   * regular expression searches, compares the input against
   * different verb/noun patterns stored in a lookup table,
   * all to reduce input to a simple
   * format such as 'verb noun noun'. Parser can accept
   * one verb and up to three nouns. Clauses joined by 'and'
   * or 'then' are split into multiple inputs and queued
   * for serial handling.</p>
   * <p>
   * This is an internal class
   * that authors should not need to construct, though there are
   * methods to allow authors to inject their own custom
   * parser code. See
   * <a href="/doc/Extend_WriteParsers.html">Write Parsers</a>
   * for more info.
   * </p>
   * <p>Here are some examples of inputs and how they would be parsed.
   * </p>
   * <h3 class="examples">Examples:</h3>
   * <code class="input">
   * <ul>
   * <li>unlock the icy door with the glass key<br>
   * <strong>parses to &nbsp;=&gt;&nbsp; </strong> unlock icy_door glass_key</li>
   * <li>ask Mr. Giles about Mrs. Beasley<br>
   * <strong>parses to &nbsp;=&gt;&nbsp; </strong> ask_about mr_giles mrs_beasley</li>
   * <li>climb over to branch<br>
   * <strong>parses to &nbsp;=&gt;&nbsp; </strong> climb_to branch</li>
   * <li>swing from the tree to the cliff on the vine <br>
   * <strong>parses to &nbsp;=&gt;&nbsp; </strong> swing_from_to_on tree cliff vine</li>
   * <li>look at the zebra through the binoculars<br>
   * <strong>parses to &nbsp;=&gt;&nbsp; </strong> lookAt_through zebra binoculars</li>
   * <li>unlock door then open door then go north<br>
   * <strong>parses to three inputs &nbsp;=&gt;&nbsp; </strong> unlock door / open door / go north</li>
   * </ul>
   * </code>
   */
  class Parser {
    constructor(game) {
      /**
       * A reference back to the main {@link adventurejs.Game|Game} object.
       * @var {Object} adventurejs.Parser#game
       * @default {}
       */
      this.game = game;

      /**
       * A reference back to the main Game {@link adventurejs.Dictionary|Dictionary} object.
       * @var {Object} adventurejs.Parser#dictionary
       * @default {}
       */
      this.dictionary = game.dictionary;

      /**
       * A reference back to the main Game {@link adventurejs.Display|Display} object.
       * @var {Object} adventurejs.Parser#display
       * @default {}
       */
      this.display = game.display;

      /**
       * Used to store custom parser code that might be written by authors.
       * @var {Object} adventurejs.Parser#custom_parsers
       * @default {}
       */
      this.custom_parsers = {};

      /**
       * If custom parsers have been written by author,
       * this provides a way to turn them on/off, something you might
       * want to do depending on context.
       * @var {Object} adventurejs.Parser#custom_parsers_enabled
       * @default {}
       */
      this.custom_parsers_enabled = {};

      /**
       * Used to store a copy of player input when player hits enter
       * and before parsing.
       * @var {String} adventurejs.Parser#input_string
       * @default ""
       */
      this.input_string = "";

      // this.game.reactor.addEventListener('inputEnter', function(e){
      //   var msg = "Parser received Event > inputEnter: " + this.game.parser.input_string;
      //   this.game.log( "log", "high", msg );
      //   this.game.parser.parseInput( this.game.parser.input_string );
      // });

      /**
       * Stores all player input. Used for Undo and Again,
       * and in letting player use arrow keys to recall prior
       * turns' input, as in a terminal application.
       * @var {Array} adventurejs.Parser#input_history
       * @default [{input:"wait"}]
       */
      this.input_history = [];

      // push an initial turn to input history so we have something to refer back to on the first turn
      this.input_history.push(
        new adventurejs.Input({ game_name: this.game.game_name, verb: "wait" })
      );

      /**
       * When player uses arrow keys to navigate between old inputs,
       * this stores player's position in the history array.
       * @var {int} adventurejs.Parser#input_history_index
       */
      this.input_history_index = 0;

      /**
       * When player uses conjuctive clauses joined by 'and' or 'then'
       * we split the input into separate inputs and queue them up to
       * handle in sequence. Queued inputs are saved here.
       * @var {Array} adventurejs.Parser#input_queue
       * @default []
       */
      this.input_queue = [];

      /**
       * Unused?
       * @var {Object} adventurejs.Parser#input_object
       * @default {}
       * @todo Is this irrelevant?
       */
      this.input_object = {};

      /**
       * Save (this.input_queue.length > 0) during parse,
       * because at some point later in the parse we shift input_queue,
       * but later than that we still need to know whether we're queued.
       * @var {Boolean} adventurejs.Parser#is_input_queued
       * @default false
       */
      this.is_input_queued = false;
    }

    /**
     * Track input for display, used to present player
     * input from previous turns in the input field,
     * like a terminal does.
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#getOlderInput
     */
    getOlderInput() {
      if ("undefined" === typeof this.input_history_index) return "";

      if (-1 === this.input_history_index) {
        this.input_history_index = 0;
      } else if (this.input_history_index < this.input_history.length - 2) {
        this.input_history_index++;
      }

      return this.input_history[this.input_history_index].unparsed_input;
    }

    /**
     * Listen for arrowDown key, used to present player input
     * from previous turns in the input field, as in a terminal.
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#getNewerInput
     */
    getNewerInput() {
      if (this.input_history_index > 0) {
        this.input_history_index--;
      }

      if (
        "undefined" !== typeof this.input_history[this.input_history_index] &&
        "undefined" !==
          typeof this.input_history[this.input_history_index].unparsed_input
      ) {
        return this.input_history[this.input_history_index].unparsed_input;
      }

      return "";
    }

    /**
     * Method to get whether we're parsing through a queue of inputs.
     * Possible redundant with is_input_queued?
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#isParsingMultiple
     * @todo Is this and is_input_queued duplicative?
     */
    isParsingMultiple() {
      return 0 < this.input_queue.length || this.is_input_queued;
    }

    /**
     * Respond to blank input from player with
     * {@link adventurejs.Settings#if_input_is_empty_print_this|settings.if_input_is_empty_print_this}.
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#parseNoInput
     */
    parseNoInput() {
      // settings.if_input_is_empty_print_this can return string, array, or function

      if (this.game.settings.if_input_is_empty_print_room_description) {
        this.game.printCurrentRoom();
        return;
      }

      this.game.print(
        A.getSAF.call(
          this.game,
          this.game.settings.if_input_is_empty_print_this
        )
      );

      return;
    }

    /**
     * Take input and return a standardized "unparsed" message.
     * {@link adventurejs.Settings#if_parser_has_no_response_print_this|settings.if_parser_has_no_response_print_this}.
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#getUnparsedMessage
     */
    getUnparsedMessage(input) {
      this.game.debug(
        `F1158 | Parser.js | print settings.if_parser_has_no_response_print_this `
      );
      var msg = A.getSAF.call(
        this.game,
        this.game.settings.if_parser_has_no_response_print_this
      );
      var regex = /\$\((.*?)\)/g;
      return msg.replace(regex, input);
    }

    /**
     * Get input from last turn.
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#getLastInput
     * @returns {String}
     */
    getLastInput() {
      return this.input_history[1].unparsed_input;
    }

    /**
     * Get a count of input history.
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#getInputCount
     * @returns {int}
     */
    getInputCount() {
      return this.input_history.length;
    }

    /**
     * Provides a chainable shortcut method for setting a number of properties on the instance.
     * @memberOf adventurejs.Parser
     * @method adventurejs.Parser#set
     * @param {Object} props A generic object containing properties to copy to the Object instance.
     * @returns {adventurejs.Parser} Returns the instance the method is called on (useful for chaining calls.)
     * @chainable
     */
    set(props) {
      if (props != null) {
        for (var n in props) {
          this[n] = props[n];
        }
      }
      return this;
    }
  }
  adventurejs.Parser = Parser;
})();