Pre-release
AdventureJS Docs Downloads
Score: 0 Moves: 0
// tokenizeAnd.js

(function () {
  /* global AdventureJS A */

  var p = AdventureJS.Parser.prototype;

  /**
   * ",", "and", and ", and" can be used in multiple ways:
   * <ol>
   *  <li>as a conjunction
   *    <ul>
   *      <li>"take glass key and silver key"</li>
   *      <li>"take all but glass key and silver key"</li>
   *      <li>"take glass key, silver key"</li>
   *      <li>"take all but glass key, silver key"</li>
   *    </ul>
   *  </li>
   *  <li>as a sentence boundary (like "then")
   *    <ul>
   *      <li>"take glass key and go north"</li>
   *      <li>"jump and go north"</li>
   *      <li>"jump up and go north"</li>
   *      <li>"go east and go north"</li>
   *      <li>STRETCH: "put a in b and c in d"</li>
   *    </ul>
   *  </li>
   * </ol>
   *
   * When "and" is used as a conjunction in phrases like
   * "take sword and stone", convert "and" to "&" to make
   * plural noun string "sword&stone".
   * <br/><br/>
   * When used as a sentence boundary, we convert "and"
   * to " | ", which will be used to split sentences
   * in a later step.
   * <br/><br/>
   * We also look for chained prepositional phrases:<br/>
   * "put a in b and c in d (ad infinitum)"
   * @memberOf AdventureJS.Parser
   * @method AdventureJS.Parser#tokenizeAnd
   * @param {String} input
   * @returns {String}
   */
  p.tokenizeAnd = function Parser_tokenizeAnd(input) {
    this.game.log(
      "L1702",
      "log",
      "high",
      `[tokenizeAnd.js] tokenizeAnd() receive: ${input}`,
      "Parser"
    );

    const sentences = input
      .split("|")
      .map((sentence) => sentence.trim())
      .filter((sentence) => sentence.length);

    let newsentences = [];

    // --------------------------------------------------
    // for each sentence
    // --------------------------------------------------
    for (let i = 0; i < sentences.length; i++) {
      // normalize variations of "and" for easier split
      // look for:
      // - ", and "
      // - " and "
      // - ", "
      let sentence = sentences[i].replace(
        /(?:,\s+and\s+|,\s+|\s+and\s+)/gi,
        " and "
      );

      let words = sentence.split(/\s+/);
      console.warn({ words });

      // --------------------------------------------------
      // chained prepositional phrases
      // "put a in b and c in d (ad infinitum)"
      // "give a to b and c to d (ad infinitum)"
      // --------------------------------------------------
      if (words.length >= 8) {
        let chained = true;

        const typed = [];

        for (let w = 0; w < words.length; w++) {
          let wordtypes = {};
          let word = words[w];
          wordtypes.noun = this.game.world_lookup[word.toLowerCase()];
          wordtypes.direction = this.game.dictionary.getDirection(word);
          wordtypes.verb = this.game.getVerb(word);
          wordtypes.preposition = this.game.dictionary.getPreposition(word);
          wordtypes.and = word === "and";
          typed.push(wordtypes);
        }

        if (
          !typed[0] ||
          !typed[0].verb ||
          !typed[1] ||
          !typed[1].noun ||
          !typed[2] ||
          !typed[2].preposition ||
          !typed[3] ||
          !typed[3].noun
        ) {
          chained = false;
        }

        for (let i = 4; i < typed.length; i += 4) {
          if (
            !typed[i] ||
            !typed[i].and ||
            !typed[i + 1] ||
            !typed[i + 1].noun ||
            !typed[i + 2] ||
            !typed[i + 2].preposition ||
            !typed[i + 3] ||
            !typed[i + 3].noun
          ) {
            chained = false;
          }
        }

        if (chained) {
          // copy verb to each clause and add boundary
          sentence = sentence.replace("and", ` | ${words[0]}`);
          words = sentence.split(/\s+/);
        }
      }

      // --------------------------------------------------
      // conjunctions - for each word
      // --------------------------------------------------
      // we want to catch all the conjunctions
      // before moving on to boundaries
      // examples:
      // "take glass key and silver key"
      // "take all but glass key and silver key"
      // "take glass key, silver key"
      // "take all but glass key, silver key"
      // --------------------------------------------------
      for (let w = 0; w < words.length; w++) {
        if (words[w] !== "and") continue;

        // get words on either side
        const lastword = words[w - 1];
        const lastnoun = this.game.world_lookup[lastword.toLowerCase()];
        const nextword = words[w + 1];
        const nextnoun = this.game.world_lookup[nextword.toLowerCase()];

        if (lastnoun && nextnoun) {
          // found conjunction
          words[w] = "&";
          continue;
        }
      }

      // ----------------------------------------
      // update current sentence
      // ----------------------------------------
      sentence = words.map((item) => item.trim()).join(" ");
      // trim &'s
      sentence = sentence.replace(/\s*&\s*/g, "&");

      // split into words again for boundary check
      words = sentence.split(/\s+/);

      // ----------------------------------------
      // boundaries - for each word
      // examples:
      // "take and wear glasses"
      // "take glass key and go north"
      // ----------------------------------------
      for (let w = 0; w < words.length; w++) {
        if (words[w] !== "and") continue;

        // get words on either side
        const lastword = words[w - 1];
        const lastnoun = this.game.world_lookup[lastword.toLowerCase()];
        const lastdirection = this.game.dictionary.getDirection(lastword);
        const lastverb = this.game.getVerb(lastword);
        const lastpreposition = this.game.dictionary.getPreposition(lastword);

        const nextword = words[w + 1];
        const nextnoun = this.game.world_lookup[nextword.toLowerCase()];
        const nextdirection = this.game.dictionary.getDirection(nextword);
        const nextverb = this.game.getVerb(nextword);
        const nextpreposition = this.game.dictionary.getPreposition(nextword);

        // all words before and after
        const before = words.slice(0, w);
        const after = words.slice(w + 1);

        // ----------------------------------------
        // if lastword is noun and nextword is verb
        // ex: "take glass key and go north"
        // ----------------------------------------
        if (lastnoun && nextverb) {
          words[w] = "|";
          continue;
        }

        // ----------------------------------------
        // if lastword is intransitive verb and nextword is verb
        // ex: "jump and go north"
        // ----------------------------------------
        if (
          lastverb?.isIntransitive() &&
          !lastdirection &&
          nextverb &&
          !nextdirection
        ) {
          words[w] = "|";
          continue;
        }

        // ----------------------------------------
        // if lastword is direction and nextword is verb
        // "go east and go north"
        // ----------------------------------------
        if (lastdirection && nextverb) {
          words[w] = "|";
          continue;
        }

        // ----------------------------------------
        // if lastword is non-intransitive verb and nextword is verb
        // ex: "take and wear eyeglasses"
        // ----------------------------------------
        if (
          lastverb &&
          !lastverb.isIntransitive() &&
          !lastdirection &&
          nextverb &&
          !nextdirection
        ) {
          // We're going to copy the entire [after] clause to the
          // [before] clause then copy lastverb back into it,
          // to end up with something like "take eyeglasses | wear eyeglasses"
          if (before.length === 1 && after.length > 1) {
            words[w - 1] = after.join(" ").replace(nextword, lastword);
            words[w] = "|";
            continue;
          }
        }

        // @TODO what other scenarios?
      }

      // rejoin words
      sentence = words.map((item) => item.trim()).join(" ");
      // trim &'s
      sentence = sentence.replace(/\s*&\s*/g, "&");
      newsentences.push(sentence);
    }

    input = newsentences.map((item) => item.trim()).join(" | ");

    this.game.log(
      "L1703",
      "log",
      "high",
      `[tokenizeAnd.js] tokenizeAnd() return: ${input}`,
      "Parser"
    );
    return input;
  };
})();