import { parseFreeformFactV3 } from './truthtrance-freeform-facts-v3'; import { parseFreeformFactV4 } from './truthtrance-freeform-facts-v4'; import type { TruthFact } from './truthtrance'; type Compound = Extract; /** * A new, deliberately narrow grammar: two existing current-fact atoms joined * by one unambiguous AND/OR. Older parser versions and their saved receipts * remain unchanged. Call only for fact-scoped text and show the interpretation. */ export function parseTruthFreeformFactsV5(text: string): Compound | null { if (typeof text !== 'string' || text.length > 500) return null; const input = text.trim(); // A complete old interpretation, including a card name containing "and", // retains its original meaning rather than being reinterpreted by V5. if (parseFreeformFactV3(input) || parseFreeformFactV4(input)) return null; const question = /^do[ \t]+you[ \t]+have[ \t]+(.+?)[ \t]*\??$/i.exec(input); if (!question) return null; const body = question[1]; const atom = (part: string): TruthFact | null => { const value = part.trim(); if (!value || /[?\r\n]/.test(value)) return null; const full = /^do[ \t]+you[ \t]+(?:have|not[ \t]+have)[ \t]+/i.test(value) ? value : /^not[ \t]+have[ \t]+/i.test(value) ? `Do you ${value}` : /^have[ \t]+/i.test(value) ? `Do you ${value}` : `Do you have ${value}`; const fact = parseFreeformFactV3(full) ?? parseFreeformFactV4(full); return fact && fact.kind !== 'and' && fact.kind !== 'or' ? fact : null; }; const matches: Compound[] = []; for (const connector of body.matchAll(/[ \t]+(and|or)[ \t]+/gi)) { const index = connector.index ?? 0; const left = atom(body.slice(0, index)); const right = atom(body.slice(index + connector[0].length)); if (left && right) matches.push({ kind: connector[1].toLowerCase() as 'and' | 'or', terms: [left, right] }); } return matches.length === 1 ? matches[0] : null; }