import { DeonticFormula, DeonticOperator } from '../types.js';
export class DeonticParser {
/**
* Parse deontic logic expressions
* Supports:
* - O (obligatory): O(p) - it is obligatory that p
* - P (permitted): P(p) - it is permitted that p
* - F (forbidden): F(p) - it is forbidden that p
* - OB (obligatory given): OB(p|q) - p is obligatory given q
* - PM (permitted given): PM(p|q) - p is permitted given q
* - IM (impermissible): IM(p) - it is impermissible that p
*/
parse(input: string): DeonticFormula {
const tokens = this.tokenize(input);
return this.parseFormula(tokens);
}
private tokenize(input: string): string[] {
// Normalize input
input = input.trim()
.replace(/\s+/g, ' ')
.replace(/obligatory/gi, 'O')
.replace(/obligated/gi, 'O')
.replace(/must/gi, 'O')
.replace(/required/gi, 'O')
.replace(/permitted/gi, 'P')
.replace(/allowed/gi, 'P')
.replace(/may/gi, 'P')
.replace(/forbidden/gi, 'F')
.replace(/prohibited/gi, 'F')
.replace(/impermissible/gi, 'IM')
.replace(/→/g, '->')
.replace(/implies/gi, '->')
.replace(/∧/g, '&')
.replace(/and/gi, '&')
.replace(/∨/g, '|')
.replace(/or/gi, '|')
.replace(/¬/g, '!')
.replace(/not/gi, '!')
.replace(/given/gi, '|')
.replace(/if/gi, '|');
// Tokenize
const tokens: string[] = [];
let current = '';
let inParens = false;
for (let i = 0; i < input.length; i++) {
const char = input[i];
if (char === '(' || char === ')') {
if (current) {
tokens.push(current);
current = '';
}
tokens.push(char);
inParens = char === '(';
} else if (/[A-Z]/i.test(char) || /[a-z]/i.test(char) || /[0-9]/.test(char)) {
current += char;
} else {
if (current) {
tokens.push(current);
current = '';
}
if (char !== ' ') {
// Handle multi-character operators
if (char === '-' && i + 1 < input.length && input[i + 1] === '>') {
tokens.push('->');
i++; // Skip next character
} else {
tokens.push(char);
}
}
}
}
if (current) {
tokens.push(current);
}
return tokens;
}
private parseFormula(tokens: string[]): DeonticFormula {
return this.parseImplication(tokens);
}
private parseImplication(tokens: string[]): DeonticFormula {
const left = this.parseOr(tokens);
if (tokens[0] === '-' && tokens[1] === '>') {
tokens.shift();
tokens.shift();
const right = this.parseImplication(tokens);
return {
type: 'implication',
left,
right
};
}
return left;
}
private parseOr(tokens: string[]): DeonticFormula {
const left = this.parseAnd(tokens);
if (tokens[0] === '|' && !this.isConditionalContext(tokens)) {
tokens.shift();
const right = this.parseOr(tokens);
return {
type: 'or',
left,
right
};
}
return left;
}
private parseAnd(tokens: string[]): DeonticFormula {
const left = this.parseUnary(tokens);
if (tokens[0] === '&') {
tokens.shift();
const right = this.parseAnd(tokens);
return {
type: 'and',
left,
right
};
}
return left;
}
private parseUnary(tokens: string[]): DeonticFormula {
if (tokens[0] === '!') {
tokens.shift();
const operand = this.parseUnary(tokens);
return {
type: 'negation',
operand
};
}
// Check for deontic operators
if (this.isDeonticOperator(tokens[0])) {
const operator = tokens.shift() as DeonticOperator;
// Check for conditional deontic operators (OB, PM)
const nextToken = tokens[0];
if ((operator === 'OB' || operator === 'PM') && nextToken === '(') {
tokens.shift(); // Remove '('
const content = this.parseConditionalContent(tokens);
const closingToken = tokens[0];
if (closingToken === ')') tokens.shift(); // Remove ')'
return {
type: 'deontic',
operator: operator,
operand: content
};
}
// Regular deontic operators
const operand = this.parseUnary(tokens);
return {
type: 'deontic',
operator: operator,
operand
};
}
return this.parseAtom(tokens);
}
private parseAtom(tokens: string[]): DeonticFormula {
const firstToken = tokens[0];
if (firstToken === '(') {
tokens.shift();
const formula = this.parseFormula(tokens);
const nextToken = tokens[0];
if (nextToken === ')') {
tokens.shift();
}
return formula;
}
const atom = tokens.shift();
if (!atom) {
throw new Error('Unexpected end of input');
}
return {
type: 'atom',
name: atom
};
}
private parseConditionalContent(tokens: string[]): DeonticFormula {
// Parse "p|q" for conditional deontic operators
const condition = this.parseBasicFormula(tokens);
if (tokens[0] === '|') {
tokens.shift();
const consequence = this.parseBasicFormula(tokens);
// Create a special structure for conditional obligations
return {
type: 'implication',
left: consequence, // What is obligated/permitted
right: condition // Given this condition
};
}
return condition;
}
private parseBasicFormula(tokens: string[]): DeonticFormula {
// Parse a basic formula without full recursion
const firstToken = tokens[0];
if (firstToken === '(') {
return this.parseAtom(tokens);
}
const atom = tokens[0];
if (atom) {
tokens.shift();
return {
type: 'atom',
name: atom
};
}
throw new Error('Expected formula');
}
private isDeonticOperator(token: string): boolean {
return ['O', 'P', 'F', 'OB', 'PM', 'IM'].includes(token);
}
private isConditionalContext(tokens: string[]): boolean {
// Check if we're in a conditional deontic context
// Look back to see if we have OB or PM
return false; // Simplified for now
}
}