feat(parser): strip leading stop-words (at, the, a, an) from noun phrase

Allows `look at lamp`, `examine the letter`, `take a key`, `take an oil lamp`.
Stop-words are only removed from the head of the noun phrase, not from
anywhere in the middle.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 13:36:34 -05:00
parent 14a58481b1
commit b325f04b02
2 changed files with 45 additions and 0 deletions
+37
View File
@@ -231,3 +231,40 @@ describe('parser — pronouns', () => {
expect(result.kind).toBe('unknown')
})
})
describe('stop-word stripping', () => {
const ctx: ParserContext = {
knownItems: ['lamp'],
knownEncounters: [],
visibleNouns: [{ id: 'lamp', aliases: ['lamp', 'oil lamp'] }],
inventoryItemIds: [],
lastNoun: null,
awaitingDisambiguation: null,
}
it('strips a leading "at" from the noun phrase', () => {
const cmd = parse('look at lamp', ctx)
expect(cmd).toEqual({
kind: 'verb-target',
verb: 'look',
target: { canonical: 'lamp', raw: 'lamp' },
})
})
it('strips a leading "the"', () => {
const cmd = parse('examine the lamp', ctx)
expect(cmd.kind).toBe('verb-target')
})
it('strips "a" and "an"', () => {
expect(parse('take a lamp', ctx).kind).toBe('verb-target')
expect(parse('take an oil lamp', ctx).kind).toBe('verb-target')
})
it('does not strip stop-words mid-phrase', () => {
// 'oil lamp' is the alias; 'oil at lamp' is not. Stop-words only strip from
// the head of the noun phrase, not anywhere in the middle.
const cmd = parse('take oil lamp', ctx)
expect(cmd.kind).toBe('verb-target')
})
})
+8
View File
@@ -59,6 +59,9 @@ const VERB_ONLY_VERBS = new Set<string>(['look', 'inventory', 'wait'])
/** Two-word verb prefixes (e.g. "pick up X"). */
const TWO_WORD_VERBS = ['pick up']
/** Leading stop-words stripped from the noun phrase before matching. */
const STOP_WORDS = new Set(['at', 'the', 'a', 'an'])
function tokenize(input: string): string[] {
return input.trim().toLowerCase().split(/\s+/).filter(Boolean)
}
@@ -126,6 +129,11 @@ export function parse(rawInput: string, ctx: ParserContext): ParsedCommand {
return { kind: 'unknown', raw: trimmed, reason: 'unknown-verb' }
}
// Strip leading stop-words from the noun phrase (e.g. "at", "the", "a", "an").
while (rest.length > 0 && STOP_WORDS.has(rest[0]!)) {
rest = rest.slice(1)
}
if (rest.length === 0) {
if (VERB_ONLY_VERBS.has(verb)) {
return { kind: 'verb-only', verb: verb as 'look' | 'inventory' | 'wait' }