// parseSentence.js
(function () {
/* global adventurejs A */
var p = adventurejs.Parser.prototype;
/**
* Parse each word in a sentence.
* @memberOf adventurejs.Parser
* @method adventurejs.Parser#parseSentence
*/
p.parseSentence = function Parser_parseSentence() {
var this_turn = this.input_history[0];
const fx = "parseSentence.js";
this.game.log(
"L1265",
"log",
"high",
`[${fx}] parseSentence() receive: ${this_turn.parsed_input_array}`,
"Parser"
);
let last_word_type;
let last_word;
let firstverb;
let count = {
adjective: 0,
adverb: 0,
ambiguous_pronoun: 0,
direction: 0,
exclusion: 0,
noun: 0,
phrase: 0,
possessive_noun: 0 /* name + apostrophe */,
possessive_determiner: 0 /* his, her, its */,
preposition: 0,
objective_pronoun: 0 /* me, you, him */,
reflexive_pronoun: 0 /* myself, yourself */,
string: 0,
unknown: 0,
verb: 0,
};
for (
let position = 0;
position < this_turn.parsed_input_array.length;
position++
) {
// --------------------------------------------------
// get word from parsed_input_array
// --------------------------------------------------
let word = this_turn.parsed_input_array[position];
// --------------------------------------------------
// verb lookup
// --------------------------------------------------
let verb = this.parseVerb(word);
// --------------------------------------------------
// adverb lookup
// --------------------------------------------------
let adverb = this.game.dictionary.getAdverb(word);
// --------------------------------------------------
// direction lookup
// --------------------------------------------------
let direction = this.game.dictionary.getDirection(word);
let compass_direction =
direction && this.game.dictionary.verbs[direction].is_compass_direction;
let relative_direction =
direction &&
this.game.dictionary.verbs[direction].is_relative_direction;
let parsed_direction = direction && this.parseNoun(direction);
// --------------------------------------------------
// preposition lookup
// --------------------------------------------------
let preposition = this.game.dictionary.getPreposition(word);
// --------------------------------------------------
// global_string
// @TODO revise to handle tokens
// --------------------------------------------------
let string = word === "global_string" ? word : false;
// --------------------------------------------------
// objective pronouns - me, him, her, it, they/them, etc
// --------------------------------------------------
let objective_pronoun = this.game.dictionary.getObjectivePronoun(word);
// --------------------------------------------------
// reflexive pronouns - myself, yourself, herself, etc
// @TODO reflexive pronoun should resolve to a noun
// --------------------------------------------------
let reflexive_pronoun = this.game.dictionary.getReflexivePronoun(word);
// --------------------------------------------------
// possessive_determiner - his, her, its, theirs, etc
// --------------------------------------------------
let possessive_determiner =
this.game.dictionary.getPossessiveDeterminer(word);
// --------------------------------------------------
// possessive_noun - has apostrophe
// --------------------------------------------------
let possessive_noun = word.match(/\b[A-Za-z-.]+(?:'s|s')\b/g);
// --------------------------------------------------
// ambiguous_pronoun - probably only applies to "her"
// is both objective pronoun and possessive determiner
// --------------------------------------------------
let ambiguous_pronoun = objective_pronoun && possessive_determiner;
// --------------------------------------------------
// noun - several conditions may indicate a noun
// --------------------------------------------------
let noun =
/* is it the actual id of an asset? */
"undefined" !== typeof this.game.world[word] ||
/* did parseNoun find something? */
/* this.parseNoun(word).matches.all.length > 0 || */
this.game.world_lookup[word.toLowerCase()]?.IDs.length ||
/* did we get multiple search results? */
word.includes("&") ||
/* did we get multiple search results? */
word.includes("=") ||
/* is it the name of a class? */
"undefined" !== typeof adventurejs[A.propercase(word)];
// --------------------------------------------------
// exclusion - the result of "all BUT that"
// --------------------------------------------------
let exclusion = word.charAt(0) === "-";
// --------------------------------------------------
// number
// --------------------------------------------------
let number = !isNaN(Number(word)) ? word : false;
// --------------------------------------------------
// adjective lookup
// --------------------------------------------------
let adjective = this.game.dictionary.getAdjective(word);
let properties = {
adjective: adjective,
ambiguous_pronoun: ambiguous_pronoun,
compass_direction: compass_direction,
direction: direction,
exclusion: exclusion,
noun: noun,
number: number,
parsed_direction: parsed_direction,
preposition: preposition,
objective_pronoun: objective_pronoun,
possessive_determiner: possessive_determiner,
possessive_noun: possessive_noun,
relative_direction: relative_direction,
string: string,
verb: verb,
word: word,
};
if (noun && exclusion) {
// both have & in them, get caught by noun check
// but only exclusion has the leading -
noun = false;
}
// Handle type-of-word ambiguities.
// Any direction can be a verb.
// Check position in sentence:
// - if it's the first word, it's a verb
// - if it's the second word...
// - if first word was an adverb, it's a verb
// - otherwise it's a direction
// Directions that are not verbs can be treated like nouns.
// Later during verification we'll check if direction is a preposition.
if (verb && direction && preposition) {
// this catches 'in' and 'out', which are defined as direction
// verbs in addition to being prepositions
// can be used to catch 'up' and 'down'
// if 'up' and 'down' are in dictionary.prepositions
if (position === 0 || (position === 1 && count.adverb > 0)) {
// almost certainly a verb
direction = false;
preposition = false;
} else if (position === 1) {
// could be "go in" - direction and preposition
// could be "look in" or "put in" - prepositions
// treat it like a preposition and let verbs handle it
verb = false;
direction = false;
} else {
// probably preposition
verb = false;
direction = false;
}
}
if (verb && direction && adverb) {
// this catches 'up' and 'down', which are
// defined as direction verbs and are also adverbs
// 2024.05.26 for now, removed directions from adverbs list
if (position === 0) {
// almost certainly a verb
direction = false;
adverb = false;
noun = false;
} else if (
position === 1 ||
position === this_turn.parsed_input_array.length - 1 ||
last_word_type === "verb"
) {
// could be "go up" - direction and adverb
// could be "look up" or "put up" - adverbs
// treat it like an adverb and let verbs handle it
verb = false;
direction = false;
noun = false;
} else {
// in the case of a phrase like "throw ball up",
// up would be an adverb
// in the case of a phrase like "throw ball up the stairs",
// up would be a preposition
// at this moment we don't know which, so treat it like a direction
// which is to say, a noun
verb = false;
adverb = false;
}
}
if (verb && noun && direction) {
this.game.log(
"L1532",
"log",
"high",
`[${fx}] word is verb, noun and direction: ${verb}, ${noun}, ${direction}`,
"Parser"
);
// this catches most directions
if (position === 0 || (position === 1 && count.adverb > 0)) {
// almost certainly a verb
direction = false;
noun = false;
} else {
// if it's a verb and a direction, but not an adverb
// or a preposition, treat it as a noun
// as in "go north" or "look north"
direction = false;
verb = false;
}
}
if (verb && noun) {
this.game.log(
"L1531",
"log",
"high",
`[${fx}] word is verb and noun: ${verb}, ${noun}`,
"Parser"
);
// consider the verb plug and the noun plug
if (position === 0 || (position === 1 && count.adverb > 0)) {
noun = false;
} else {
verb = false;
}
}
if (verb && direction) {
this.game.log(
"L1530",
"log",
"high",
`[${fx}] word is verb and direction: ${verb}, ${direction}`,
"Parser"
);
if (position === 0 || (position === 1 && count.adverb > 0)) {
direction = false;
} else {
verb = false;
}
}
if (verb && preposition) {
this.game.log(
"L1529",
"log",
"high",
`[${fx}] word is verb and preposition: ${verb}, ${preposition}`,
"Parser"
);
if (position === 0 || (position === 1 && count.adverb > 0)) {
preposition = false;
} else {
verb = false;
}
}
/* verb ---------------------------------------- OK */
if (verb) {
this.game.log(
"L1266",
"log",
"high",
`[${fx}] word is verb: ${verb}`,
"Parser"
);
if (count.verb === 0) firstverb = verb;
count.verb++;
this_turn.pushParsedWord({
type: "verb",
word: verb,
properties: properties,
});
last_word_type = "verb";
last_word = word;
continue;
}
// --------------------------------------------------
// adverb
// --------------------------------------------------
// not parsing adverbs for now but keep this for later
if (adverb) {
this.game.log(
"L1267",
"log",
"high",
`[${fx}] word is adverb: ${adverb}`,
"Parser"
);
count.adverb++;
this_turn.pushParsedWord({
type: "adverb",
word: adverb,
properties: properties,
});
last_word_type = "adverb";
last_word = word;
continue;
}
// distinguish directions from nouns,
// but also allow directions to be used as nouns
// --------------------------------------------------
// direction
// --------------------------------------------------
if (direction) {
this.game.log(
"L1268",
"log",
"high",
`[${fx}] word is direction: ${direction}`,
"Parser"
);
count.direction++;
count.phrase++;
this_turn.pushParsedWord({
type: "direction",
word: direction,
properties: properties,
});
last_word_type = "direction";
last_word = word;
continue;
}
/* preposition ---------------------------------------- OK */
if (preposition) {
// it's recognized as a preposition
this.game.log(
"L1269",
"log",
"high",
`[${fx}] word is preposition: ${word}`,
"Parser"
);
count.preposition++;
count.phrase++;
// store the typed words in their original order
this_turn.pushParsedWord({
type: "preposition",
word: word,
phrase: count.phrase,
properties: properties,
});
last_word_type = "preposition";
last_word = word;
continue;
}
// --------------------------------------------------
// string
// --------------------------------------------------
// if( string )
// {
// // it's a string, but we're not handling them that way yet
// this_turn.pushParsedWord( { type:'string',this_turn.parsed_input_array[position] } );
// // save it as a string or continue to save it as a noun?
// }
// --------------------------------------------------
// ambiguous pronoun - only applies to "her" ...?
// could be objective pronoun, could be possessive determiner
// we don't have enough information yet to tell
// we'll handle in verifySentence
// --------------------------------------------------
if (possessive_determiner && objective_pronoun) {
this.game.log(
"L1607",
"log",
"high",
`[${fx}] word is ambiguous pronoun: ${word}`,
"Parser"
);
count.ambiguous_pronoun++;
this_turn.pushParsedWord({
type: "ambiguous_pronoun",
word: word,
preposition: null,
properties: properties,
adjective: null,
});
last_word_type = "ambiguous_pronoun";
last_word = word;
continue;
}
// --------------------------------------------------
// possessive determiner
// --------------------------------------------------
if (possessive_determiner) {
this.game.log(
"L1606",
"log",
"high",
`[${fx}] word is possessive_determiner: ${word}`,
"Parser"
);
count.possessive_determiner++;
this_turn.pushParsedWord({
type: "possessive_determiner",
word: word,
preposition: null,
properties: properties,
adjective: null,
});
last_word_type = "possessive_determiner";
last_word = word;
continue;
}
// --------------------------------------------------
// objective pronoun
// --------------------------------------------------
if (objective_pronoun) {
this.game.log(
"L1599",
"log",
"high",
`[${fx}] word is objective pronoun: ${word}`,
"Parser"
);
count.objective_pronoun++;
this_turn.pushParsedWord({
type: "objective_pronoun",
word: word,
preposition: null,
properties: properties,
adjective: null,
});
last_word_type = "objective_pronoun";
last_word = word;
continue;
}
// --------------------------------------------------
// possessive noun
// --------------------------------------------------
if (possessive_noun) {
this.game.log(
"L1599",
"log",
"high",
`[${fx}] word is possessive noun: ${word}`,
"Parser"
);
count.possessive_noun++;
this_turn.pushParsedWord({
type: "possessive_noun",
word: word,
preposition: null,
properties: properties,
adjective: null,
});
last_word_type = "possessive_noun";
last_word = word;
continue;
}
// --------------------------------------------------
// noun
// --------------------------------------------------
if (noun) {
// Treat it like a noun. We do more checks later to see
// if it's unknown, and if it's not we want to soft prompt
// for "oops"
this.game.log(
"L1270",
"log",
"high",
`[${fx}] word is noun: ${word}`,
"Parser"
);
// --------------------------------------------------
// Unordered phrase check
// --------------------------------------------------
// This block looks for multiple parts of the same noun.
// This is a thing that might happen if player reverses
// the order of words in a multi-word name.
// The working example is "Mrs. Mighty Hero Ph.D."
// This block correctly catches, "mighty mrs hero"
// which would otherwise result in an error, partly
// due to reversed words and partly due to overlap
// with similarly named character "Mighty Hero".
// However we need to watch to ensure that it doesn't
// catch false positives.
if (last_word_type === "noun") {
const this_lookup = this.game.world_lookup[word.toLowerCase()]?.IDs;
const last_lookup =
this.game.world_lookup[last_word.toLowerCase()]?.IDs;
// do both words resolve to the same asset?
// @TODO is this robust enough? what edge cases will there be?
const common = this_lookup?.find((x) => last_lookup?.includes(x));
if (common) {
this.game.log(
"L1600",
"log",
"high",
`[${fx}] found common words ${last_word} and ${word} resolving to ${common}`,
"Parser"
);
// update the last word and discard this one
this_turn.parsed_sentence[
this_turn.parsed_sentence.length - 1
].word = common;
continue;
}
} // end out-of-order word check
// --------------------------------------------------
count.noun++;
this_turn.pushParsedWord({
type: "noun",
word: word,
preposition: null,
properties: properties,
adjective: adjective,
});
last_word_type = "noun";
last_word = word;
continue;
}
// --------------------------------------------------
// exclusion
// --------------------------------------------------
if (exclusion) {
// it's an exclusionary noun, like "take all but thing"
// the last noun will be the one we want to exclude from
this.game.log(
"L1271",
"log",
"high",
`[${fx}] exclude noun: ${word}`,
"Parser"
);
// remove the leading '-' which has done its job
word = word.slice(1);
if (last_word_type === "noun") {
let l = this_turn.parsed_sentence.length - 1;
this_turn.parsed_sentence[l].exclusion = word;
} else {
// this should not happen
this_turn.pushParsedWord({
type: "exclusion",
word: word,
properties: properties,
});
}
// END BLOCK
last_word_type = "exclusion";
last_word = word;
continue;
}
// --------------------------------------------------
// number
// --------------------------------------------------
if (number) {
// it's a number
this.game.log(
"L1272",
"log",
"high",
`[${fx}] word is number: ${word}`,
"Parser"
);
count.number++;
count.phrase++;
// store the typed words in their original order
this_turn.pushParsedWord({
type: "number",
word: word,
properties: properties,
});
last_word_type = "number";
last_word = word;
continue;
}
// --------------------------------------------------
// adjective
// --------------------------------------------------
if (adjective) {
// it's an adjective
this.game.log(
"L1273",
"log",
"high",
`[${fx}] word is adjective: ${word}`,
"Parser"
);
count.adjective++;
this_turn.pushParsedWord({
type: "adjective",
word: word,
properties: properties,
});
last_word_type = "adjective";
last_word = word;
continue;
}
// --------------------------------------------------
// unknown
// --------------------------------------------------
// it's unknown
this.game.log(
"L1274",
"log",
"high",
`[${fx}] word is unknown: ${word}`,
"Parser"
);
last_word_type = "unknown";
this_turn.pushParsedWord({
type: "unknown",
word: word,
properties: properties,
});
last_word = word;
} // for position loop
return true;
}; // parseSentence
})();