46 lines
2.3 KiB
TypeScript
46 lines
2.3 KiB
TypeScript
import { parseFreeformFactV1 } from './truthtrance-freeform-facts';
|
|
import type { HandInventoryFact } from './truthtrance-hand-inventory';
|
|
import type { TruthFact } from './truthtrance';
|
|
|
|
type V1Fact = Extract<TruthFact, { kind: 'hand' | 'spice' }>;
|
|
export type FreeformFactV2 = V1Fact | HandInventoryFact |
|
|
{ kind: 'and' | 'or'; terms: [V1Fact, V1Fact] };
|
|
|
|
/**
|
|
* Version 2 preserves version 1 and recognizes a small additional set of
|
|
* complete current facts. Do not change this grammar for saved v2 questions.
|
|
* Callers must still restrict interpretation to fact-scoped questions.
|
|
*/
|
|
export function parseFreeformFactV2(text: string): FreeformFactV2 | null {
|
|
if (typeof text !== 'string' || text.length > 500) return null;
|
|
const input = text.trim();
|
|
// Check both whole-name and compound parses before deciding; a future
|
|
// printed name might itself contain a conjunction between known names.
|
|
const existing = parseFreeformFactV1(input);
|
|
const count = /^do[ \t]+you[ \t]+have[ \t]+(at[ \t]+least|exactly|at[ \t]+most)[ \t]+([0-9]+)[ \t]+cards?[ \t]+in[ \t]+your[ \t]+hand[ \t]*\??$/i.exec(input);
|
|
if (count) {
|
|
const value = Number(count[2]);
|
|
if (!Number.isSafeInteger(value) || value < 0) return null;
|
|
const relation = count[1].toLowerCase().replace(/[ \t]+/g, ' ');
|
|
return { kind: 'handInventory', category: 'all',
|
|
compare: relation === 'at least' ? 'gte' : relation === 'at most' ? 'lte' : 'eq', value };
|
|
}
|
|
const compound = /^do[ \t]+you[ \t]+have[ \t]+(.+?)[ \t]*\??$/i.exec(input);
|
|
if (!compound) return existing;
|
|
const body = compound[1];
|
|
const separator = /[ \t]+(and|or)[ \t]+/gi;
|
|
const matches: Array<Extract<FreeformFactV2, { kind: 'and' | 'or' }>> = [];
|
|
for (const match of body.matchAll(separator)) {
|
|
const index = match.index ?? 0;
|
|
const left = body.slice(0, index);
|
|
const right = body.slice(index + match[0].length);
|
|
const first = parseFreeformFactV1(`Do you have ${left}`);
|
|
const second = parseFreeformFactV1(/^do[ \t]+you[ \t]+have[ \t]+/i.test(right)
|
|
? right : `Do you have ${right}`);
|
|
if (first && second) matches.push({ kind: match[1].toLowerCase() as 'and' | 'or', terms: [first, second] });
|
|
}
|
|
// A whole printed name and a compound are also ambiguous if both fit.
|
|
if (matches.length > 1 || existing && matches.length > 0) return null;
|
|
return existing ?? matches[0] ?? null;
|
|
}
|