tdln_compiler/
lib.rs

1//! TDLN Compiler — NL/DSL → AST + Canonical JSON + `ProofBundle` (deterministic).
2//!
3//! Determinism: same input + same rule set → same outputs (AST, canon, CID).
4
5#![forbid(unsafe_code)]
6
7#[cfg(feature = "dv25")]
8use logline_core as _;
9
10use blake3::Hasher;
11use serde::{Deserialize, Serialize};
12use tdln_ast::SemanticUnit;
13use tdln_proof::{build_proof, ProofBundle};
14use thiserror::Error;
15
16#[derive(Debug, Error)]
17pub enum CompileError {
18    #[error("empty input")]
19    Empty,
20    #[error("internal")]
21    Internal,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CompileCtx {
26    /// Versioned rule set id; choose different ids to represent tie-breaking evolution.
27    pub rule_set: String,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct CompiledIntent {
32    pub ast: SemanticUnit,
33    pub canon_json: Vec<u8>,
34    pub cid: [u8; 32],
35    pub proof: ProofBundle,
36}
37
38/// Compila intent em AST + JSON canônico + prova.
39///
40/// # Errors
41///
42/// - `CompileError::Empty` se a entrada estiver vazia ou só whitespace
43/// - `CompileError::Internal` reservado para futuras falhas internas
44pub fn compile(input: &str, ctx: &CompileCtx) -> Result<CompiledIntent, CompileError> {
45    if input.trim().is_empty() {
46        return Err(CompileError::Empty);
47    }
48    let ast = SemanticUnit::from_intent(input);
49    let canon = ast.canonical_bytes();
50    let mut h = Hasher::new();
51    h.update(&canon);
52    let cid = h.finalize().into();
53    let proof = build_proof(&ast, &canon, &[ctx.rule_set.as_str()]);
54    Ok(CompiledIntent {
55        ast,
56        canon_json: canon,
57        cid,
58        proof,
59    })
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    #[test]
66    fn determinism_whitespace() {
67        let ctx = CompileCtx {
68            rule_set: "v1".into(),
69        };
70        let a = compile("  HELLO   world", &ctx).unwrap();
71        let b = compile("hello world", &ctx).unwrap();
72        assert_eq!(a.cid, b.cid);
73        assert_eq!(a.proof.ast_cid, b.proof.ast_cid);
74        assert_eq!(a.proof.canon_cid, b.proof.canon_cid);
75    }
76}