Skip to main content

datalogic_rs/
context.rs

1use serde_json::Value;
2use std::sync::Arc;
3
4/// A single frame in the context stack, optimized as an enum to minimize size.
5///
6/// Field usage analysis shows mutually exclusive patterns:
7/// - 59% of frames use only data + index (array iteration)
8/// - 18% use data + index + key (object iteration)
9/// - 9% use reduce_current + reduce_accumulator (reduce operations)
10/// - 14% use only data (error context, etc.)
11pub enum ContextFrame {
12    /// Array iteration frame — most common (data + index)
13    Indexed { data: Value, index: usize },
14    /// Object iteration frame (data + index + key)
15    Keyed {
16        data: Value,
17        index: usize,
18        key: String,
19    },
20    /// Reduce operation frame (current + accumulator)
21    Reduce { current: Value, accumulator: Value },
22    /// Simple data-only frame
23    Data(Value),
24}
25
26impl ContextFrame {
27    /// Get the data value for this frame
28    #[inline]
29    pub fn data(&self) -> &Value {
30        match self {
31            Self::Indexed { data, .. } | Self::Keyed { data, .. } | Self::Data(data) => data,
32            Self::Reduce { current, .. } => current,
33        }
34    }
35
36    /// Get the index if available
37    #[inline]
38    pub fn get_index(&self) -> Option<usize> {
39        match self {
40            Self::Indexed { index, .. } | Self::Keyed { index, .. } => Some(*index),
41            _ => None,
42        }
43    }
44
45    /// Get the key if available
46    #[inline]
47    pub fn get_key(&self) -> Option<&str> {
48        match self {
49            Self::Keyed { key, .. } => Some(key.as_str()),
50            _ => None,
51        }
52    }
53
54    /// Get the reduce "current" value if this is a reduce frame
55    #[inline]
56    pub fn get_reduce_current(&self) -> Option<&Value> {
57        match self {
58            Self::Reduce { current, .. } => Some(current),
59            _ => None,
60        }
61    }
62
63    /// Get the reduce "accumulator" value if this is a reduce frame
64    #[inline]
65    pub fn get_reduce_accumulator(&self) -> Option<&Value> {
66        match self {
67            Self::Reduce { accumulator, .. } => Some(accumulator),
68            _ => None,
69        }
70    }
71}
72
73/// Reference to a context frame (either actual frame or root)
74pub enum ContextFrameRef<'a> {
75    /// Reference to an actual frame
76    Frame(&'a ContextFrame),
77    /// Reference to the root data
78    Root(&'a Arc<Value>),
79}
80
81impl<'a> ContextFrameRef<'a> {
82    /// Get the data value
83    pub fn data(&self) -> &Value {
84        match self {
85            ContextFrameRef::Frame(frame) => frame.data(),
86            ContextFrameRef::Root(root) => root,
87        }
88    }
89
90    /// Get the index if available
91    #[inline]
92    pub fn get_index(&self) -> Option<usize> {
93        match self {
94            ContextFrameRef::Frame(frame) => frame.get_index(),
95            ContextFrameRef::Root(_) => None,
96        }
97    }
98
99    /// Get the key if available
100    #[inline]
101    pub fn get_key(&self) -> Option<&str> {
102        match self {
103            ContextFrameRef::Frame(frame) => frame.get_key(),
104            ContextFrameRef::Root(_) => None,
105        }
106    }
107
108    /// Get the reduce "current" value if this is a reduce frame
109    #[inline]
110    pub fn get_reduce_current(&self) -> Option<&Value> {
111        match self {
112            ContextFrameRef::Frame(frame) => frame.get_reduce_current(),
113            ContextFrameRef::Root(_) => None,
114        }
115    }
116
117    /// Get the reduce "accumulator" value if this is a reduce frame
118    #[inline]
119    pub fn get_reduce_accumulator(&self) -> Option<&Value> {
120        match self {
121            ContextFrameRef::Frame(frame) => frame.get_reduce_accumulator(),
122            ContextFrameRef::Root(_) => None,
123        }
124    }
125
126    /// Get metadata — always returns None (metadata HashMap has been removed;
127    /// index/key/reduce fields are accessed through dedicated methods).
128    #[inline]
129    pub fn metadata(&self) -> Option<&std::collections::HashMap<String, Value>> {
130        None
131    }
132}
133
134/// Context stack for nested evaluations
135pub struct ContextStack {
136    /// Immutable root data (never changes during evaluation)
137    root: Arc<Value>,
138    /// Stack of temporary frames (excludes root)
139    frames: Vec<ContextFrame>,
140}
141
142impl ContextStack {
143    /// Create a new context stack with Arc root data
144    pub fn new(root: Arc<Value>) -> Self {
145        Self {
146            root,
147            frames: Vec::new(),
148        }
149    }
150
151    /// Pushes a new context frame for nested evaluation (data only).
152    pub fn push(&mut self, data: Value) {
153        self.frames.push(ContextFrame::Data(data));
154    }
155
156    /// Pushes a frame with just an index (optimized path for array iteration).
157    #[inline]
158    pub fn push_with_index(&mut self, data: Value, index: usize) {
159        self.frames.push(ContextFrame::Indexed { data, index });
160    }
161
162    /// Pushes a frame with both index and key (optimized path for object iteration).
163    #[inline]
164    pub fn push_with_key_index(&mut self, data: Value, index: usize, key: String) {
165        self.frames.push(ContextFrame::Keyed { data, index, key });
166    }
167
168    /// Replaces data, index, and key in the top frame in-place (for object iteration).
169    #[inline]
170    pub fn replace_top_key_data(&mut self, data: Value, index: usize, key: String) {
171        if let Some(frame) = self.frames.last_mut() {
172            *frame = ContextFrame::Keyed { data, index, key };
173        }
174    }
175
176    /// Takes the data from the top frame, replacing it with Null.
177    #[inline]
178    pub fn take_top_data(&mut self) -> Value {
179        if let Some(frame) = self.frames.last_mut() {
180            match frame {
181                ContextFrame::Indexed { data, .. }
182                | ContextFrame::Keyed { data, .. }
183                | ContextFrame::Data(data) => std::mem::replace(data, Value::Null),
184                ContextFrame::Reduce { current, .. } => std::mem::replace(current, Value::Null),
185            }
186        } else {
187            Value::Null
188        }
189    }
190
191    /// Replaces the data and index in the top frame in-place.
192    #[inline]
193    pub fn replace_top_data(&mut self, data: Value, index: usize) {
194        if let Some(frame) = self.frames.last_mut() {
195            *frame = ContextFrame::Indexed { data, index };
196        }
197    }
198
199    /// Pushes a reduce frame with "current" and "accumulator" values.
200    #[inline]
201    pub fn push_reduce(&mut self, current: Value, accumulator: Value) {
202        self.frames.push(ContextFrame::Reduce {
203            current,
204            accumulator,
205        });
206    }
207
208    /// Replaces current and accumulator values in the top reduce frame in-place.
209    #[inline]
210    pub fn replace_reduce_data(&mut self, current: Value, accumulator: Value) {
211        if let Some(frame) = self.frames.last_mut() {
212            *frame = ContextFrame::Reduce {
213                current,
214                accumulator,
215            };
216        }
217    }
218
219    /// Pops the current context frame from the stack.
220    pub fn pop(&mut self) -> Option<ContextFrame> {
221        self.frames.pop()
222    }
223
224    /// Accesses data at a context level relative to current.
225    ///
226    /// # Arguments
227    ///
228    /// * `level` - The number of levels to traverse up the context stack
229    ///   - 0: returns the current context
230    ///   - N (positive or negative): goes up N levels from current
231    pub fn get_at_level(&self, level: isize) -> Option<ContextFrameRef<'_>> {
232        let levels_up = level.unsigned_abs();
233
234        if levels_up == 0 {
235            return Some(self.current());
236        }
237
238        let frame_count = self.frames.len();
239
240        if levels_up >= frame_count {
241            return Some(ContextFrameRef::Root(&self.root));
242        }
243
244        let target_index = frame_count - levels_up;
245        self.frames.get(target_index).map(ContextFrameRef::Frame)
246    }
247
248    /// Get the current context frame (top of stack)
249    pub fn current(&self) -> ContextFrameRef<'_> {
250        if let Some(frame) = self.frames.last() {
251            ContextFrameRef::Frame(frame)
252        } else {
253            ContextFrameRef::Root(&self.root)
254        }
255    }
256
257    /// Get the root context frame
258    pub fn root(&self) -> ContextFrameRef<'_> {
259        ContextFrameRef::Root(&self.root)
260    }
261
262    /// Get the current depth (number of frames)
263    pub fn depth(&self) -> usize {
264        self.frames.len()
265    }
266}