1#![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 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
38pub 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}