arena_terms/
view.rs

1//! Defines [`View`], a borrowed read-only representation of a [`Term`].
2//!
3//! Provides lightweight accessors for inspecting terms without allocation.
4
5use crate::{Arena, Handle, Term, TermError};
6use core::fmt;
7use std::cmp::Ordering;
8
9impl fmt::Debug for View<'_> {
10    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11        match &self {
12            View::Int(i) => f.debug_tuple("Int").field(&i).finish(),
13            View::Real(r) => f.debug_tuple("Real").field(&r).finish(),
14            View::Date(d) => f.debug_tuple("Date").field(&d).finish(),
15            View::Var(v) => f.debug_tuple("Var").field(&v).finish(),
16            View::Atom(a) => f.debug_tuple("Atom").field(&a).finish(),
17            View::Str(s) => f.debug_tuple("Str").field(&s).finish(),
18            View::Bin(b) => f.debug_tuple("Bin").field(&b).finish(),
19            View::Func(a, fr, ts) => f
20                .debug_tuple("Func")
21                .field(&a.arena_id)
22                .field(&fr)
23                .field(&ts.iter().map(|t| t.view(a)).collect::<Vec<_>>())
24                .finish(),
25            View::List(a, ts, tail) => f
26                .debug_tuple("List")
27                .field(&a.arena_id)
28                .field(&ts.iter().map(|t| t.view(a)).collect::<Vec<_>>())
29                .field(&tail.view(a))
30                .finish(),
31            View::Tuple(a, ts) => f
32                .debug_tuple("Tuple")
33                .field(&a.arena_id)
34                .field(&ts.iter().map(|t| t.view(a)).collect::<Vec<_>>())
35                .finish(),
36        }
37    }
38}
39
40/// A borrowed view into the interned contents of a [`Term`].
41///
42/// Use [`Term::view`] to obtain a view.  Each variant of [`View`]
43/// represents the decoded form of a term and borrows any data
44/// referenced from the [`Arena`] or the term handle itself.  No
45/// allocations are performed when constructing a `View`; instead
46/// references into the underlying storage are returned directly.  The
47/// lifetime `'a` binds the returned references to both the borrowed
48/// `Term` and the supplied `Arena`.
49#[derive(Clone, Copy)]
50pub enum View<'a> {
51    /// An integer value.
52    Int(i64),
53    /// A floating point value.
54    Real(f64),
55    /// A date value (milliseconds since the Unix epoch).
56    Date(i64),
57    /// A variable name borrowed from the term or arena.
58    Var(&'a str),
59    /// An atom name borrowed from the term or arena.
60    Atom(&'a str),
61    /// A UTF‑8 string borrowed from the term or arena.
62    Str(&'a str),
63    /// A binary slice borrowed from the term or arena.
64    Bin(&'a [u8]),
65    /// A compound term view containing the functor name and a slice
66    /// of arguments.  Both the functor and the argument slice are
67    /// borrowed; the arguments themselves are `Term` handles owned
68    /// by the arena.
69    Func(&'a Arena, &'a Term, &'a [Term]),
70    /// A list view containing a slice of the list elements
71    /// and a reference to the tail term. The element slice and the tail are
72    /// borrowed; the elements themselves are `Term` handles owned by the arena.
73    /// The tail of a proper list will always reference Term::NIL.
74    List(&'a Arena, &'a [Term], &'a Term),
75    /// A tuple view containing a slice of the tuple elements.
76    /// The element slice are borrowed; the elements
77    /// themselves are `Term` handles owned by the arena.
78    Tuple(&'a Arena, &'a [Term]),
79}
80
81impl Term {
82    /// Produce a [`View`] of this term that borrows from the given
83    /// [`Arena`].  This method decodes any inlined bytes and
84    /// dereferences indexes into the arena to yield structured
85    /// references.  See [`View`] for details.
86    #[inline]
87    pub fn view<'a>(&'a self, arena: &'a Arena) -> Result<View<'a>, TermError> {
88        match &self.0 {
89            Handle::Int(i) => Ok(View::Int(*i)),
90            Handle::Real(f) => Ok(View::Real(*f)),
91            Handle::Date(d) => Ok(View::Date(*d)),
92            Handle::Var(vs) => {
93                let s_bytes = &vs.bytes[..vs.len as usize];
94                let s = unsafe { core::str::from_utf8_unchecked(s_bytes) };
95                Ok(View::Var(s))
96            }
97            Handle::VarRef(vr) => Ok(View::Var(unsafe {
98                core::str::from_utf8_unchecked(
99                    arena
100                        .byte_slice(vr)
101                        .map_err(|_| TermError::InvalidTerm(*self))?,
102                )
103            })),
104            Handle::Atom(a) => {
105                let s_bytes = &a.bytes[..a.len as usize];
106                let s = unsafe { core::str::from_utf8_unchecked(s_bytes) };
107                Ok(View::Atom(s))
108            }
109            Handle::AtomRef(ar) => Ok(View::Atom(unsafe {
110                core::str::from_utf8_unchecked(
111                    arena
112                        .byte_slice(ar)
113                        .map_err(|_| TermError::InvalidTerm(*self))?,
114                )
115            })),
116            Handle::Str(ss) => {
117                let s_bytes = &ss.bytes[..ss.len as usize];
118                let s = unsafe { core::str::from_utf8_unchecked(s_bytes) };
119                Ok(View::Str(s))
120            }
121            Handle::StrRef(sr) => Ok(View::Str(unsafe {
122                core::str::from_utf8_unchecked(
123                    arena
124                        .byte_slice(sr)
125                        .map_err(|_| TermError::InvalidTerm(*self))?,
126                )
127            })),
128            Handle::Bin(bs) => {
129                let b = &bs.bytes[..bs.len as usize];
130                Ok(View::Bin(b))
131            }
132            Handle::BinRef(br) => Ok(View::Bin(
133                arena
134                    .byte_slice(br)
135                    .map_err(|_| TermError::InvalidTerm(*self))?,
136            )),
137            Handle::FuncRef(fr) => {
138                let slice = arena
139                    .term_slice(fr)
140                    .map_err(|_| TermError::InvalidTerm(*self))?;
141                // Functor is the first element of the slice
142                let functor = &slice[0];
143                let args = &slice[1..];
144                Ok(View::Func(arena, functor, args))
145            }
146            Handle::ListRef(lr) => {
147                let slice = arena
148                    .term_slice(lr)
149                    .map_err(|_| TermError::InvalidTerm(*self))?;
150                Ok(View::List(arena, slice, &Term::NIL))
151            }
152            Handle::ListCRef(lr) => {
153                let slice = arena
154                    .term_slice(lr)
155                    .map_err(|_| TermError::InvalidTerm(*self))?;
156                let last = slice.len() - 1;
157                Ok(View::List(arena, &slice[..last], &slice[last]))
158            }
159            Handle::TupleRef(tr) => {
160                let slice = arena
161                    .term_slice(tr)
162                    .map_err(|_| TermError::InvalidTerm(*self))?;
163                Ok(View::Tuple(arena, slice))
164            }
165        }
166    }
167}
168
169impl Arena {
170    /// Produce a [`View`] of the given `term` that borrows from
171    /// this [`Arena`].  This method decodes any inlined bytes and
172    /// dereferences indexes into the arena to yield structured
173    /// references.  See [`View`] for details.
174    #[inline]
175    pub fn view<'a>(&'a self, term: &'a Term) -> Result<View<'a>, TermError> {
176        term.view(self)
177    }
178}
179
180impl<'a> PartialEq for View<'a> {
181    fn eq(&self, other: &Self) -> bool {
182        let order_a = kind_order(self);
183        let order_b = kind_order(other);
184        if order_a != order_b {
185            return false;
186        }
187        match (self, other) {
188            // Numbers: compare by numeric value irrespective of the exact type.
189            (
190                View::Int(_) | View::Real(_) | View::Date(_),
191                View::Int(_) | View::Real(_) | View::Date(_),
192            ) => {
193                let a = numeric_value(self);
194                let b = numeric_value(other);
195                a == b
196            }
197            // Variables
198            (View::Var(a), View::Var(b)) => a == b,
199            // Atoms
200            (View::Atom(a), View::Atom(b)) => a == b,
201            // Strings
202            (View::Str(a), View::Str(b)) => a == b,
203            // Binaries
204            (View::Bin(a), View::Bin(b)) => a == b,
205            // Compounds: compare by length (arity+1) then by slice index.
206            (View::Func(arena_a, functor_a, args_a), View::Func(arena_b, functor_b, args_b)) => {
207                if args_a.len() != args_b.len() {
208                    return false;
209                }
210                if functor_a != functor_b {
211                    return false;
212                }
213                args_a.iter().zip(args_b.iter()).all(|(a, b)| {
214                    a.view(arena_a).expect("arena mismatch")
215                        == b.view(arena_b).expect("arena mismatch")
216                })
217            }
218            (View::List(arena_a, args_a, tail_a), View::List(arena_b, args_b, tail_b)) => {
219                if args_a.len() != args_b.len() {
220                    return false;
221                }
222                args_a.iter().zip(args_b.iter()).all(|(a, b)| {
223                    a.view(arena_a).expect("arena mismatch")
224                        == b.view(arena_b).expect("arena mismatch")
225                }) && tail_a.view(arena_a).expect("arena mismatch")
226                    == tail_b.view(arena_b).expect("arena mismatch")
227            }
228            (View::Tuple(arena_a, args_a), View::Tuple(arena_b, args_b)) => {
229                if args_a.len() != args_b.len() {
230                    return false;
231                }
232                args_a.iter().zip(args_b.iter()).all(|(a, b)| {
233                    a.view(arena_a).expect("arena mismatch")
234                        == b.view(arena_b).expect("arena mismatch")
235                })
236            }
237            _ => unreachable!(),
238        }
239    }
240}
241
242impl<'a> Eq for View<'a> {}
243
244impl core::cmp::PartialOrd for View<'_> {
245    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
246        Some(self.cmp(other))
247    }
248}
249
250impl core::cmp::Ord for View<'_> {
251    fn cmp(&self, other: &Self) -> Ordering {
252        let order_a = kind_order(self);
253        let order_b = kind_order(other);
254        if order_a != order_b {
255            return order_a.cmp(&order_b);
256        }
257        match (self, other) {
258            // Numbers: compare by numeric value irrespective of the exact type.
259            (
260                View::Int(_) | View::Real(_) | View::Date(_),
261                View::Int(_) | View::Real(_) | View::Date(_),
262            ) => {
263                let a = numeric_value(self);
264                let b = numeric_value(other);
265                a.total_cmp(&b)
266            }
267            // Variables
268            (View::Var(a), View::Var(b)) => a.cmp(b),
269            // Atoms
270            (View::Atom(a), View::Atom(b)) => a.cmp(b),
271            // Strings
272            (View::Str(a), View::Str(b)) => a.cmp(b),
273            // Binaries
274            (View::Bin(a), View::Bin(b)) => a.cmp(b),
275            // Compounds: compare by length (arity+1) then by slice index.
276            (View::Func(arena_a, functor_a, args_a), View::Func(arena_b, functor_b, args_b)) => {
277                let ord = args_a.len().cmp(&args_b.len());
278                if ord != Ordering::Equal {
279                    return ord;
280                }
281                let ord = functor_a
282                    .view(arena_a)
283                    .expect("arena mismatch")
284                    .cmp(&functor_b.view(arena_b).expect("arena mismatch"));
285                if ord != Ordering::Equal {
286                    return ord;
287                }
288                for (arg_a, arg_b) in args_a.iter().zip(args_b.iter()).map(|(a, b)| {
289                    (
290                        a.view(arena_a).expect("arena mismatch"),
291                        b.view(arena_b).expect("arena mismatch"),
292                    )
293                }) {
294                    let ord = arg_a.cmp(&arg_b);
295                    if ord != Ordering::Equal {
296                        return ord;
297                    }
298                }
299                Ordering::Equal
300            }
301            (View::List(arena_a, args_a, tail_a), View::List(arena_b, args_b, tail_b)) => {
302                let ord = args_a.len().cmp(&args_b.len());
303                if ord != Ordering::Equal {
304                    return ord;
305                }
306                for (arg_a, arg_b) in args_a.iter().zip(args_b.iter()).map(|(a, b)| {
307                    (
308                        a.view(arena_a).expect("arena mismatch"),
309                        b.view(arena_b).expect("arena mismatch"),
310                    )
311                }) {
312                    let ord = arg_a.cmp(&arg_b);
313                    if ord != Ordering::Equal {
314                        return ord;
315                    }
316                }
317                tail_a
318                    .view(arena_a)
319                    .expect("arena mismatch")
320                    .cmp(&tail_b.view(arena_b).expect("arena mismatch"))
321            }
322            (View::Tuple(arena_a, args_a), View::Tuple(arena_b, args_b)) => {
323                let ord = args_a.len().cmp(&args_b.len());
324                if ord != Ordering::Equal {
325                    return ord;
326                }
327                for (arg_a, arg_b) in args_a.iter().zip(args_b.iter()).map(|(a, b)| {
328                    (
329                        a.view(arena_a).expect("arena mismatch"),
330                        b.view(arena_b).expect("arena mismatch"),
331                    )
332                }) {
333                    let ord = arg_a.cmp(&arg_b);
334                    if ord != Ordering::Equal {
335                        return ord;
336                    }
337                }
338                Ordering::Equal
339            }
340
341            _ => unreachable!(),
342        }
343    }
344}
345
346/// Compute the kind order used for comparing terms of different kinds.
347/// According to Prolog standard order: variables < numbers < atoms < strings
348/// < binaries < compounds.
349fn kind_order(t: &View) -> u8 {
350    match t {
351        View::Var(_) => 0,
352        View::Int(_) => 1,
353        View::Date(_) => 2,
354        View::Real(_) => 3,
355        View::Atom(_) => 4,
356        View::Str(_) => 5,
357        View::Func(_, _, _) => 6,
358        View::Tuple(_, _) => 7,
359        View::List(_, _, _) => 8,
360        View::Bin(_) => 9,
361    }
362}
363
364/// Extract a numeric value from a term for ordering purposes.  All
365/// numeric kinds (int, real and date) are converted to `f64` for
366/// comparison.  `Date` values use their millisecond representation as
367/// the numeric value.
368fn numeric_value(t: &View) -> f64 {
369    match t {
370        View::Int(i) => *i as f64,
371        View::Real(f) => *f,
372        View::Date(d) => *d as f64,
373        _ => unreachable!(),
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn view_size_is_40_bytes() {
383        assert_eq!(core::mem::size_of::<View>(), 40);
384    }
385
386    #[test]
387    fn option_view_size_is_40_bytes() {
388        assert_eq!(core::mem::size_of::<Option<View>>(), 40);
389    }
390}