332 lines
8.4 KiB
Rust
Raw Normal View History

2026-02-26 11:50:51 +01:00
//! 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<T> {
pub node: T,
pub span: Span,
}
impl<T> Spanned<T> {
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<String>,
}
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<Spanned<Declaration>>,
}
/// 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<Param>,
pub name: String,
/// Optional parent theory to extend
pub extends: Option<Path>,
pub body: Vec<Spanned<TheoryItem>>,
}
/// 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<QuantifiedVar>,
pub hypotheses: Vec<Formula>,
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<String>,
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<TypeToken>,
}
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<Self> {
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<Term>, Box<Term>),
/// Field projection: `expr .field`
/// Note the space before `.` to distinguish from path qualification
Project(Box<Term>, 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<Formula>),
/// Disjunction: `phi \/ psi`
Or(Vec<Formula>),
/// Existential: `exists w : W. phi`
Exists(Vec<QuantifiedVar>, Box<Formula>),
/// 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<Spanned<InstanceItem>>,
/// 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<String>, 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,
}