//! Abstract Syntax Tree for Geolog //! //! Based on the syntax sketched in loose_thoughts/2025-12-12_12:10.md use std::fmt; /// A span in the source code, for error reporting #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Span { pub start: usize, pub end: usize, } impl Span { pub fn new(start: usize, end: usize) -> Self { Self { start, end } } } /// A node with source location #[derive(Clone, Debug, PartialEq, Eq)] pub struct Spanned { pub node: T, pub span: Span, } impl Spanned { pub fn new(node: T, span: Span) -> Self { Self { node, span } } } /// An identifier, possibly qualified with `/` (e.g., `N/P`, `W/src/arc`) #[derive(Clone, Debug, PartialEq, Eq)] pub struct Path { pub segments: Vec, } impl Path { pub fn single(name: String) -> Self { Self { segments: vec![name], } } pub fn is_single(&self) -> bool { self.segments.len() == 1 } pub fn as_single(&self) -> Option<&str> { if self.segments.len() == 1 { Some(&self.segments[0]) } else { None } } } impl fmt::Display for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.segments.join("/")) } } /// A complete source file #[derive(Clone, Debug, PartialEq, Eq)] pub struct File { pub declarations: Vec>, } /// Top-level declarations #[derive(Clone, Debug, PartialEq, Eq)] pub enum Declaration { /// `namespace Foo;` Namespace(String), /// `theory (params) Name { body }` Theory(TheoryDecl), /// `TypeExpr instance Name { body }` Instance(InstanceDecl), /// `query Name { ? : Type; }` Query(QueryDecl), } /// A theory declaration /// e.g., `theory (N : PetriNet instance) Marking { ... }` /// or `theory Foo extends Bar { ... }` #[derive(Clone, Debug, PartialEq, Eq)] pub struct TheoryDecl { pub params: Vec, pub name: String, /// Optional parent theory to extend pub extends: Option, pub body: Vec>, } /// A parameter to a theory /// e.g., `N : PetriNet instance` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Param { pub name: String, pub ty: TypeExpr, } /// Items that can appear in a theory body #[derive(Clone, Debug, PartialEq, Eq)] pub enum TheoryItem { /// `P : Sort;` Sort(String), /// `in.src : in -> P;` Function(FunctionDecl), /// `ax1 : forall w : W. hyps |- concl;` Axiom(AxiomDecl), /// Inline instance (for nested definitions) /// `initial_marking : N Marking instance;` Field(String, TypeExpr), } /// A function/morphism declaration #[derive(Clone, Debug, PartialEq, Eq)] pub struct FunctionDecl { pub name: Path, // Can be dotted like `in.src` pub domain: TypeExpr, pub codomain: TypeExpr, } /// An axiom declaration #[derive(Clone, Debug, PartialEq, Eq)] pub struct AxiomDecl { pub name: Path, // Can be hierarchical like `ax/anc/base` pub quantified: Vec, pub hypotheses: Vec, pub conclusion: Formula, } /// A quantified variable in an axiom /// e.g., `w : W` or `w1, w2 : W` #[derive(Clone, Debug, PartialEq, Eq)] pub struct QuantifiedVar { pub names: Vec, pub ty: TypeExpr, } /// A single token in a type expression stack program (concatenative parsing) #[derive(Clone, Debug, PartialEq, Eq)] pub enum TypeToken { /// Push a path onto the stack (might be sort, instance ref, or theory name) Path(Path), /// The `Sort` keyword - pushes the Sort kind Sort, /// The `Prop` keyword - pushes the Prop kind Prop, /// The `instance` keyword - pops top, wraps as instance type, pushes Instance, /// Arrow - pops two types (domain, codomain), pushes function type /// Note: arrows are handled specially during parsing to maintain infix syntax Arrow, /// Record type literal: `[field : Type, ...]` /// Contains nested TypeExprs for field types (evaluated recursively) Record(Vec<(String, TypeExpr)>), } /// A type expression as a flat stack program (concatenative style) /// /// Instead of a tree like `App(App(A, B), C)`, we store a flat sequence /// `[Path(A), Path(B), Path(C)]` that gets evaluated during elaboration /// when we have access to the symbol table (to know theory arities). #[derive(Clone, Debug, PartialEq, Eq)] pub struct TypeExpr { pub tokens: Vec, } impl TypeExpr { /// Create a type expression from a single path pub fn single_path(p: Path) -> Self { Self { tokens: vec![TypeToken::Path(p)], } } /// Create the Sort kind pub fn sort() -> Self { Self { tokens: vec![TypeToken::Sort], } } /// Create the Prop kind pub fn prop() -> Self { Self { tokens: vec![TypeToken::Prop], } } /// Check if this is a single path (common case) pub fn as_single_path(&self) -> Option<&Path> { if self.tokens.len() == 1 && let TypeToken::Path(p) = &self.tokens[0] { return Some(p); } None } /// Check if this is the Sort kind pub fn is_sort(&self) -> bool { matches!(self.tokens.as_slice(), [TypeToken::Sort]) } /// Check if this ends with `instance` pub fn is_instance(&self) -> bool { self.tokens.last() == Some(&TypeToken::Instance) } /// Get the inner type expression (without the trailing `instance` token) pub fn instance_inner(&self) -> Option { if self.is_instance() { Some(Self { tokens: self.tokens[..self.tokens.len() - 1].to_vec(), }) } else { None } } /// Check if this is the Prop kind pub fn is_prop(&self) -> bool { matches!(self.tokens.as_slice(), [TypeToken::Prop]) } /// Check if this is a record type pub fn as_record(&self) -> Option<&Vec<(String, TypeExpr)>> { if self.tokens.len() == 1 && let TypeToken::Record(fields) = &self.tokens[0] { return Some(fields); } None } } /// Terms (elements of types) #[derive(Clone, Debug, PartialEq, Eq)] pub enum Term { /// A variable or path: `w`, `W/src/arc` /// `/` is namespace qualification Path(Path), /// Function application (postfix style in surface syntax) /// `w W/src` means "apply W/src to w" App(Box, Box), /// Field projection: `expr .field` /// Note the space before `.` to distinguish from path qualification Project(Box, String), /// Record literal: `[firing: f, arc: arc]` Record(Vec<(String, Term)>), } /// Formulas (geometric logic) #[derive(Clone, Debug, PartialEq, Eq)] pub enum Formula { /// False/Bottom (⊥): inconsistency, empty disjunction False, /// Relation application: `rel(term)` or `rel([field: value, ...])` RelApp(String, Term), /// Equality: `t1 = t2` Eq(Term, Term), /// Conjunction (often implicit in antecedents) And(Vec), /// Disjunction: `phi \/ psi` Or(Vec), /// Existential: `exists w : W. phi` Exists(Vec, Box), /// Truth True, } /// An instance declaration /// e.g., `instance ExampleNet : PetriNet = { ... }` /// or `instance ExampleNet : PetriNet = chase { ... }` for chase-before-check #[derive(Clone, Debug, PartialEq, Eq)] pub struct InstanceDecl { pub theory: TypeExpr, pub name: String, pub body: Vec>, /// If true, run chase algorithm after elaboration before checking axioms. /// Syntax: `instance Name : Theory = chase { ... }` pub needs_chase: bool, } /// Items in an instance body #[derive(Clone, Debug, PartialEq, Eq)] pub enum InstanceItem { /// Element declaration: `A : P;` or `a, b, c : P;` Element(Vec, TypeExpr), /// Equation: `ab_in in.src = A;` Equation(Term, Term), /// Nested instance: `initial_marking = N Marking instance { ... };` NestedInstance(String, InstanceDecl), /// Relation assertion: `[item: buy_groceries] completed;` /// The Term should be a record with the relation's domain fields, /// and String is the relation name. RelationAssertion(Term, String), } /// A query declaration /// e.g., `query query0 { ? : ExampleNet Problem0 ReachabilityProblemSolution; }` #[derive(Clone, Debug, PartialEq, Eq)] pub struct QueryDecl { pub name: String, pub goal: TypeExpr, }