// 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 => </strong> unlock icy_door glass_key</li>
* <li>ask Mr. Giles about Mrs. Beasley<br>
* <strong>parses to => </strong> ask_about mr_giles mrs_beasley</li>
* <li>climb over to branch<br>
* <strong>parses to => </strong> climb_to branch</li>
* <li>swing from the tree to the cliff on the vine <br>
* <strong>parses to => </strong> swing_from_to_on tree cliff vine</li>
* <li>look at the zebra through the binoculars<br>
* <strong>parses to => </strong> lookAt_through zebra binoculars</li>
* <li>unlock door then open door then go north<br>
* <strong>parses to three inputs => </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;
})();