Skip to main content

hidane_rules/eval/
trace.rs

1//! What a decision reports besides the decision: the values `debug()` received and, for the
2//! coverage report (`crate::coverage`), what each expression evaluated to.
3
4use std::hash::{Hash, Hasher};
5
6use super::{
7    Timestamp,
8    constraint::Comparator,
9    value::{Error, NANOS, Val},
10};
11use crate::{
12    ast::Expr,
13    diag::Position,
14    paths::{UNKNOWN_ID, UNKNOWN_RUN},
15};
16
17/// A value as `debug()` logs it and the coverage report counts it: the official emulator's
18/// value message, which also holds what only rules compute (sets, map diffs, constraints) and
19/// the path segments a list does not know.
20#[derive(Debug, Clone)]
21pub enum Observed {
22    Null,
23    Bool(bool),
24    Int(i64),
25    Float(f64),
26    String(String),
27    Bytes(Vec<u8>),
28    Timestamp(Timestamp),
29    /// Seconds and nanoseconds, both of the duration's sign.
30    Duration(i64, i32),
31    /// Latitude and longitude.
32    LatLng(f64, f64),
33    Path(Vec<Segment>),
34    /// In the map's order.
35    Map(Vec<(String, Observed)>),
36    List(Vec<Observed>),
37    Set(Vec<Observed>),
38    /// Every key of the two maps: the other map's, in its order, then those only in this one.
39    MapDiff(Vec<(String, KeyStatus)>),
40    /// What a list knows of a field: each filter's comparator and value.
41    Constraint(Vec<(Comparator, Observed)>),
42}
43
44/// A segment of a path: a name, or what a list does not know, one document ID (`capture`) or
45/// any number of segments (`glob_capture`).
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub enum Segment {
48    Simple(String),
49    Capture,
50    GlobCapture,
51}
52
53/// What `a.diff(b)` says of a key.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum KeyStatus {
56    /// Only in `a`.
57    Added,
58    /// Only in `b`.
59    Removed,
60    Changed,
61    Unchanged,
62}
63
64impl KeyStatus {
65    /// The official name: `ADDED`, `REMOVED`, `CHANGED` or `UNCHANGED`.
66    pub fn name(self) -> &'static str {
67        match self {
68            Self::Added => "ADDED",
69            Self::Removed => "REMOVED",
70            Self::Changed => "CHANGED",
71            Self::Unchanged => "UNCHANGED",
72        }
73    }
74}
75
76/// Values are the same as the official emulator's messages are: floats by their bits (every
77/// NaN alike, `-0.0` apart from `0.0`), maps and lists in their order.
78impl PartialEq for Observed {
79    fn eq(&self, other: &Self) -> bool {
80        match (self, other) {
81            (Self::Null, Self::Null) => true,
82            (Self::Bool(a), Self::Bool(b)) => a == b,
83            (Self::Int(a), Self::Int(b)) => a == b,
84            (Self::Float(a), Self::Float(b)) => bits(*a) == bits(*b),
85            (Self::String(a), Self::String(b)) => a == b,
86            (Self::Bytes(a), Self::Bytes(b)) => a == b,
87            (Self::Timestamp(a), Self::Timestamp(b)) => a == b,
88            (Self::Duration(a, b), Self::Duration(c, d)) => a == c && b == d,
89            (Self::LatLng(a, b), Self::LatLng(c, d)) => {
90                bits(*a) == bits(*c) && bits(*b) == bits(*d)
91            }
92            (Self::Path(a), Self::Path(b)) => a == b,
93            (Self::Map(a), Self::Map(b)) => a == b,
94            (Self::List(a), Self::List(b)) | (Self::Set(a), Self::Set(b)) => a == b,
95            (Self::MapDiff(a), Self::MapDiff(b)) => a == b,
96            (Self::Constraint(a), Self::Constraint(b)) => a == b,
97            _ => false,
98        }
99    }
100}
101
102impl Eq for Observed {}
103
104fn bits(f: f64) -> u64 {
105    if f.is_nan() {
106        f64::NAN.to_bits()
107    } else {
108        f.to_bits()
109    }
110}
111
112/// Hashes `v` as what it shows ([`observe`]), without building that.
113pub(crate) fn hash_val<H: Hasher>(v: &Val, state: &mut H) {
114    match v {
115        Val::Null | Val::Unknown => 0u8.hash(state),
116        Val::Bool(b) => (1u8, b).hash(state),
117        Val::Int(i) => (2u8, i).hash(state),
118        Val::Float(f) => (3u8, bits(*f)).hash(state),
119        Val::Str(s) => (4u8, &**s).hash(state),
120        Val::Bytes(b) => (5u8, &**b).hash(state),
121        Val::Timestamp(seconds, nanos) => (6u8, seconds, nanos).hash(state),
122        Val::Duration(nanos) => (7u8, nanos).hash(state),
123        Val::LatLng(lat, lng) => (8u8, bits(*lat), bits(*lng)).hash(state),
124        Val::Path(segments) => (9u8, &**segments).hash(state),
125        Val::Map(map) => {
126            (10u8, map.len()).hash(state);
127            for (k, v) in map.iter() {
128                k.hash(state);
129                hash_val(v, state);
130            }
131        }
132        Val::List(items) | Val::Set(items) => {
133            (
134                if matches!(v, Val::Set(_)) { 12u8 } else { 11u8 },
135                items.len(),
136            )
137                .hash(state);
138            for item in items.iter() {
139                hash_val(item, state);
140            }
141        }
142        Val::MapDiff(diff) => (13u8, &diff.statuses).hash(state),
143        Val::Constraint(simple) => {
144            (14u8, simple.len()).hash(state);
145            for s in simple.iter() {
146                s.comparator.hash(state);
147                hash_val(&s.value, state);
148            }
149        }
150    }
151}
152
153/// Whether `o` is what `v` shows ([`observe`]).
154pub(crate) fn shows(o: &Observed, v: &Val) -> bool {
155    match (o, v) {
156        (Observed::Null, Val::Null | Val::Unknown) => true,
157        (Observed::Bool(a), Val::Bool(b)) => a == b,
158        (Observed::Int(a), Val::Int(b)) => a == b,
159        (Observed::Float(a), Val::Float(b)) => bits(*a) == bits(*b),
160        (Observed::String(a), Val::Str(b)) => a.as_str() == &**b,
161        (Observed::Bytes(a), Val::Bytes(b)) => a.as_slice() == &**b,
162        (Observed::Timestamp(t), Val::Timestamp(seconds, nanos)) => {
163            t.seconds == *seconds && i64::from(t.nanos) == i64::from(*nanos)
164        }
165        (Observed::Duration(..), Val::Duration(_)) | (Observed::Path(_), Val::Path(_)) => {
166            *o == observe(v)
167        }
168        (Observed::LatLng(a, b), Val::LatLng(c, d)) => bits(*a) == bits(*c) && bits(*b) == bits(*d),
169        (Observed::Map(a), Val::Map(b)) => {
170            a.len() == b.len()
171                && a.iter()
172                    .zip(b.iter())
173                    .all(|((k, x), (l, y))| k == l && shows(x, y))
174        }
175        (Observed::List(a), Val::List(b)) | (Observed::Set(a), Val::Set(b)) => {
176            a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| shows(x, y))
177        }
178        (Observed::MapDiff(a), Val::MapDiff(b)) => *a == b.statuses,
179        (Observed::Constraint(a), Val::Constraint(b)) => {
180            a.len() == b.len()
181                && a.iter()
182                    .zip(b.iter())
183                    .all(|((c, x), s)| *c == s.comparator && shows(x, &s.value))
184        }
185        _ => false,
186    }
187}
188
189/// What an expression of the coverage report evaluated to once.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub enum Outcome {
192    Value(Observed),
193    /// An evaluation error: where the official emulator places it, and its text. A comparison
194    /// a list's filter does not decide has neither.
195    Error {
196        position: Option<Position>,
197        message: String,
198    },
199    /// Not evaluated: the other side of `&&` or `||` decided, or `?:` took the other branch.
200    Skipped,
201}
202
203/// Which expression of the coverage report a value belongs to: an expression by its byte range,
204/// or the body of a function from one of its `let`s (by the keyword's offset).
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
206pub(crate) enum NodeKey {
207    Expr(usize, usize),
208    Body(usize),
209}
210
211impl NodeKey {
212    pub fn of(e: &Expr) -> Self {
213        Self::Expr(e.span.start, e.span.end)
214    }
215}
216
217/// An expression's value as the evaluator had it.
218#[derive(Clone)]
219pub(crate) enum Traced {
220    Value(Val),
221    Error(Error),
222    Skipped,
223}
224
225/// What one decision reports besides the decision: the value of each `debug()` call, in order,
226/// and, when values are traced ([`Trace::with_values`]), what each expression of the coverage
227/// report evaluated to, for [`crate::coverage::Coverage::record`].
228#[derive(Default)]
229pub struct Trace {
230    pub(crate) debug: Vec<Observed>,
231    pub(crate) values: Option<Vec<(NodeKey, Traced)>>,
232}
233
234impl Trace {
235    /// A trace of `debug()` alone.
236    pub fn new() -> Self {
237        Self::default()
238    }
239
240    /// A trace of `debug()` and of every expression's value.
241    pub fn with_values() -> Self {
242        Self {
243            debug: Vec::new(),
244            values: Some(Vec::new()),
245        }
246    }
247
248    /// The values `debug()` received, in order.
249    pub fn debug(&self) -> &[Observed] {
250        &self.debug
251    }
252
253    pub(crate) fn tracing(&self) -> bool {
254        self.values.is_some()
255    }
256
257    pub(crate) fn record(&mut self, key: NodeKey, result: &Result<Val, Error>) {
258        if let Some(values) = &mut self.values {
259            values.push((
260                key,
261                match result {
262                    Ok(v) => Traced::Value(v.clone()),
263                    Err(e) => Traced::Error(e.clone()),
264                },
265            ));
266        }
267    }
268
269    pub(crate) fn skip(&mut self, key: NodeKey) {
270        if let Some(values) = &mut self.values {
271            values.push((key, Traced::Skipped));
272        }
273    }
274
275    /// Where the values traced next start.
276    pub(crate) fn mark(&self) -> usize {
277        self.values.as_ref().map_or(0, Vec::len)
278    }
279
280    /// Counts what each statement traced again for each statement evaluated after it, as the
281    /// official emulator's report counts them: as if it evaluated a growing `||` of the
282    /// statements, the first alone, then the first two, and so on (but it runs each `debug()`
283    /// once). `marks` holds where each statement's values start, in order.
284    pub(crate) fn repeat(&mut self, marks: &[usize]) {
285        let Some(values) = &mut self.values else {
286            return;
287        };
288        let end = values.len();
289        for (i, &start) in marks.iter().enumerate() {
290            let stop = marks.get(i + 1).copied().unwrap_or(end);
291            for _ in i + 1..marks.len() {
292                for j in start..stop {
293                    let again = values[j].clone();
294                    values.push(again);
295                }
296            }
297        }
298    }
299}
300
301/// A value as `debug()` and the coverage report show it.
302pub(crate) fn observe(v: &Val) -> Observed {
303    match v {
304        // What reading it would leave undecided only shows inside a map.
305        Val::Null | Val::Unknown => Observed::Null,
306        Val::Bool(b) => Observed::Bool(*b),
307        Val::Int(i) => Observed::Int(*i),
308        Val::Float(f) => Observed::Float(*f),
309        Val::Str(s) => Observed::String(s.to_string()),
310        Val::Bytes(b) => Observed::Bytes(b.to_vec()),
311        Val::List(items) => Observed::List(items.iter().map(observe).collect()),
312        Val::Set(items) => Observed::Set(items.iter().map(observe).collect()),
313        Val::Map(map) => Observed::Map(map.iter().map(|(k, v)| (k.clone(), observe(v))).collect()),
314        Val::MapDiff(diff) => Observed::MapDiff(diff.statuses.clone()),
315        Val::Timestamp(seconds, nanos) => Observed::Timestamp(Timestamp {
316            seconds: *seconds,
317            nanos: i32::try_from(*nanos).unwrap_or(0),
318        }),
319        Val::Duration(nanos) => Observed::Duration(
320            i64::try_from(nanos / NANOS).unwrap_or(0),
321            i32::try_from(nanos % NANOS).unwrap_or(0),
322        ),
323        Val::LatLng(lat, lng) => Observed::LatLng(*lat, *lng),
324        Val::Path(segments) => Observed::Path(
325            segments
326                .iter()
327                .map(|s| match s.as_str() {
328                    UNKNOWN_ID => Segment::Capture,
329                    UNKNOWN_RUN => Segment::GlobCapture,
330                    s => Segment::Simple(s.to_owned()),
331                })
332                .collect(),
333        ),
334        Val::Constraint(simple) => Observed::Constraint(
335            simple
336                .iter()
337                .map(|s| (s.comparator, observe(&s.value)))
338                .collect(),
339        ),
340    }
341}