arena_terms/
arena.rs

1//! Defines the [`Arena`] type, which manages allocation and interning
2//! of data for [`Term`] values.
3//!
4//! Provides constructors, basic allocation methods, and utilities for
5//! working with terms stored in the arena.
6
7use crate::{InternalTermError, IntoTerm, OperDefs, Slice, Term, TermError, View};
8
9/// The arena interns atoms, variables, strings, binaries, and compound terms.  
10/// An `Arena` owns all memory for interned data. Terms store only indices into
11/// this arena and remain valid as long as the epoch they belong to is alive.
12///
13/// ### Epochs
14/// The arena is divided into *epochs*. Conceptually, epochs form a stack.  
15/// Allocation begins in epoch `0`, which starts at offset `0` in all
16/// underlying storages. At any time, the user can call `begin_epoch()`.  
17/// This operation:
18/// - Freezes the current epoch (recording its byte and term offsets).  
19/// - Starts a new *active* epoch for subsequent allocations.  
20///
21/// At any point, there are `K` alive epochs, where:
22/// - `K - 1` are frozen (no new data is added),  
23/// - The last one is active (all new allocations go there),  
24/// - and `K <= MAX_LIVE_EPOCHS` (typically very small number, currently 8).
25///
26/// Terms remain valid only while the epoch they were created in is alive.  
27///
28/// ### Truncation
29/// The arena can be truncated back to a given epoch `m`, where
30/// `0 <= m < MAX_LIVE_EPOCHS`:
31/// - Epoch `m` and all epochs more recent than `m` are erased in O(1).  
32/// - Terms from those epochs become invalid.  
33/// - `truncate(0)` erases all data (synonym: `clear()`).  
34/// - `truncate(current_epoch())` erases only the latest epoch  
35///   (synonym: `truncate_current()`).  
36///
37/// Conceptually, epochs form a stack: you can `push` with `begin_epoch()`
38/// and `pop` with `truncate_current()`. This makes it efficient to manage
39/// temporary, scoped allocations. For example:
40/// ```
41/// # use arena_terms::Arena;
42/// let mut arena = Arena::with_capacity(4096, 1024);
43/// let epoch = arena.begin_epoch().unwrap();
44/// // … build temporary terms here …
45/// arena.truncate(epoch).unwrap(); // frees them all at once
46/// ```
47///
48/// This is especially useful during iteration: each loop can create
49/// short-lived terms, then discard them cleanly all at once at the end.
50
51#[derive(Default, Clone, Debug)]
52pub struct Arena {
53    /// Randomly generated Arena ID
54    pub(crate) arena_id: ArenaID,
55
56    /// Index into the buffers of alive epochs.
57    /// Always points to the "current" epoch (latest allocations).
58    pub(crate) current_epoch: usize,
59
60    /// Randomly generated identifiers, one per epoch.
61    /// Every handle (e.g., term, func, var) that references this arena
62    /// carries the epoch ID that was current at allocation time.
63    /// When a handle is later resolved, the epoch ID is checked to
64    /// ensure it still belongs to the same arena instance.
65    pub(crate) epoch_ids: [EpochID; MAX_LIVE_EPOCHS],
66
67    /// Storage for interned atoms, variables, strings, and binary blobs.
68    /// Data are appended sequentially in the last active epoch.
69    pub(crate) bytes: Vec<u8>,
70
71    /// For each epoch, the starting offset into `bytes`.
72    /// Used to "rewind" or reclaim all data belonging to an expired epoch.
73    pub(crate) byte_start_by_epoch: [usize; MAX_LIVE_EPOCHS],
74
75    /// Storage for compound terms (structured values).
76    /// Terms are appended sequentially in the last active epoch.
77    /// Each term is represented as a contiguous slice:
78    ///   [functor_atom, arg1, arg2, …]
79    /// The `Func` handle encodes both the slice’s starting index and length.
80    pub(crate) terms: Vec<Term>,
81
82    /// For each epoch, the starting index into `terms`.
83    /// Used to drop/pick up all terms from an expired epoch in bulk.
84    pub(crate) term_start_by_epoch: [usize; MAX_LIVE_EPOCHS],
85
86    /// Operator definitions associated with this arena.
87    pub(crate) opers: OperDefs,
88}
89
90pub const MAX_LIVE_EPOCHS: usize = 8;
91
92#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
93pub struct EpochID(pub(crate) u32); // Random Epoch ID
94
95#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
96pub struct ArenaID(pub(crate) u32); // Random Arena ID
97
98#[derive(Debug, Clone, Copy)]
99pub struct ArenaStats {
100    pub current_epoch: EpochID,
101    pub bytes_len: usize,
102    pub terms_len: usize,
103}
104
105impl Arena {
106    /// Create a new, empty arena with given capacities.
107    pub fn with_capacity(bytes_capacity: usize, terms_capacity: usize) -> Self {
108        let mut epoch_ids = [EpochID(0); MAX_LIVE_EPOCHS];
109        epoch_ids[0] = EpochID(rand::random());
110
111        Self {
112            arena_id: ArenaID(rand::random()),
113            current_epoch: 0,
114            epoch_ids,
115            bytes: Vec::with_capacity(bytes_capacity),
116            byte_start_by_epoch: [0; MAX_LIVE_EPOCHS],
117            terms: Vec::with_capacity(terms_capacity),
118            term_start_by_epoch: [0; MAX_LIVE_EPOCHS],
119            opers: OperDefs::new(),
120        }
121    }
122
123    /// Create a new, empty arena with default capacities.
124    pub fn new() -> Self {
125        Self::with_capacity(4096, 1024)
126    }
127
128    /// Create a new, empty arena with default operator definitions
129    /// and default capacities.
130    pub fn try_with_default_opers() -> Result<Self, TermError> {
131        let mut arena = Self::new();
132        arena.define_default_opers()?;
133        Ok(arena)
134    }
135
136    /// Returns stats.
137    pub fn stats(&self) -> ArenaStats {
138        ArenaStats {
139            current_epoch: self.epoch_ids[self.current_epoch],
140            bytes_len: self.bytes.len(),
141            terms_len: self.terms.len(),
142        }
143    }
144
145    /// Returns current epoch.
146    pub fn current_epoch(&self) -> EpochID {
147        self.epoch_ids[self.current_epoch]
148    }
149
150    /// Freezes current epoch and begins a new one.
151    pub fn begin_epoch(&mut self) -> Result<EpochID, TermError> {
152        let new_epoch = self.current_epoch + 1;
153        if new_epoch >= MAX_LIVE_EPOCHS {
154            return Err(TermError::LiveEpochsExceeded);
155        }
156        self.epoch_ids[new_epoch] = EpochID(rand::random());
157        self.byte_start_by_epoch[new_epoch] = self.bytes.len();
158        self.term_start_by_epoch[new_epoch] = self.terms.len();
159        self.current_epoch = new_epoch;
160        Ok(self.epoch_ids[new_epoch])
161    }
162
163    /// Erases arena in O(1).
164    /// Does not shrink the allocated capacity.
165    pub fn clear(&mut self) -> Result<(), TermError> {
166        self.truncate(self.epoch_ids[0])
167    }
168
169    /// Epoch `m` and all epochs more recent than `m` are erased in O(1)
170    /// Does not shrink the allocated capacity.
171    pub fn truncate_current(&mut self) -> Result<(), TermError> {
172        self.truncate(self.epoch_ids[self.current_epoch])
173    }
174
175    /// Epoch `m` and all epochs more recent than `m` are erased in O(1)
176    /// Does not shrink the allocated capacity.
177    pub fn truncate(&mut self, epoch_id: EpochID) -> Result<(), TermError> {
178        let epoch = self
179            .epoch_index(epoch_id)
180            .map_err(|_| TermError::InvalidEpoch(epoch_id))?;
181        self.bytes.truncate(self.byte_start_by_epoch[epoch]);
182        self.terms.truncate(self.term_start_by_epoch[epoch]);
183        self.current_epoch = epoch;
184        Ok(())
185    }
186
187    /// Searches epoch ID in alive epochs and returns its index.
188    #[inline]
189    fn epoch_index(&self, epoch_id: EpochID) -> Result<usize, InternalTermError> {
190        let Some(epoch) = self.epoch_ids[..=self.current_epoch]
191            .iter()
192            .position(|&id| id == epoch_id)
193        else {
194            return Err(InternalTermError::InvalidEpoch(epoch_id));
195        };
196        Ok(epoch)
197    }
198
199    /// Returns an error if the term's slice's epoch is not among the alive epochs,
200    /// or if the slice's index/length is inconsistent with the epoch's range.
201    #[inline]
202    fn verify_byte_slice(&self, slice: &Slice) -> Result<(), InternalTermError> {
203        let epoch = self.epoch_index(slice.epoch_id)?;
204        let epoch_start = self.byte_start_by_epoch[epoch];
205        let epoch_end = if epoch == self.current_epoch {
206            self.bytes.len()
207        } else {
208            self.byte_start_by_epoch[epoch + 1]
209        };
210        if (slice.index as usize) < epoch_start
211            || (slice.index as usize) + (slice.len as usize) > epoch_end
212        {
213            return Err(InternalTermError::InvalidSlice(*slice));
214        }
215        Ok(())
216    }
217
218    /// Returns an error if the byte's slice's epoch is not among the alive epochs,
219    /// or if the slice's index/length is inconsistent with the epoch's range.
220    #[inline]
221    fn verify_term_slice(&self, slice: &Slice) -> Result<(), InternalTermError> {
222        let epoch = self.epoch_index(slice.epoch_id)?;
223        let epoch_start = self.term_start_by_epoch[epoch];
224        let epoch_end = if epoch == self.current_epoch {
225            self.terms.len()
226        } else {
227            self.term_start_by_epoch[epoch + 1]
228        };
229        if (slice.index as usize) < epoch_start
230            || (slice.index as usize) + (slice.len as usize) > epoch_end
231        {
232            return Err(InternalTermError::InvalidSlice(*slice));
233        }
234        Ok(())
235    }
236
237    /// Convert a `value` into `Term`.
238    #[inline]
239    pub fn term<'a, T: IntoTerm>(&'a mut self, value: T) -> Term {
240        value.into_term(self)
241    }
242
243    /// Construct a new integer term.  The full 64 bit two's complement
244    /// representation of `i` is stored in the payload.  No truncation
245    /// occurs.
246    #[inline]
247    pub fn int(&mut self, i: impl Into<i64>) -> Term {
248        Term::int(i)
249    }
250
251    /// Construct a new floating point term.  The full 64 bit IEEE‑754
252    /// bit pattern is stored in the payload without truncation.
253    #[inline]
254    pub fn real(&mut self, r: impl Into<f64>) -> Term {
255        Term::real(r)
256    }
257
258    /// Construct a new date term representing a Unix epoch in
259    /// milliseconds.
260    #[inline]
261    pub fn date(&mut self, ms: impl Into<i64>) -> Term {
262        Term::date(ms)
263    }
264
265    /// Construct or intern an atom into the arena and produce a term
266    /// referencing it.  Small atom names (≤14 bytes of UTF‑8) are
267    /// inlined directly into the handle; longer names are interned
268    /// into the arena and referenced by index and length.
269    #[inline]
270    pub fn atom(&mut self, name: impl AsRef<str>) -> Term {
271        Term::atom(self, name)
272    }
273
274    /// Construct or intern a variable into the arena and produce a
275    /// term referencing it.  Small variable names (≤14 bytes) are
276    /// inlined directly into the handle; longer names are interned in
277    /// the arena and referenced by index.
278    #[inline]
279    pub fn var(&mut self, name: impl AsRef<str>) -> Term {
280        Term::var(self, name)
281    }
282
283    /// Construct or intern a UTF‑8 string into the arena and produce a
284    /// term referencing it.  Strings longer than 14 bytes are interned
285    /// in the arena; shorter strings are inlined.  Invalid UTF‑8 will
286    /// result in an error.
287    #[inline]
288    pub fn str(&mut self, s: impl AsRef<str>) -> Term {
289        Term::str(self, s)
290    }
291
292    /// Construct or intern a binary blob into the arena and produce a
293    /// term referencing it.  Blobs longer than 14 bytes are interned
294    /// in the arena; shorter blobs are inlined.
295    #[inline]
296    pub fn bin(&mut self, bytes: impl AsRef<[u8]>) -> Term {
297        Term::bin(self, bytes)
298    }
299
300    /// Construct a new compound term by interning the functor and
301    /// arguments in the arena.  The returned term references a slice
302    /// in the arena's term storage consisting of the functor atom as
303    /// the first entry followed by the argument handles.  A functor of
304    /// arity zero results in an atom.
305    #[inline]
306    pub fn func(
307        &mut self,
308        functor: impl AsRef<str>,
309        args: impl IntoIterator<Item = impl IntoTerm>,
310    ) -> Term {
311        Term::func(self, functor, args)
312    }
313
314    /// Construct a new compound term by interning the functor and its arguments
315    /// into the arena as a sequence of terms (functor first, then arguments).
316    /// A functor with no arguments yields the atom itself.  Errors if
317    /// no functor is provided or if the first term is not an atom.
318    #[inline]
319    pub fn funcv(
320        &mut self,
321        terms: impl IntoIterator<Item = impl IntoTerm>,
322    ) -> Result<Term, TermError> {
323        Term::funcv(self, terms)
324    }
325
326    /// Constructs a new list. A list is represented internally as an
327    /// array of terms. If `terms` is empty, returns `nil`.
328    #[inline]
329    pub fn list(&mut self, terms: impl IntoIterator<Item = impl IntoTerm>) -> Term {
330        Term::list(self, terms)
331    }
332
333    /// Constructs a new improper list. An improper list is represented as
334    /// a list and additional argument. If `terms` is empty, returns `nil`.
335    #[inline]
336    pub fn listc(
337        &mut self,
338        terms: impl IntoIterator<Item = impl IntoTerm>,
339        tail: impl IntoTerm,
340    ) -> Term {
341        Term::listc(self, terms, tail)
342    }
343
344    /// Constructs a new tuple. A tuple is represented internally as an array
345    /// of terms.
346    #[inline]
347    pub fn tuple(&mut self, terms: impl IntoIterator<Item = impl IntoTerm>) -> Term {
348        Term::tuple(self, terms)
349    }
350
351    /// Constant representing the zero‑arity tuple (unit).  Internally
352    /// this is the atom `"unit"` encoded as a small atom.  It may
353    /// be copied freely and does not depend on any arena.
354    pub const UNIT: Term = Term::UNIT;
355
356    /// Constant representing the empty list (nil).  Internally this is
357    /// the atom `"nil"` encoded as a small atom.  It may be copied
358    /// freely and does not depend on any arena.
359    pub const NIL: Term = Term::NIL;
360
361    /// Returns the name of a compound term, atom, or variable.
362    /// Use [`unpack_atom`], [`unpack_func`], or [`unpack_var`]
363    /// to ensure the term is of a specific kind.
364    #[inline]
365    pub fn name<'a>(&'a self, term: &'a Term) -> Result<&'a str, TermError> {
366        match self.view(term)? {
367            View::Var(name) | View::Atom(name) => Ok(name),
368            View::Func(ar, functor, _) => Ok(functor.atom_name(ar)?),
369            _ => Err(TermError::UnexpectedKind {
370                expected: "var, atom, func",
371                found: term.kind_name(),
372            }),
373        }
374    }
375
376    /// Returns the name of an atom,
377    #[inline]
378    pub fn atom_name<'a>(&'a self, term: &'a Term) -> Result<&'a str, TermError> {
379        self.unpack_atom(term, &[])
380    }
381
382    /// Returns the name of a variable.
383    #[inline]
384    pub fn var_name<'a>(&'a self, term: &'a Term) -> Result<&'a str, TermError> {
385        self.unpack_var(term, &[])
386    }
387
388    /// Returns the name of a compund term.
389    #[inline]
390    pub fn func_name<'a>(&'a self, term: &'a Term) -> Result<&'a str, TermError> {
391        let (functor, _) = self.unpack_func_any(term, &[])?;
392        self.atom_name(functor)
393    }
394
395    /// Returns the value if `term` is an integer, otherwise an error.
396    #[inline]
397    pub fn unpack_int(&self, term: &Term) -> Result<i64, TermError> {
398        match self.view(term)? {
399            View::Int(v) => Ok(v),
400            _ => Err(TermError::UnexpectedKind {
401                expected: "int",
402                found: term.kind_name(),
403            }),
404        }
405    }
406
407    /// Returns the value if `term` is a real, otherwise an error.
408    #[inline]
409    pub fn unpack_real(&self, term: &Term) -> Result<f64, TermError> {
410        match self.view(term)? {
411            View::Real(v) => Ok(v),
412            _ => Err(TermError::UnexpectedKind {
413                expected: "real",
414                found: term.kind_name(),
415            }),
416        }
417    }
418
419    /// Returns the value if `term` is a date, otherwise an error.
420    #[inline]
421    pub fn unpack_date(&self, term: &Term) -> Result<i64, TermError> {
422        match self.view(term)? {
423            View::Date(v) => Ok(v),
424            _ => Err(TermError::UnexpectedKind {
425                expected: "date",
426                found: term.kind_name(),
427            }),
428        }
429    }
430
431    /// Returns the string slice if `term` is a string, otherwise an error.
432    #[inline]
433    pub fn unpack_str<'a>(&'a self, term: &'a Term) -> Result<&'a str, TermError> {
434        match self.view(term)? {
435            View::Str(v) => Ok(v),
436            _ => Err(TermError::UnexpectedKind {
437                expected: "str",
438                found: term.kind_name(),
439            }),
440        }
441    }
442
443    /// Returns the slice if `term` is a binary blob, otherwise an error.
444    #[inline]
445    pub fn unpack_bin<'a>(&'a self, term: &'a Term) -> Result<&'a [u8], TermError> {
446        match self.view(term)? {
447            View::Bin(v) => Ok(v),
448            _ => Err(TermError::UnexpectedKind {
449                expected: "bin",
450                found: term.kind_name(),
451            }),
452        }
453    }
454
455    /// Returns the name if `term` is an atom, otherwise an error.
456    #[inline]
457    pub fn unpack_atom<'a>(
458        &'a self,
459        term: &'a Term,
460        allowed_names: &[&str],
461    ) -> Result<&'a str, TermError> {
462        match self.view(term)? {
463            View::Atom(name) => {
464                if !allowed_names.is_empty() && !allowed_names.contains(&name) {
465                    return Err(TermError::UnexpectedName(*term));
466                }
467                Ok(name)
468            }
469            _ => Err(TermError::UnexpectedKind {
470                expected: "atom",
471                found: term.kind_name(),
472            }),
473        }
474    }
475
476    /// Returns the name if `term` is a variable, otherwise an error.
477    #[inline]
478    pub fn unpack_var<'a>(
479        &'a self,
480        term: &'a Term,
481        allowed_names: &[&str],
482    ) -> Result<&'a str, TermError> {
483        match self.view(term)? {
484            View::Var(name) => {
485                if !allowed_names.is_empty() && !allowed_names.contains(&name) {
486                    return Err(TermError::UnexpectedName(*term));
487                }
488                Ok(name)
489            }
490            _ => Err(TermError::UnexpectedKind {
491                expected: "var",
492                found: term.kind_name(),
493            }),
494        }
495    }
496
497    /// Returns the name and arguments if `term` is a compound term of any arity
498    /// or an atom and its name is in `allowed_names` (or if `allowed_names` is empty),
499    /// otherwise returns an error.
500    #[inline]
501    pub fn unpack_func_any<'a>(
502        &'a self,
503        term: &'a Term,
504        allowed_names: &[&str],
505    ) -> Result<(&'a Term, &'a [Term]), TermError> {
506        match self.view(term)? {
507            View::Atom(name) => {
508                if !allowed_names.is_empty() && !allowed_names.contains(&name) {
509                    return Err(TermError::UnexpectedName(*term));
510                }
511                Ok((term, &[] as &[Term]))
512            }
513            View::Func(_, functor, args) => {
514                if args.is_empty() {
515                    return Err(TermError::InvalidTerm(*term));
516                }
517                if !allowed_names.is_empty() {
518                    let name = self.atom_name(functor)?;
519                    if !allowed_names.contains(&name) {
520                        return Err(TermError::UnexpectedName(*term));
521                    }
522                }
523                Ok((functor, args))
524            }
525            _ => Err(TermError::UnexpectedKind {
526                expected: "func",
527                found: term.kind_name(),
528            }),
529        }
530    }
531
532    /// Returns the name and arguments if `term` is a compound term of arity `ARITY`
533    /// (or an atom if `ARITY == 0`) and its name is in `allowed_names` (or if `allowed_names` is empty),
534    /// otherwise returns an error.
535    #[inline]
536    pub fn unpack_func<'a, const ARITY: usize>(
537        &'a self,
538        term: &'a Term,
539        allowed_names: &[&str],
540    ) -> Result<(&'a Term, [Term; ARITY]), TermError> {
541        let (functor, args) = self.unpack_func_any(term, allowed_names)?;
542        if args.len() != ARITY {
543            return Err(TermError::UnexpectedArity {
544                expected: ARITY,
545                found: args.len(),
546            });
547        }
548        let arr: [_; ARITY] = args.try_into().unwrap();
549        return Ok((functor, arr));
550    }
551
552    /// Returns the slice with list elements and the tail if `term` is a list,
553    /// otherwise returns an error.
554    #[inline]
555    pub fn unpack_list<'a>(&'a self, term: &'a Term) -> Result<(&'a [Term], &'a Term), TermError> {
556        match self.view(term)? {
557            View::Atom(_) if term == &Term::NIL => Ok((&[], &Term::NIL)),
558            View::List(_, terms, tail) => Ok((terms, tail)),
559            _ => Err(TermError::UnexpectedKind {
560                expected: "list",
561                found: term.kind_name(),
562            }),
563        }
564    }
565
566    /// Returns the slice with tuple elements if `term` is a tuple of any arity,
567    /// otherwise returns an error.
568    #[inline]
569    pub fn unpack_tuple_any<'a>(&'a self, term: &'a Term) -> Result<&'a [Term], TermError> {
570        match self.view(term)? {
571            View::Atom(_) if *term == Term::UNIT => Ok(&[]),
572            View::Tuple(_, terms) => Ok(terms),
573            _ => Err(TermError::UnexpectedKind {
574                expected: "tuple",
575                found: term.kind_name(),
576            }),
577        }
578    }
579
580    /// Returns the tuple elements if `term` is a tuple of arity `ARITY`,
581    /// otherwise returns an error.
582    #[inline]
583    pub fn unpack_tuple<const ARITY: usize>(
584        &self,
585        term: &Term,
586    ) -> Result<[Term; ARITY], TermError> {
587        let terms = self.unpack_tuple_any(term)?;
588        if terms.len() != ARITY {
589            return Err(TermError::UnexpectedArity {
590                expected: ARITY,
591                found: terms.len(),
592            });
593        }
594        let arr: [_; ARITY] = terms.try_into().unwrap();
595        return Ok(arr);
596    }
597
598    /// Intern a UTF‑8 string into the arena and return its slice
599    /// descriptor.  Strings are stored in a contiguous bump vector.
600    #[inline]
601    pub(crate) fn intern_str(&mut self, s: &str) -> Slice {
602        let index = self.bytes.len();
603        self.bytes.extend_from_slice(s.as_bytes());
604        let len = s.len();
605        Slice {
606            epoch_id: self.epoch_ids[self.current_epoch],
607            index: index as u32,
608            len: len as u32,
609        }
610    }
611
612    /// Intern a binary blob into the arena and return its slice descriptor.
613    #[inline]
614    pub(crate) fn intern_bytes(&mut self, bytes: &[u8]) -> Slice {
615        let index = self.bytes.len();
616        self.bytes.extend_from_slice(bytes);
617        let len = bytes.len();
618        Slice {
619            epoch_id: self.epoch_ids[self.current_epoch],
620            index: index as u32,
621            len: len as u32,
622        }
623    }
624
625    /// Intern a compound term slice (functor + args) into the term arena.
626    #[inline]
627    pub(crate) fn intern_func(
628        &mut self,
629        functor: Term,
630        args: impl IntoIterator<Item = impl IntoTerm>,
631    ) -> Slice {
632        let index = self.terms.len();
633        self.terms.push(functor);
634        for x in args {
635            let t = x.into_term(self);
636            self.terms.push(t);
637        }
638        let len = self.terms.len() - index;
639        Slice {
640            epoch_id: self.epoch_ids[self.current_epoch],
641            index: index as u32,
642            len: len as u32,
643        }
644    }
645
646    /// Intern a seq term slice into the term arena.
647    #[inline]
648    pub(crate) fn intern_seq(&mut self, terms: impl IntoIterator<Item = impl IntoTerm>) -> Slice {
649        let index = self.terms.len();
650        for x in terms {
651            let t = x.into_term(self);
652            self.terms.push(t);
653        }
654        let len = self.terms.len() - index;
655        Slice {
656            epoch_id: self.epoch_ids[self.current_epoch],
657            index: index as u32,
658            len: len as u32,
659        }
660    }
661
662    /// Intern a seq term slice plus tail into the term arena.
663    #[inline]
664    pub(crate) fn intern_seq_plus_one(
665        &mut self,
666        terms: impl IntoIterator<Item = impl IntoTerm>,
667        tail: impl IntoTerm,
668    ) -> Slice {
669        let index = self.terms.len();
670        for x in terms {
671            let t = x.into_term(self);
672            self.terms.push(t);
673        }
674        let t = tail.into_term(self);
675        self.terms.push(t);
676        let len = self.terms.len() - index;
677        Slice {
678            epoch_id: self.epoch_ids[self.current_epoch],
679            index: index as u32,
680            len: len as u32,
681        }
682    }
683
684    /// Borrow a slice of bytes stored in the arena.
685    /// should not be called directly by users; instead use
686    /// [`Term::view`].
687    #[inline]
688    pub(crate) fn byte_slice<'a>(&'a self, slice: &Slice) -> Result<&'a [u8], InternalTermError> {
689        self.verify_byte_slice(slice)?;
690        Ok(&self.bytes[(slice.index as usize)..((slice.index + slice.len) as usize)])
691    }
692
693    /// Borrow a slice of terms comprising a compound term.
694    #[inline]
695    pub(crate) fn term_slice<'a>(&'a self, slice: &Slice) -> Result<&'a [Term], InternalTermError> {
696        self.verify_term_slice(slice)?;
697        Ok(&self.terms[(slice.index as usize)..((slice.index + slice.len) as usize)])
698    }
699}