tessellate-core 0.7.0

Compiler and deterministic runtime for the Tess rule language
use crate::source::{SourceFile, Span, Spanned};
use serde::{Deserialize, Serialize};

pub type Name = String;

#[derive(Clone, Debug)]
pub struct Program {
    pub source: SourceFile,
    pub module: Spanned<Name>,
    /// Other partial-module files referenced by this program's `use` declarations.
    ///
    /// Used files are resolved by project-aware hosts such as the Tess CLI. The
    /// compiler itself receives the declarations from every reachable file as
    /// one program, so name resolution remains intentionally module-wide.
    pub imports: Vec<ImportDecl>,
    /// Set only by a project-aware loader after every `use` target is included.
    pub imports_resolved: bool,
    pub declarations: Vec<Declaration>,
}

#[derive(Clone, Debug)]
pub struct ImportDecl {
    pub path: Spanned<String>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub enum Declaration {
    Enum(EnumDecl),
    Entity(EntityDecl),
    Derive(DeriveDecl),
    Decision(DecisionDecl),
    Fragment(FragmentDecl),
    Rule(RuleDecl),
    Fixture(FixtureDecl),
    Case(CaseDecl),
    Invariant(InvariantDecl),
}

impl Declaration {
    #[must_use]
    pub fn name(&self) -> &Spanned<Name> {
        match self {
            Self::Enum(value) => &value.name,
            Self::Entity(value) => &value.name,
            Self::Derive(value) => &value.name,
            Self::Decision(value) => &value.name,
            Self::Fragment(value) => &value.id,
            Self::Rule(value) => &value.name,
            Self::Fixture(value) => &value.name,
            Self::Case(value) => &value.name,
            Self::Invariant(value) => &value.name,
        }
    }

    #[must_use]
    pub fn span(&self) -> Span {
        match self {
            Self::Enum(value) => value.span,
            Self::Entity(value) => value.span,
            Self::Derive(value) => value.span,
            Self::Decision(value) => value.span,
            Self::Fragment(value) => value.span,
            Self::Rule(value) => value.span,
            Self::Fixture(value) => value.span,
            Self::Case(value) => value.span,
            Self::Invariant(value) => value.span,
        }
    }
}

#[derive(Clone, Debug)]
pub struct EnumDecl {
    pub name: Spanned<Name>,
    pub variants: Vec<Spanned<Name>>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct EntityDecl {
    pub name: Spanned<Name>,
    pub fields: Vec<FieldDecl>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct FieldDecl {
    pub name: Spanned<Name>,
    pub ty: TypeRef,
    pub range: Option<RangeConstraint>,
    pub domain: Option<DomainConstraint>,
    pub optional: bool,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TypeRef {
    Bool,
    Int,
    Decimal,
    String,
    Date,
    Duration,
    Named(Name),
    Unknown,
}

#[derive(Clone, Debug)]
pub struct RangeConstraint {
    pub start: NumericLiteral,
    pub end: NumericLiteral,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct DomainConstraint {
    pub values: Vec<Expr>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct Parameter {
    pub ty: Spanned<Name>,
    pub name: Spanned<Name>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct DeriveDecl {
    pub name: Spanned<Name>,
    pub parameters: Vec<Parameter>,
    pub return_type: Spanned<TypeRef>,
    /// Whether the source omitted the return type and asks the compiler to
    /// infer it from the function body.
    pub implicit_return_type: bool,
    pub expression: Expr,
    /// Native-document fragments that define or justify this function.
    ///
    /// A function nested in a fragment receives that fragment as its implicit
    /// first basis, just as a nested rule does.
    pub basis: Vec<FragmentRef>,
    pub span: Span,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Cardinality {
    #[default]
    ExactlyOne,
    ZeroOrOne,
    Many,
}

#[derive(Clone, Debug)]
pub struct DecisionDecl {
    pub name: Spanned<Name>,
    pub parameters: Vec<Parameter>,
    /// Whether the input signature is inferred from the rules that decide
    /// this value. An explicitly zero-argument rule effect clears this flag.
    pub implicit_parameters: bool,
    pub return_type: Spanned<TypeRef>,
    pub cardinality: Cardinality,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct FragmentDecl {
    /// Stable Tess identity used for references, package qualification, and graphs.
    pub id: Spanned<Name>,
    /// Opaque native-document locator retained as display/provenance metadata.
    pub locator: Spanned<String>,
    pub text: Spanned<String>,
    pub refs: Vec<FragmentRef>,
    pub derives: Vec<DeriveDecl>,
    pub rules: Vec<RuleDecl>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct FragmentRef {
    pub id: Spanned<Name>,
}

impl FragmentRef {
    #[must_use]
    pub const fn span(&self) -> Span {
        self.id.span
    }
}

#[derive(Clone, Debug)]
pub struct RuleDecl {
    pub name: Spanned<Name>,
    pub parameters: Vec<Parameter>,
    /// Whether the source omitted the parameter list and asks the compiler to
    /// infer record bindings used by the rule body.
    pub implicit_parameters: bool,
    pub condition: Expr,
    pub effect: Effect,
    pub basis: Vec<FragmentRef>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub enum Effect {
    Decide {
        decision: Spanned<Name>,
        arguments: Vec<Expr>,
        /// Whether the source omitted the decision argument list. The compiler
        /// fills it from the surrounding rule and the decision signature.
        implicit_arguments: bool,
        value: Expr,
        span: Span,
    },
    Override {
        rule: Spanned<Name>,
        span: Span,
    },
    /// Recovery placeholder for a rule whose effect line could not be parsed.
    Invalid {
        span: Span,
    },
}

/// A reusable, typed partial record value for tests.
///
/// Fixtures deliberately have no inheritance of their own. A test binding may
/// select one fixture and override any of its fields locally.
#[derive(Clone, Debug)]
pub struct FixtureDecl {
    pub name: Spanned<Name>,
    pub entity: Spanned<Name>,
    pub fields: Vec<FieldValue>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct CaseDecl {
    pub name: Spanned<Name>,
    pub bindings: Vec<BindingDecl>,
    pub expectations: Vec<Expectation>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct BindingDecl {
    pub name: Spanned<Name>,
    pub entity: Spanned<Name>,
    pub fixture: Option<Spanned<Name>>,
    pub fields: Vec<FieldValue>,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct FieldValue {
    pub name: Spanned<Name>,
    pub value: Expr,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct Expectation {
    pub decision: Spanned<Name>,
    pub arguments: Vec<Expr>,
    /// Whether the source omitted the decision argument list. The compiler
    /// fills it from the surrounding test bindings or assertion parameters.
    pub implicit_arguments: bool,
    pub operator: CompareOp,
    pub value: Expr,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub struct InvariantDecl {
    pub name: Spanned<Name>,
    pub quantifier: InvariantQuantifier,
    pub variables: Vec<Parameter>,
    pub assertion: InvariantAssertion,
    pub span: Span,
}

/// How an assertion ranges over its record bindings.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InvariantQuantifier {
    /// Every binding must satisfy the assertion.
    #[default]
    All,
    /// At least one binding must satisfy the assertion.
    Some,
}

#[derive(Clone, Debug)]
pub enum InvariantAssertion {
    Cardinality {
        cardinality: Cardinality,
        decision: Spanned<Name>,
        arguments: Vec<Expr>,
        span: Span,
    },
    Implication {
        condition: Expr,
        expectation: Expectation,
        span: Span,
    },
}

#[derive(Clone, Debug)]
pub struct Expr {
    pub kind: ExprKind,
    pub span: Span,
}

impl Expr {
    #[must_use]
    pub const fn new(kind: ExprKind, span: Span) -> Self {
        Self { kind, span }
    }
}

#[derive(Clone, Debug)]
pub enum ExprKind {
    Literal(Literal),
    Name(Name),
    Field {
        receiver: Box<Expr>,
        field: Spanned<Name>,
    },
    Call {
        callee: Spanned<Name>,
        arguments: Vec<Expr>,
    },
    Unary {
        operator: UnaryOp,
        operand: Box<Expr>,
    },
    Binary {
        left: Box<Expr>,
        operator: BinaryOp,
        right: Box<Expr>,
    },
}

#[derive(Clone, Debug)]
pub enum Literal {
    Bool(bool),
    Number(NumericLiteral),
    String(String),
    Unknown,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NumericLiteral {
    Int(i64),
    Decimal(String),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnaryOp {
    Not,
    Negate,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryOp {
    Or,
    And,
    Equal,
    NotEqual,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
    Add,
    Subtract,
    Multiply,
    Divide,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompareOp {
    Equal,
    NotEqual,
}