simplicityhl_core/trackers/
default_tracker.rs

1//! Default execution tracker with configurable sinks for debug and jet tracing.
2//!
3//! Intended for embedding in runners; avoids panics and exposes structured hooks
4//! for observing program execution.
5
6use simplicityhl::either::Either;
7
8use anyhow::{Context, Result, anyhow};
9
10use simplicityhl::debug::DebugSymbols;
11use simplicityhl::jet::{source_type, target_type};
12use simplicityhl::str::AliasName;
13use simplicityhl::types::AliasedType;
14use simplicityhl::value::StructuralValue;
15use simplicityhl::{ResolvedType, Value};
16
17use simplicityhl::simplicity::bit_machine::ExecTracker;
18use simplicityhl::simplicity::ffi::ffi::UWORD;
19use simplicityhl::simplicity::jet::type_name::TypeName;
20use simplicityhl::simplicity::jet::{Elements, Jet};
21use simplicityhl::simplicity::{BitIter, Cmr, Value as SimValue, ValueRef};
22
23type TrackerDebugSink<'a> = Box<dyn FnMut(&str, &dyn core::fmt::Display) + 'a>;
24type TrackerJetTraceSink<'a> = Box<dyn FnMut(&Elements, &[Value], &Value) + 'a>;
25
26fn default_debug_sink(label: &str, value: &dyn core::fmt::Display) {
27    println!("DBG: {} = {}", label, value);
28}
29
30fn default_jet_trace_sink(jet: &Elements, args: &[Value], result: &Value) {
31    print!("{:?}(", jet);
32    for (i, a) in args.iter().enumerate() {
33        if i > 0 {
34            print!(", ");
35        }
36        print!("{}", a);
37    }
38    println!(") = {}", result);
39}
40
41/// Tracks Simplicity execution events and forwards them to configurable sinks.
42///
43/// This tracker is designed to be embedded in higher-level runners. It avoids
44/// printing or panicking on parse errors. Consumers can opt-in to receive
45/// structured debug events and jet traces via builder methods.
46pub struct DefaultTracker<'a> {
47    debug_symbols: &'a DebugSymbols,
48    debug_sink: Option<TrackerDebugSink<'a>>, // (label, value)
49    jet_trace_sink: Option<TrackerJetTraceSink<'a>>, // (jet, args, result)
50}
51
52impl<'a> DefaultTracker<'a> {
53    /// Create a new tracker bound to the provided debug symbol table.
54    pub fn new(debug_symbols: &'a DebugSymbols) -> Self {
55        Self {
56            debug_symbols,
57            debug_sink: None,
58            jet_trace_sink: None,
59        }
60    }
61
62    /// Enable forwarding of debug!() calls to the provided sink.
63    pub fn with_debug_sink<F>(mut self, sink: F) -> Self
64    where
65        F: FnMut(&str, &dyn core::fmt::Display) + 'a,
66    {
67        self.debug_sink = Some(Box::new(sink));
68        Self { ..self }
69    }
70
71    /// Enable the default debug!() sink that prints to stdout.
72    pub fn with_default_debug_sink(self) -> Self {
73        self.with_debug_sink(default_debug_sink)
74    }
75
76    /// Enable forwarding of jet call traces to the provided sink.
77    pub fn with_jet_trace_sink<F>(mut self, sink: F) -> Self
78    where
79        F: FnMut(&Elements, &[Value], &Value) + 'a,
80    {
81        self.jet_trace_sink = Some(Box::new(sink));
82        Self { ..self }
83    }
84
85    /// Enable the default jet trace sink that prints to stdout.
86    pub fn with_default_jet_trace_sink(self) -> Self {
87        self.with_jet_trace_sink(default_jet_trace_sink)
88    }
89}
90
91impl<'a> ExecTracker<Elements> for DefaultTracker<'a> {
92    fn track_left(&mut self, _: simplicityhl::simplicity::Ihr) {}
93
94    fn track_right(&mut self, _: simplicityhl::simplicity::Ihr) {}
95
96    fn track_jet_call(
97        &mut self,
98        jet: &Elements,
99        input_buffer: &[UWORD],
100        output_buffer: &[UWORD],
101        _: bool,
102    ) {
103        if let Some(sink) = self.jet_trace_sink.as_mut()
104            && let (Ok(args), Ok(result)) = (
105                parse_args(jet, input_buffer),
106                parse_result(jet, output_buffer),
107            )
108        {
109            sink(jet, &args, &result);
110        }
111    }
112
113    fn track_dbg_call(&mut self, cmr: &Cmr, value: simplicityhl::simplicity::Value) {
114        if let Some(sink) = self.debug_sink.as_mut()
115            && let Some(tracked_call) = self.debug_symbols.get(cmr)
116            && let Some(Either::Right(debug_value)) =
117                tracked_call.map_value(&StructuralValue::from(value))
118        {
119            sink(debug_value.text(), &debug_value.value());
120        }
121    }
122
123    fn is_track_debug_enabled(&self) -> bool {
124        self.debug_sink.is_some()
125    }
126}
127
128/// Converts an array of words into a bit iterator.
129/// Bits are reversed.
130fn words_into_bit_iter(words: &[UWORD]) -> BitIter<std::vec::IntoIter<u8>> {
131    let bytes_per_word = std::mem::size_of::<UWORD>();
132    let mut bytes = Vec::with_capacity(std::mem::size_of_val(words));
133    for word in words.iter().rev() {
134        for i in 0..bytes_per_word {
135            let byte: u8 = ((word >> ((bytes_per_word - i - 1) * 8)) & 0xFF) as u8;
136            bytes.push(byte);
137        }
138    }
139    BitIter::from(bytes.into_iter())
140}
141
142/// Converts an aliased type to a resolved type.
143fn resolve_type(aliased_type: &AliasedType) -> Result<ResolvedType> {
144    let get_alias = |_: &AliasName| -> Option<ResolvedType> { None };
145    aliased_type
146        .resolve(get_alias)
147        .map_err(|alias| anyhow!("unexpected alias: {}", alias))
148}
149
150/// Traverses a product and collects the arguments.
151fn collect_args(node: ValueRef, num_args: usize, args: &mut Vec<SimValue>) -> Result<()> {
152    if num_args == 0 {
153        return Ok(());
154    }
155    if num_args == 1 {
156        args.push(node.to_value());
157        Ok(())
158    } else if let Some((left, right)) = node.as_product() {
159        args.push(left.to_value());
160        collect_args(right, num_args - 1, args)
161    } else {
162        Err(anyhow!(
163            "unexpected value structure while collecting arguments"
164        ))
165    }
166}
167
168/// Parses a SimValue from an array of words.
169fn parse_sim_value(words: &[UWORD], type_name: TypeName) -> Result<SimValue> {
170    let sim_type = type_name.to_final();
171    let mut bit_iter = words_into_bit_iter(words);
172    let sim_value = SimValue::from_padded_bits(&mut bit_iter, &sim_type)
173        .context("failed to decode Simplicity value from padded bits")?;
174    // Ensure the iterator is closed; ignore any trailing-bit discrepancies.
175    let _ = bit_iter.close();
176    Ok(sim_value)
177}
178
179/// Parses a Simf value from a Simplicity value.
180fn parse_simf_value(sim_value: SimValue, aliased_type: &AliasedType) -> Result<Value> {
181    let resolved_type = resolve_type(aliased_type)?;
182    let value = Value::reconstruct(&sim_value.into(), &resolved_type)
183        .ok_or_else(|| anyhow!("failed to reconstruct high-level value"))?;
184    Ok(value)
185}
186
187/// Parses the arguments of a jet call.
188fn parse_args(jet: &Elements, words: &[UWORD]) -> Result<Vec<Value>> {
189    let simf_types = source_type(*jet);
190    if simf_types.is_empty() {
191        return Ok(vec![]);
192    }
193
194    let sim_value = parse_sim_value(words, jet.source_ty())?;
195
196    let mut args = Vec::with_capacity(simf_types.len());
197    collect_args(sim_value.as_ref(), simf_types.len(), &mut args)?;
198
199    args.into_iter()
200        .zip(simf_types.iter())
201        .map(|(arg, ty)| parse_simf_value(arg, ty))
202        .collect()
203}
204
205/// Parses the result of a jet call.
206fn parse_result(jet: &Elements, words: &[UWORD]) -> Result<Value> {
207    let simf_type = target_type(*jet);
208    let sim_value = parse_sim_value(words, jet.target_ty())?;
209    parse_simf_value(sim_value, &simf_type)
210}