Skip to main content

tenferro_runtime/program/
transform.rs

1use std::collections::HashMap;
2
3use super::{
4    FrozenProgram, ImportedProgramValues, ProgramImport, ProgramValue, SemanticFingerprint,
5    SemanticProgramBuilder, SemanticTransformError,
6};
7
8/// Opaque fixed-size identity of one semantic transform implementation/configuration.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct TransformIdentity([u8; 16]);
11
12impl TransformIdentity {
13    /// Construct a caller-stable transform identity.
14    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
15        Self(bytes)
16    }
17
18    /// Borrow the fixed-size identity bytes.
19    pub const fn as_bytes(&self) -> &[u8; 16] {
20        &self.0
21    }
22}
23
24/// Validation-preserving destination transaction exposed to transforms.
25pub struct SemanticTransformContext<'a> {
26    builder: &'a mut SemanticProgramBuilder,
27}
28
29impl SemanticTransformContext<'_> {
30    /// Import source structure, bindings, and ordered roots atomically.
31    ///
32    /// # Errors
33    ///
34    /// Returns [`SemanticTransformError::Build`] when the underlying
35    /// [`SemanticProgramBuilder`](super::SemanticProgramBuilder) rejects
36    /// foreign roots/bindings, invalid structure, or an unrepresentable value
37    /// count.
38    pub fn import_program(
39        &mut self,
40        input: &FrozenProgram,
41        roots: &[ProgramValue],
42    ) -> Result<ImportedProgramValues, SemanticTransformError> {
43        self.builder
44            .import(ProgramImport {
45                program: input.program.as_ref(),
46                bindings: Some(&input.bindings),
47                roots,
48                input_map: &[],
49            })
50            .map_err(SemanticTransformError::from)
51    }
52
53    /// Borrow the destination builder for validated semantic additions.
54    pub fn builder(&mut self) -> &mut SemanticProgramBuilder {
55        self.builder
56    }
57}
58
59/// Object-safe transformation over immutable semantic programs and bindings.
60///
61/// # Examples
62///
63/// ```
64/// use tenferro_runtime::program::{
65///     FrozenProgram, ProgramValue, SemanticTransform, SemanticTransformContext,
66///     SemanticTransformError, TransformIdentity,
67/// };
68///
69/// struct Identity;
70/// impl SemanticTransform for Identity {
71///     fn identity(&self) -> TransformIdentity {
72///         TransformIdentity::from_bytes([7; 16])
73///     }
74///
75///     fn apply(
76///         &self,
77///         context: &mut SemanticTransformContext<'_>,
78///         input: &FrozenProgram,
79///     ) -> Result<Box<[ProgramValue]>, SemanticTransformError> {
80///         Ok(context
81///             .import_program(input, input.program.outputs())?
82///             .roots()
83///             .into())
84///     }
85/// }
86///
87/// fn accepts_object_safe(_: &dyn SemanticTransform) {}
88/// accepts_object_safe(&Identity);
89/// ```
90pub trait SemanticTransform: Send + Sync {
91    /// Return caller-stable identity including transform configuration.
92    fn identity(&self) -> TransformIdentity;
93
94    /// Import/build into the supplied fresh destination and return local roots.
95    ///
96    /// # Errors
97    ///
98    /// Implementations return [`SemanticTransformError::Build`] for validated
99    /// builder/import failures or [`SemanticTransformError::Rejected`] when
100    /// their semantic policy does not support the input. The compiler driver
101    /// additionally reports [`SemanticTransformError::ForeignReturnedValue`],
102    /// [`SemanticTransformError::DroppedBindings`], or
103    /// [`SemanticTransformError::Finish`] after this callback returns.
104    fn apply(
105        &self,
106        context: &mut SemanticTransformContext<'_>,
107        input: &FrozenProgram,
108    ) -> Result<Box<[ProgramValue]>, SemanticTransformError>;
109}
110
111#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
112pub(crate) fn apply_semantic_transform(
113    input: &FrozenProgram,
114    transform: &dyn SemanticTransform,
115) -> Result<FrozenProgram, SemanticTransformError> {
116    let mut builder = SemanticProgramBuilder::new();
117    let roots = {
118        let mut context = SemanticTransformContext {
119            builder: &mut builder,
120        };
121        transform.apply(&mut context, input)?
122    };
123    if roots
124        .iter()
125        .any(|root| builder.validate_value(*root).is_err())
126    {
127        return Err(SemanticTransformError::ForeignReturnedValue);
128    }
129    let output = builder.finish(&roots)?;
130    if !output.bindings.preserves_all(&input.bindings) {
131        return Err(SemanticTransformError::DroppedBindings);
132    }
133    Ok(output)
134}
135
136#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
137pub(crate) fn apply_semantic_transforms(
138    input: &FrozenProgram,
139    transforms: &[&dyn SemanticTransform],
140) -> Result<FrozenProgram, SemanticTransformError> {
141    let mut current = input.clone();
142    for transform in transforms {
143        current = apply_semantic_transform(&current, *transform)?;
144    }
145    Ok(current)
146}
147
148#[derive(Clone, PartialEq, Eq, Hash)]
149#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
150struct TransformCacheKey {
151    input: SemanticFingerprint,
152    ordered_transforms: Box<[TransformIdentity]>,
153}
154
155#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
156struct TransformCacheEntry {
157    input: FrozenProgram,
158    output: FrozenProgram,
159}
160
161/// Collision-safe cache used by the later graph compiler transform pipeline.
162#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
163pub(crate) struct SemanticTransformCache {
164    buckets: HashMap<TransformCacheKey, Vec<TransformCacheEntry>>,
165    len: usize,
166}
167
168#[allow(dead_code, reason = "wired into GraphCompiler in Phase 3 A3")]
169impl SemanticTransformCache {
170    pub(crate) fn new() -> Self {
171        Self {
172            buckets: HashMap::new(),
173            len: 0,
174        }
175    }
176
177    pub(crate) fn len(&self) -> usize {
178        self.len
179    }
180
181    pub(crate) fn apply(
182        &mut self,
183        input: &FrozenProgram,
184        transforms: &[&dyn SemanticTransform],
185    ) -> Result<FrozenProgram, SemanticTransformError> {
186        let key = TransformCacheKey {
187            input: input.program.semantic_fingerprint(),
188            ordered_transforms: transforms
189                .iter()
190                .map(|transform| transform.identity())
191                .collect(),
192        };
193        if let Some(output) = self.buckets.get(&key).and_then(|bucket| {
194            bucket.iter().find_map(|entry| {
195                (entry.input.program.semantic_eq(input.program.as_ref())
196                    && entry.input.bindings.cache_exact_eq(&input.bindings))
197                .then(|| entry.output.clone())
198            })
199        }) {
200            return Ok(output);
201        }
202
203        let output = apply_semantic_transforms(input, transforms)?;
204        self.buckets
205            .entry(key)
206            .or_default()
207            .push(TransformCacheEntry {
208                input: input.clone(),
209                output: output.clone(),
210            });
211        self.len += 1;
212        Ok(output)
213    }
214}