export type PropVar = "p" | "q" | "r" | "s"; export const propVars: PropVar[] = ["p", "q", "r", "s"]; export type Formula = | { type: "top" | "bot" } | { type: "propvar"; name: PropVar } | { type: "not" | "box" | "diamond"; fml: Formula } | { type: "to" | "or" | "and" | "eq"; left: Formula; right: Formula; }; export function vars(fml: Formula): Set { switch (fml.type) { case "top": case "bot": return new Set(); case "box": case "diamond": case "not": return vars(fml.fml); case "to": case "or": case "and": case "eq": return new Set([...vars(fml.left), ...vars(fml.right)]); case "propvar": return new Set([fml.name]); } } export const propvar = (name: PropVar) => ({ type: "propvar", name }) as const; export const top = { type: "top" } as const satisfies Formula; export const bot = { type: "bot" } as const satisfies Formula; export const box = (fml: Formula) => ({ type: "box", fml }) as const satisfies Formula; export const diamond = (fml: Formula) => ({ type: "diamond", fml }) as const satisfies Formula; export const not = (fml: Formula) => ({ type: "not", fml }) as const satisfies Formula; export const to = (left: Formula, right: Formula) => ({ type: "to", left, right }) as const satisfies Formula; export const and = (left: Formula, right: Formula) => ({ type: "and", left, right }) as const satisfies Formula; export const or = (left: Formula, right: Formula) => ({ type: "or", left, right }) as const satisfies Formula; export const eq = (left: Formula, right: Formula) => ({ type: "eq", left, right }) as const satisfies Formula; export type Symbols = Record, string>; export const asciiSymbols: Symbols = { top: "T", bot: "F", box: "[]", diamond: "<>", not: "~", and: "^", or: "v", to: "->", eq: "<->", }; export const unicodeSymbols: Symbols = { top: "⊤", bot: "⊥", box: "□", diamond: "◇", not: "¬", and: "∧", or: "∨", to: "→", eq: "↔", }; export const latexSymbols: Symbols = { top: "\\top ", bot: "\\bot ", box: "\\Box ", diamond: "\\Diamond ", not: "\\neg ", and: "\\land", or: "\\lor", to: "\\to", eq: "\\leftrightarrow", }; export function prettyPrint( fml: Formula, config?: { paren?: boolean | ((fml: Formula) => boolean); symbols?: Symbols }, ): string { const { paren = false, symbols = unicodeSymbols } = config ?? {}; const withParen = (s: string) => (typeof paren === "boolean" ? paren : paren(fml)) ? `(${s})` : s; switch (fml.type) { case "top": return symbols.top; case "bot": return symbols.bot; case "propvar": return fml.name; case "not": return `${symbols.not}${prettyPrint(fml.fml, { paren: true, symbols })}`; case "box": return `${symbols.box}${prettyPrint(fml.fml, { paren: true, symbols })}`; case "diamond": return `${symbols.diamond}${prettyPrint(fml.fml, { paren: true, symbols })}`; case "and": { const newConfig: typeof config = { symbols, paren: (f) => f.type !== "and", }; return withParen( `${prettyPrint(fml.left, newConfig)} ${symbols.and} ${prettyPrint(fml.right, newConfig)}`, ); } case "or": { const newConfig: typeof config = { symbols, paren: (f) => f.type !== "or", }; return withParen( `${prettyPrint(fml.left, newConfig)} ${symbols.or} ${prettyPrint(fml.right, newConfig)}`, ); } case "to": { const newConfig: typeof config = { symbols, paren: (f) => f.type !== "to", }; return withParen( `${prettyPrint(fml.left, { symbols, paren: true })} ${symbols.to} ${prettyPrint(fml.right, newConfig)}`, ); } case "eq": { const newConfig: typeof config = { symbols, paren: (f) => f.type !== "eq", }; return withParen( `${prettyPrint(fml.left, newConfig)} ${symbols.eq} ${prettyPrint(fml.right, newConfig)}`, ); } } }