snix_eval/
upvalues.rs

1//! This module encapsulates some logic for upvalue handling, which is
2//! relevant to both thunks (delayed computations for lazy-evaluation)
3//! as well as closures (lambdas that capture variables from the
4//! surrounding scope).
5//!
6//! The upvalues of a scope are whatever data are needed at runtime
7//! in order to resolve each free variable in the scope to a value.
8//! "Upvalue" is a term taken from Lua.
9
10use std::ops::Index;
11
12use crate::{Value, opcode::UpvalueIdx};
13
14/// Wrapper type to assist with assembling [Upvalues].
15/// The first bit is used to signify that data for a `with` scope is
16/// being used, the other 63 bits define the upvalue count.
17pub struct UpvalueData(u64);
18
19impl UpvalueData {
20    #[inline]
21    pub fn new(count: usize, captures_with: bool) -> Self {
22        debug_assert!(
23            count < (i64::MAX as usize),
24            "upvalue count exceeds storage size"
25        );
26
27        let raw = ((count as u64) << 1) | (if captures_with { 1 } else { 0 });
28        Self(raw)
29    }
30
31    #[inline]
32    pub fn from_raw(value: u64) -> Self {
33        Self(value)
34    }
35
36    #[inline]
37    pub fn count(&self) -> usize {
38        (self.0 as usize) >> 1
39    }
40
41    #[inline]
42    pub fn captures_with(&self) -> bool {
43        self.0 & 0b1 == 1
44    }
45
46    #[inline]
47    pub fn into_raw(self) -> u64 {
48        self.0
49    }
50}
51
52/// Structure for carrying upvalues of an UpvalueCarrier.  The
53/// implementation of this struct encapsulates the logic for
54/// capturing and accessing upvalues.
55///
56/// Nix's `with` cannot be used to shadow an enclosing binding --
57/// like Rust's `use xyz::*` construct, but unlike Javascript's
58/// `with (xyz)`.  This means that Nix has two kinds of identifiers,
59/// which can be distinguished at compile time:
60///
61/// - Static identifiers, which are bound in some enclosing scope by
62///   `let`, `name:` or `{name}:`
63/// - Dynamic identifiers, which are not bound in any enclosing
64///   scope
65#[derive(Clone, Debug)]
66pub struct Upvalues {
67    /// The upvalues of static identifiers.  Each static identifier
68    /// is assigned an integer identifier at compile time, which is
69    /// an index into this Vec.
70    static_upvalues: Vec<Value>,
71
72    /// The upvalues of dynamic identifiers, if any exist.  This
73    /// consists of the value passed to each enclosing `with val;`,
74    /// from outermost to innermost.
75    with_stack: Vec<Value>,
76}
77
78impl Upvalues {
79    pub fn with_capacity(count: usize) -> Self {
80        Upvalues {
81            static_upvalues: Vec::with_capacity(count),
82            with_stack: vec![],
83        }
84    }
85
86    /// Construct an [Upvalues] instance from the raw static and with stacks.
87    pub fn from_raw_parts(static_upvalues: Vec<Value>, with_stack: Vec<Value>) -> Self {
88        Self {
89            static_upvalues,
90            with_stack,
91        }
92    }
93
94    /// Get the number of static upvalues
95    pub fn len(&self) -> usize {
96        self.static_upvalues.len()
97    }
98
99    /// Retrieve a single value from the `with_stack`. Returns `None`
100    /// if the stack doesn't exist or the value isn't in range.
101    pub fn get_from_with_stack(&self, index: usize) -> Option<Value> {
102        self.with_stack.get(index).cloned()
103    }
104
105    pub fn with_stack(&self) -> &Vec<Value> {
106        self.with_stack.as_ref()
107    }
108
109    pub fn with_stack_len(&self) -> usize {
110        self.with_stack.len()
111    }
112
113    pub fn into_static_upvalues(self) -> Vec<Value> {
114        self.static_upvalues
115    }
116
117    /// Resolve deferred upvalues from the provided stack slice,
118    /// mutating them in the internal upvalue slots.
119    pub fn resolve_deferred_upvalues(&mut self, stack: &[Value]) {
120        for upvalue in self.static_upvalues.iter_mut() {
121            if let Value::DeferredUpvalue(update_from_idx) = upvalue {
122                *upvalue = stack[update_from_idx.0].clone();
123            }
124        }
125    }
126}
127
128impl Index<UpvalueIdx> for Upvalues {
129    type Output = Value;
130
131    fn index(&self, index: UpvalueIdx) -> &Self::Output {
132        &self.static_upvalues[index.0]
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::UpvalueData;
139    #[test]
140    fn test_upvalue() {
141        assert_eq!(
142            std::mem::size_of::<UpvalueData>(),
143            std::mem::size_of::<usize>(),
144            "The size of the UpvalueData should match the size of usize/u64."
145        );
146        let value = UpvalueData::new(456, false);
147        assert!(!value.captures_with());
148        assert_eq!(value.count(), 456);
149
150        let value = UpvalueData::from_raw(value.into_raw());
151        assert!(!value.captures_with());
152        assert_eq!(value.count(), 456);
153
154        let value = UpvalueData::new(1024, true);
155        assert!(value.captures_with());
156        assert_eq!(value.count(), 1024);
157
158        let value = UpvalueData::from_raw(value.into_raw());
159        assert!(value.captures_with());
160        assert_eq!(value.count(), 1024);
161    }
162}