simfony/
debug.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use either::Either;
5use hashes::{sha256, Hash, HashEngine};
6use simplicity::{hashes, Cmr};
7
8use crate::error::Span;
9use crate::types::ResolvedType;
10use crate::value::{StructuralValue, Value};
11
12/// Tracker of Simfony call expressions inside Simplicity target code.
13///
14/// Tracking happens via CMRs that are inserted into the Simplicity target code.
15#[derive(Debug, Clone, PartialEq, Eq, Default)]
16pub struct DebugSymbols(HashMap<Cmr, TrackedCall>);
17
18/// Intermediate representation of tracked Simfony call expressions
19/// that is mutable and that lacks information about the source file.
20///
21/// The struct can be converted to [`DebugSymbols`] by providing the source file.
22#[derive(Debug, Clone, Eq, PartialEq, Default)]
23pub(crate) struct CallTracker {
24    next_id: u32,
25    map: HashMap<Span, (Cmr, TrackedCallName)>,
26}
27
28/// Call expression with a debug symbol.
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub struct TrackedCall {
31    text: Arc<str>,
32    name: TrackedCallName,
33}
34
35/// Name of a call expression with a debug symbol.
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum TrackedCallName {
38    Assert,
39    Panic,
40    Jet,
41    UnwrapLeft(ResolvedType),
42    UnwrapRight(ResolvedType),
43    Unwrap,
44    Debug(ResolvedType),
45}
46
47/// Fallible call expression with runtime input value.
48#[derive(Debug, Clone, Eq, PartialEq, Hash)]
49pub struct FallibleCall {
50    text: Arc<str>,
51    name: FallibleCallName,
52}
53
54/// Name of a fallible call expression with runtime input value.
55#[derive(Debug, Clone, Eq, PartialEq, Hash)]
56pub enum FallibleCallName {
57    Assert,
58    Panic,
59    Jet,
60    UnwrapLeft(Value),
61    UnwrapRight(Value),
62    Unwrap,
63}
64
65/// Debug expression with runtime input value.
66#[derive(Debug, Clone, Eq, PartialEq, Hash)]
67pub struct DebugValue {
68    text: Arc<str>,
69    value: Value,
70}
71
72impl DebugSymbols {
73    /// Insert a tracked call expression.
74    /// Use the Simfony source `file` to extract the Simfony text of the expression.
75    pub(crate) fn insert(&mut self, span: Span, cmr: Cmr, name: TrackedCallName, file: &str) {
76        let text = remove_excess_whitespace(span.to_slice(file).unwrap_or(""));
77        let text = text
78            .strip_prefix("dbg!(")
79            .and_then(|s| s.strip_suffix(")"))
80            .unwrap_or(&text);
81
82        self.0.insert(
83            cmr,
84            TrackedCall {
85                text: Arc::from(text),
86                name,
87            },
88        );
89    }
90
91    /// Check if the given CMR tracks any call expressions.
92    pub fn contains_key(&self, cmr: &Cmr) -> bool {
93        self.0.contains_key(cmr)
94    }
95
96    /// Get the call expression that is tracked by the given CMR.
97    pub fn get(&self, cmr: &Cmr) -> Option<&TrackedCall> {
98        self.0.get(cmr)
99    }
100}
101
102fn remove_excess_whitespace(s: &str) -> String {
103    let mut last_was_space = true;
104    let is_excess_whitespace = move |c: char| match c {
105        ' ' => std::mem::replace(&mut last_was_space, true),
106        '\n' => true,
107        _ => {
108            last_was_space = false;
109            false
110        }
111    };
112    s.replace(is_excess_whitespace, "")
113}
114
115impl CallTracker {
116    /// Track a new function call with the given `span`.
117    ///
118    /// ## Precondition
119    ///
120    /// Different function calls have different spans.
121    ///
122    /// This holds true when the method is called on a real source file.
123    /// The precondition might be broken when this method is called on random input.
124    pub fn track_call(&mut self, span: Span, name: TrackedCallName) {
125        let cmr = self.next_id_cmr();
126        let _replaced = self.map.insert(span, (cmr, name));
127        self.next_id += 1;
128    }
129
130    /// Get the CMR of the tracked function call with the given `span`.
131    pub fn get_cmr(&self, span: &Span) -> Option<Cmr> {
132        self.map.get(span).map(|x| x.0)
133    }
134
135    fn next_id_cmr(&self) -> Cmr {
136        let tag_hash = sha256::Hash::hash(b"simfony\x1fdebug\x1f");
137        let mut engine = sha256::Hash::engine();
138        engine.input(tag_hash.as_ref());
139        engine.input(tag_hash.as_ref());
140        engine.input(self.next_id.to_be_bytes().as_ref());
141        Cmr::from_byte_array(sha256::Hash::from_engine(engine).to_byte_array())
142    }
143
144    /// Create debug symbols by attaching information from the source `file`.
145    pub fn with_file(&self, file: &str) -> DebugSymbols {
146        let mut debug_symbols = DebugSymbols::default();
147        for (span, (cmr, name)) in &self.map {
148            debug_symbols.insert(*span, *cmr, name.clone(), file);
149        }
150        debug_symbols
151    }
152}
153
154impl TrackedCall {
155    /// Access the text of the Simfony call expression.
156    pub fn text(&self) -> &str {
157        &self.text
158    }
159
160    /// Access the name of the call.
161    pub fn name(&self) -> &TrackedCallName {
162        &self.name
163    }
164
165    /// Supply the Simplicity input value of the call expression at runtime.
166    /// Convert the debug call into a fallible call or into a debug value,
167    /// depending on the kind of debug symbol.
168    ///
169    /// Return `None` if the Simplicity input value is of the wrong type,
170    /// according to the debug symbol.
171    pub fn map_value(&self, value: &StructuralValue) -> Option<Either<FallibleCall, DebugValue>> {
172        let name = match self.name() {
173            TrackedCallName::Assert => FallibleCallName::Assert,
174            TrackedCallName::Panic => FallibleCallName::Panic,
175            TrackedCallName::Jet => FallibleCallName::Jet,
176            TrackedCallName::UnwrapLeft(ty) => {
177                Value::reconstruct(value, ty).map(FallibleCallName::UnwrapLeft)?
178            }
179            TrackedCallName::UnwrapRight(ty) => {
180                Value::reconstruct(value, ty).map(FallibleCallName::UnwrapRight)?
181            }
182            TrackedCallName::Unwrap => FallibleCallName::Unwrap,
183            TrackedCallName::Debug(ty) => {
184                return Value::reconstruct(value, ty)
185                    .map(|value| DebugValue {
186                        text: Arc::clone(&self.text),
187                        value,
188                    })
189                    .map(Either::Right)
190            }
191        };
192        Some(Either::Left(FallibleCall {
193            text: Arc::clone(&self.text),
194            name,
195        }))
196    }
197}
198
199impl FallibleCall {
200    /// Access the Simfony text of the call expression.
201    pub fn text(&self) -> &str {
202        &self.text
203    }
204
205    /// Access the name of the call.
206    pub fn name(&self) -> &FallibleCallName {
207        &self.name
208    }
209}
210
211impl DebugValue {
212    /// Access the Simfony text of the debug expression.
213    pub fn text(&self) -> &str {
214        &self.text
215    }
216
217    /// Access the runtime input value.
218    pub fn value(&self) -> &Value {
219        &self.value
220    }
221}