gollum-ir 0.4.0

Intermediate Representation for the Gollum language
Documentation
//! IR-level term type.

/// An IR-level term, possibly annotated with reasoning metadata.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[allow(missing_docs)]
pub enum IrTerm {
    // --- Primitive terms ---
    Var(String),
    Atom(String),
    Number(i64),
    /// Extension: the AST has `Term::Float(f64)` which would lose information as `Number(i64)`.
    Float(f64),
    /// A dense tensor / embedding value (e.g., from `#[1.0, 0.5, -0.3]`).
    Tensor(Vec<f32>),
    Structure {
        name: String,
        args: Vec<IrTerm>,
    },
    /// Cut (`!`): commits to the current clause, removing all choice points back
    /// to the enclosing predicate's cut barrier.
    Cut,

    // --- Annotated terms ---
    /// Type-constrained term
    Typed {
        term: Box<IrTerm>,
        ty: String,
    },
    /// Probabilistic fact annotation
    Probabilistic {
        term: Box<IrTerm>,
        prob: f64,
    },
    /// Temporal scoping annotation
    Temporal {
        term: Box<IrTerm>,
        start: i128,
        end: i128,
    },
    /// Modal reasoning annotation
    ///
    /// Carries a structured `ModalAnnotation` containing operator and optional agent.
    Modal {
        term: Box<IrTerm>,
        annotation: crate::ModalAnnotation,
    },
    /// Neural predicate reference
    Neural {
        term: Box<IrTerm>,
        model_id: String,
    },
    /// Differentiable logic annotation
    Differentiable {
        term: Box<IrTerm>,
        grad_id: String,
    },
    /// Neural predicate with gradient tracking (bidirectional).
    /// Combines neural model execution with differentiable gradient flow.
    DiffNeural {
        term: Box<IrTerm>,
        model_id: String,
        grad_id: String,
    },
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ModalAnnotation;
    use std::f64::consts::PI;

    #[test]
    fn test_primitive_variants() {
        assert_eq!(IrTerm::Var("X".into()), IrTerm::Var("X".into()));
        assert_eq!(IrTerm::Atom("foo".into()), IrTerm::Atom("foo".into()));
        assert_eq!(IrTerm::Number(42), IrTerm::Number(42));
        assert_eq!(IrTerm::Float(PI), IrTerm::Float(PI));
        assert_eq!(
            IrTerm::Structure {
                name: "f".into(),
                args: vec![IrTerm::Atom("a".into())]
            },
            IrTerm::Structure {
                name: "f".into(),
                args: vec![IrTerm::Atom("a".into())]
            },
        );
    }

    #[test]
    fn test_annotated_variants() {
        let base = Box::new(IrTerm::Atom("rain".into()));
        let typed = IrTerm::Typed {
            term: base.clone(),
            ty: "bool".into(),
        };
        let prob = IrTerm::Probabilistic {
            term: base.clone(),
            prob: 0.8,
        };
        let temporal = IrTerm::Temporal {
            term: base.clone(),
            start: 0,
            end: 100,
        };
        let modal = IrTerm::Modal {
            term: base.clone(),
            annotation: ModalAnnotation::necessity(),
        };
        let neural = IrTerm::Neural {
            term: base.clone(),
            model_id: "m1".into(),
        };
        let diff = IrTerm::Differentiable {
            term: base.clone(),
            grad_id: "g1".into(),
        };

        assert_eq!(
            typed,
            IrTerm::Typed {
                term: base.clone(),
                ty: "bool".into()
            }
        );
        assert_eq!(
            prob,
            IrTerm::Probabilistic {
                term: base.clone(),
                prob: 0.8
            }
        );
        assert_eq!(
            temporal,
            IrTerm::Temporal {
                term: base.clone(),
                start: 0,
                end: 100
            }
        );
        assert_eq!(
            modal,
            IrTerm::Modal {
                term: base.clone(),
                annotation: ModalAnnotation::necessity(),
            }
        );
        assert_eq!(
            neural,
            IrTerm::Neural {
                term: base.clone(),
                model_id: "m1".into()
            }
        );
        assert_eq!(
            diff,
            IrTerm::Differentiable {
                term: base.clone(),
                grad_id: "g1".into()
            }
        );
    }

    #[test]
    fn test_primitive_ron_roundtrip() {
        let terms = vec![
            IrTerm::Var("X".into()),
            IrTerm::Atom("foo".into()),
            IrTerm::Number(42),
            IrTerm::Float(1.5),
            IrTerm::Structure {
                name: "f".into(),
                args: vec![IrTerm::Number(1)],
            },
        ];
        for term in &terms {
            let s = ron::to_string(term).unwrap();
            let back: IrTerm = ron::from_str(&s).unwrap();
            assert_eq!(term, &back);
        }
    }

    #[test]
    #[ignore = "RON does not support i128 serialization"]
    fn test_annotated_ron_roundtrip() {
        let base = Box::new(IrTerm::Atom("rain".into()));
        let terms = vec![
            IrTerm::Typed {
                term: base.clone(),
                ty: "bool".into(),
            },
            IrTerm::Probabilistic {
                term: base.clone(),
                prob: 0.9,
            },
            IrTerm::Temporal {
                term: base.clone(),
                start: 10,
                end: 20,
            },
            IrTerm::Modal {
                term: base.clone(),
                annotation: ModalAnnotation::necessity(),
            },
            IrTerm::Neural {
                term: base.clone(),
                model_id: "net1".into(),
            },
            IrTerm::Differentiable {
                term: base.clone(),
                grad_id: "grad1".into(),
            },
        ];
        for term in &terms {
            let s = ron::to_string(term).unwrap();
            let back: IrTerm = ron::from_str(&s).unwrap();
            assert_eq!(term, &back);
        }
    }
}