/**
* Abstract Syntax Tree (AST) node definitions for PDSL
*
* Defines the structure of parsed PDSL programs.
*/
// ============================================================================
// Location Tracking
// ============================================================================
export interface Location {
line: number;
column: number;
}
export function createLocation(line: number, column: number): Location {
return { line, column };
}
// ============================================================================
// Top-Level Nodes
// ============================================================================
export interface Program {
type: 'Program';
models: Model[];
location: Location;
}
export interface Model {
type: 'Model';
name: string;
statements: Statement[];
location: Location;
}
// ============================================================================
// Statement Types
// ============================================================================
export type Statement =
| ProbabilisticFact
| ProbabilisticRule
| DeterministicFact
| AnnotatedDisjunction
| Observation
| Query
| LearningDirective;
export interface ProbabilisticFact {
type: 'ProbabilisticFact';
probability: number | string; // number or variable name (for learning)
atom: Atom;
location: Location;
}
export interface ProbabilisticRule {
type: 'ProbabilisticRule';
probability: number | string; // number or variable name (for learning)
head: Atom;
body: Literal[];
location: Location;
}
export interface DeterministicFact {
type: 'DeterministicFact';
atom: Atom;
location: Location;
}
export interface AnnotatedDisjunction {
type: 'AnnotatedDisjunction';
choices: ProbabilisticFact[];
location: Location;
}
export interface Observation {
type: 'Observation';
literal: Literal;
location: Location;
}
export interface Query {
type: 'Query';
atom: Atom;
location: Location;
}
export interface LearningDirective {
type: 'LearningDirective';
dataset: string;
location: Location;
}
// ============================================================================
// Logical Constructs
// ============================================================================
export interface Atom {
type: 'Atom';
predicate: string;
args: Term[];
location: Location;
}
export interface Literal {
type: 'Literal';
negated: boolean;
atom: Atom;
location: Location;
}
// ============================================================================
// Terms
// ============================================================================
export type Term = Variable | Constant | NumberTerm | Atom;
export interface Variable {
type: 'Variable';
name: string;
location: Location;
}
export interface Constant {
type: 'Constant';
value: string;
location: Location;
}
export interface NumberTerm {
type: 'Number';
value: number;
location: Location;
}
// ============================================================================
// Visitor Pattern Support
// ============================================================================
export interface ASTVisitor<T> {
visitProgram(node: Program): T;
visitModel(node: Model): T;
visitProbabilisticFact(node: ProbabilisticFact): T;
visitProbabilisticRule(node: ProbabilisticRule): T;
visitDeterministicFact(node: DeterministicFact): T;
visitAnnotatedDisjunction(node: AnnotatedDisjunction): T;
visitObservation(node: Observation): T;
visitQuery(node: Query): T;
visitLearningDirective(node: LearningDirective): T;
visitAtom(node: Atom): T;
visitLiteral(node: Literal): T;
visitVariable(node: Variable): T;
visitConstant(node: Constant): T;
visitNumber(node: NumberTerm): T;
}
// ============================================================================
// Utility Functions
// ============================================================================
/**
* Check if a term is ground (contains no variables)
*/
export function isGroundTerm(term: Term): boolean {
if (term.type === 'Variable') {
return false;
}
if (term.type === 'Atom') {
return term.args.every(isGroundTerm);
}
return true;
}
/**
* Check if an atom is ground
*/
export function isGroundAtom(atom: Atom): boolean {
return atom.args.every(isGroundTerm);
}
/**
* Extract all variables from a term
*/
export function extractVariables(term: Term): Set<string> {
const vars = new Set<string>();
function collect(t: Term): void {
if (t.type === 'Variable') {
vars.add(t.name);
} else if (t.type === 'Atom') {
t.args.forEach(collect);
}
}
collect(term);
return vars;
}
/**
* Extract all variables from an atom
*/
export function extractAtomVariables(atom: Atom): Set<string> {
const vars = new Set<string>();
atom.args.forEach(arg => {
extractVariables(arg).forEach(v => vars.add(v));
});
return vars;
}
/**
* Extract all variables from a literal
*/
export function extractLiteralVariables(literal: Literal): Set<string> {
return extractAtomVariables(literal.atom);
}
/**
* Pretty-print an AST node for debugging
*/
export function prettyPrint(node: any, indent: number = 0): string {
const spaces = ' '.repeat(indent);
const lines: string[] = [];
if (node.type === 'Program') {
lines.push(`${spaces}Program`);
node.models.forEach((m: Model) => {
lines.push(prettyPrint(m, indent + 1));
});
} else if (node.type === 'Model') {
lines.push(`${spaces}Model: ${node.name}`);
node.statements.forEach((s: Statement) => {
lines.push(prettyPrint(s, indent + 1));
});
} else if (node.type === 'ProbabilisticFact') {
lines.push(`${spaces}ProbFact: ${node.probability} :: ${prettyPrint(node.atom, 0)}`);
} else if (node.type === 'ProbabilisticRule') {
const bodyStr = node.body.map((l: Literal) => prettyPrint(l, 0)).join(', ');
lines.push(`${spaces}ProbRule: ${node.probability} :: ${prettyPrint(node.head, 0)} :- ${bodyStr}`);
} else if (node.type === 'DeterministicFact') {
lines.push(`${spaces}Fact: ${prettyPrint(node.atom, 0)}`);
} else if (node.type === 'AnnotatedDisjunction') {
const choices = node.choices.map((c: ProbabilisticFact) =>
`${c.probability}::${prettyPrint(c.atom, 0)}`
).join('; ');
lines.push(`${spaces}AD: ${choices}`);
} else if (node.type === 'Observation') {
lines.push(`${spaces}Observe: ${prettyPrint(node.literal, 0)}`);
} else if (node.type === 'Query') {
lines.push(`${spaces}Query: ${prettyPrint(node.atom, 0)}`);
} else if (node.type === 'LearningDirective') {
lines.push(`${spaces}Learn from: ${node.dataset}`);
} else if (node.type === 'Atom') {
if (node.args.length === 0) {
return node.predicate;
}
const argsStr = node.args.map((a: Term) => prettyPrint(a, 0)).join(', ');
return `${node.predicate}(${argsStr})`;
} else if (node.type === 'Literal') {
const prefix = node.negated ? 'not ' : '';
return `${prefix}${prettyPrint(node.atom, 0)}`;
} else if (node.type === 'Variable') {
return node.name;
} else if (node.type === 'Constant') {
return node.value;
} else if (node.type === 'Number') {
return node.value.toString();
} else {
return `${spaces}${node.type}`;
}
return lines.join('\n');
}