import { type Token, alt, apply, buildLexer, expectEOF, expectSingleResult, kmid, lrec_sc, rule, seq, tok, } from "typescript-parsec"; import { type Formula, and, bot, box, diamond, eq, not, or, propVars, propvar, to, top, } from "./syntax"; enum TokenKind { PropVar = 0, Top = 1, Bot = 2, Not = 3, Box = 4, Diamond = 5, And = 6, Or = 7, To = 8, Eq = 9, LParen = 10, RParen = 11, Space = 12, } const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const generateAlt = (words: string[]) => new RegExp(`^(${words.map((s) => escapeRegExp(s)).join("|")})`, "g"); export const topSymbols = ["⊤", "T", "1", "\\top", "top"]; export const botSymbols = ["⊥", "F", "0", "\\bot", "bot"]; export const notSymbols = ["¬", "~", "\\neg", "\\lnot", "not"]; export const boxSymbols = ["□", "◻", "[]", "!", "L", "\\Box", "box"]; export const diamondSymbols = [ "◇", "◊", "♢", "⋄", "<>", "?", "M", "\\Diamond", "dia", ]; export const andSymbols = ["∧", "^", "&", "\\wedge", "\\land", "and"]; export const orSymbols = ["∨", "v", "|", "\\vee", "\\lor", "or"]; export const toSymbols = ["→", "->", "⊃", ">", "\\rightarrow", "\\to", "to"]; export const eqSymbols = [ "↔", "<->", "≡", "=", "\\leftrightarrow", "\\equiv", "eq", ]; export const lParenSymbols = ["(", "\\left"]; export const rParenSymbols = [")", "\\right"]; const lexer = buildLexer([ [true, generateAlt(propVars), TokenKind.PropVar], [true, generateAlt(topSymbols), TokenKind.Top], [true, generateAlt(botSymbols), TokenKind.Bot], [true, generateAlt(notSymbols), TokenKind.Not], [true, generateAlt(boxSymbols), TokenKind.Box], [true, generateAlt(diamondSymbols), TokenKind.Diamond], [true, generateAlt(andSymbols), TokenKind.And], [true, generateAlt(orSymbols), TokenKind.Or], [true, generateAlt(toSymbols), TokenKind.To], [true, generateAlt(eqSymbols), TokenKind.Eq], [true, generateAlt(lParenSymbols), TokenKind.LParen], [true, generateAlt(rParenSymbols), TokenKind.RParen], [false, /^\s+/g, TokenKind.Space], ]); function atom( value: Token, ): Formula { switch (value.kind) { case TokenKind.PropVar: { if (propVars.includes(value.text)) { return propvar(value.text); } throw new Error(`Unknown atom: ${value.text}`); } case TokenKind.Top: return top; case TokenKind.Bot: return bot; default: throw new Error(`Unknown atom: ${value.text}`); } } function unary([op, value]: [ Token, Formula, ]): Formula { switch (op.kind) { case TokenKind.Not: return not(value); case TokenKind.Box: return box(value); case TokenKind.Diamond: return diamond(value); default: throw new Error(`Unknown unary operator: ${op.text}`); } } function binary( left: Formula, [op, right]: [ Token, Formula, ], ): Formula { switch (op.kind) { case TokenKind.And: return and(left, right); case TokenKind.Or: return or(left, right); case TokenKind.To: return to(left, right); case TokenKind.Eq: return eq(left, right); default: throw new Error(`Unknown binary operator: ${op.text}`); } } const TERM = rule(); const ANDOR = rule(); const EXP = rule(); TERM.setPattern( alt( apply( alt(tok(TokenKind.PropVar), tok(TokenKind.Top), tok(TokenKind.Bot)), atom, ), apply( seq( alt(tok(TokenKind.Not), tok(TokenKind.Box), tok(TokenKind.Diamond)), TERM, ), unary, ), kmid(tok(TokenKind.LParen), EXP, tok(TokenKind.RParen)), ), ); ANDOR.setPattern( lrec_sc(TERM, seq(alt(tok(TokenKind.And), tok(TokenKind.Or)), ANDOR), binary), ); EXP.setPattern( lrec_sc(ANDOR, seq(alt(tok(TokenKind.To), tok(TokenKind.Eq)), EXP), binary), ); export function parse(expr: string): Formula { return expectSingleResult(expectEOF(EXP.parse(lexer.parse(expr)))); } export function tryParse(expr: string): Formula | undefined { try { return parse(expr); } catch { return undefined; } }