// 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;
};
})();