1use std::cell::{Cell, RefCell};
2use std::collections::{BTreeMap, HashMap};
3use std::path::PathBuf;
4
5use crate::{CallFrame, Env, Sandbox, SemaError, Span, SpanMap, StackTrace, Value};
6
7const MAX_SPAN_TABLE_ENTRIES: usize = 200_000;
8
9pub type EvalCallbackFn = fn(&EvalContext, &Value, &Env) -> Result<Value, SemaError>;
11
12pub type CallCallbackFn = fn(&EvalContext, &Value, &[Value]) -> Result<Value, SemaError>;
14
15pub struct EvalContext {
16 pub module_cache: RefCell<BTreeMap<PathBuf, BTreeMap<String, Value>>>,
17 pub current_file: RefCell<Vec<PathBuf>>,
18 pub module_exports: RefCell<Vec<Option<Vec<String>>>>,
19 pub module_load_stack: RefCell<Vec<PathBuf>>,
20 pub call_stack: RefCell<Vec<CallFrame>>,
21 pub span_table: RefCell<HashMap<usize, Span>>,
22 pub eval_depth: Cell<usize>,
23 pub eval_step_limit: Cell<usize>,
24 pub eval_steps: Cell<usize>,
25 pub sandbox: Sandbox,
26 pub user_context: RefCell<Vec<BTreeMap<Value, Value>>>,
27 pub hidden_context: RefCell<Vec<BTreeMap<Value, Value>>>,
28 pub context_stacks: RefCell<BTreeMap<Value, Vec<Value>>>,
29 pub eval_fn: Cell<Option<EvalCallbackFn>>,
30 pub call_fn: Cell<Option<CallCallbackFn>>,
31}
32
33impl EvalContext {
34 pub fn new() -> Self {
35 EvalContext {
36 module_cache: RefCell::new(BTreeMap::new()),
37 current_file: RefCell::new(Vec::new()),
38 module_exports: RefCell::new(Vec::new()),
39 module_load_stack: RefCell::new(Vec::new()),
40 call_stack: RefCell::new(Vec::new()),
41 span_table: RefCell::new(HashMap::new()),
42 eval_depth: Cell::new(0),
43 eval_step_limit: Cell::new(0),
44 eval_steps: Cell::new(0),
45 sandbox: Sandbox::allow_all(),
46 user_context: RefCell::new(vec![BTreeMap::new()]),
47 hidden_context: RefCell::new(vec![BTreeMap::new()]),
48 context_stacks: RefCell::new(BTreeMap::new()),
49 eval_fn: Cell::new(None),
50 call_fn: Cell::new(None),
51 }
52 }
53
54 pub fn new_with_sandbox(sandbox: Sandbox) -> Self {
55 EvalContext {
56 module_cache: RefCell::new(BTreeMap::new()),
57 current_file: RefCell::new(Vec::new()),
58 module_exports: RefCell::new(Vec::new()),
59 module_load_stack: RefCell::new(Vec::new()),
60 call_stack: RefCell::new(Vec::new()),
61 span_table: RefCell::new(HashMap::new()),
62 eval_depth: Cell::new(0),
63 eval_step_limit: Cell::new(0),
64 eval_steps: Cell::new(0),
65 sandbox,
66 user_context: RefCell::new(vec![BTreeMap::new()]),
67 hidden_context: RefCell::new(vec![BTreeMap::new()]),
68 context_stacks: RefCell::new(BTreeMap::new()),
69 eval_fn: Cell::new(None),
70 call_fn: Cell::new(None),
71 }
72 }
73
74 pub fn push_file_path(&self, path: PathBuf) {
75 self.current_file.borrow_mut().push(path);
76 }
77
78 pub fn pop_file_path(&self) {
79 self.current_file.borrow_mut().pop();
80 }
81
82 pub fn current_file_dir(&self) -> Option<PathBuf> {
83 self.current_file
84 .borrow()
85 .last()
86 .and_then(|p| p.parent().map(|d| d.to_path_buf()))
87 }
88
89 pub fn current_file_path(&self) -> Option<PathBuf> {
90 self.current_file.borrow().last().cloned()
91 }
92
93 pub fn get_cached_module(&self, path: &PathBuf) -> Option<BTreeMap<String, Value>> {
94 self.module_cache.borrow().get(path).cloned()
95 }
96
97 pub fn cache_module(&self, path: PathBuf, exports: BTreeMap<String, Value>) {
98 self.module_cache.borrow_mut().insert(path, exports);
99 }
100
101 pub fn set_module_exports(&self, names: Vec<String>) {
102 let mut stack = self.module_exports.borrow_mut();
103 if let Some(top) = stack.last_mut() {
104 *top = Some(names);
105 }
106 }
107
108 pub fn clear_module_exports(&self) {
109 self.module_exports.borrow_mut().push(None);
110 }
111
112 pub fn take_module_exports(&self) -> Option<Vec<String>> {
113 self.module_exports.borrow_mut().pop().flatten()
114 }
115
116 pub fn begin_module_load(&self, path: &PathBuf) -> Result<(), SemaError> {
117 let mut stack = self.module_load_stack.borrow_mut();
118 if let Some(pos) = stack.iter().position(|p| p == path) {
119 let mut cycle: Vec<String> = stack[pos..]
120 .iter()
121 .map(|p| p.display().to_string())
122 .collect();
123 cycle.push(path.display().to_string());
124 return Err(SemaError::eval(format!(
125 "cyclic import detected: {}",
126 cycle.join(" -> ")
127 )));
128 }
129 stack.push(path.clone());
130 Ok(())
131 }
132
133 pub fn end_module_load(&self, path: &PathBuf) {
134 let mut stack = self.module_load_stack.borrow_mut();
135 if matches!(stack.last(), Some(last) if last == path) {
136 stack.pop();
137 } else if let Some(pos) = stack.iter().rposition(|p| p == path) {
138 stack.remove(pos);
139 }
140 }
141
142 pub fn push_call_frame(&self, frame: CallFrame) {
143 self.call_stack.borrow_mut().push(frame);
144 }
145
146 pub fn call_stack_depth(&self) -> usize {
147 self.call_stack.borrow().len()
148 }
149
150 pub fn truncate_call_stack(&self, depth: usize) {
151 self.call_stack.borrow_mut().truncate(depth);
152 }
153
154 pub fn capture_stack_trace(&self) -> StackTrace {
155 let stack = self.call_stack.borrow();
156 StackTrace(stack.iter().rev().cloned().collect())
157 }
158
159 pub fn merge_span_table(&self, spans: SpanMap) {
160 let mut table = self.span_table.borrow_mut();
161 if table.len() < MAX_SPAN_TABLE_ENTRIES {
162 table.extend(spans);
163 }
164 }
166
167 pub fn lookup_span(&self, ptr: usize) -> Option<Span> {
168 self.span_table.borrow().get(&ptr).cloned()
169 }
170
171 pub fn set_eval_step_limit(&self, limit: usize) {
172 self.eval_step_limit.set(limit);
173 }
174
175 pub fn context_get(&self, key: &Value) -> Option<Value> {
178 let frames = self.user_context.borrow();
179 for frame in frames.iter().rev() {
180 if let Some(v) = frame.get(key) {
181 return Some(v.clone());
182 }
183 }
184 None
185 }
186
187 pub fn context_set(&self, key: Value, value: Value) {
188 let mut frames = self.user_context.borrow_mut();
189 if let Some(top) = frames.last_mut() {
190 top.insert(key, value);
191 }
192 }
193
194 pub fn context_has(&self, key: &Value) -> bool {
195 let frames = self.user_context.borrow();
196 frames.iter().any(|frame| frame.contains_key(key))
197 }
198
199 pub fn context_remove(&self, key: &Value) -> Option<Value> {
200 let mut frames = self.user_context.borrow_mut();
201 let mut first_found = None;
202 for frame in frames.iter_mut().rev() {
203 if let Some(v) = frame.remove(key) {
204 if first_found.is_none() {
205 first_found = Some(v);
206 }
207 }
208 }
209 first_found
210 }
211
212 pub fn context_all(&self) -> BTreeMap<Value, Value> {
213 let frames = self.user_context.borrow();
214 let mut merged = BTreeMap::new();
215 for frame in frames.iter() {
216 for (k, v) in frame {
217 merged.insert(k.clone(), v.clone());
218 }
219 }
220 merged
221 }
222
223 pub fn context_push_frame(&self) {
224 self.user_context.borrow_mut().push(BTreeMap::new());
225 }
226
227 pub fn context_push_frame_with(&self, bindings: BTreeMap<Value, Value>) {
228 self.user_context.borrow_mut().push(bindings);
229 }
230
231 pub fn context_pop_frame(&self) {
232 let mut frames = self.user_context.borrow_mut();
233 if frames.len() > 1 {
234 frames.pop();
235 }
236 }
237
238 pub fn context_clear(&self) {
239 let mut frames = self.user_context.borrow_mut();
240 frames.clear();
241 frames.push(BTreeMap::new());
242 }
243
244 pub fn hidden_get(&self, key: &Value) -> Option<Value> {
247 let frames = self.hidden_context.borrow();
248 for frame in frames.iter().rev() {
249 if let Some(v) = frame.get(key) {
250 return Some(v.clone());
251 }
252 }
253 None
254 }
255
256 pub fn hidden_set(&self, key: Value, value: Value) {
257 let mut frames = self.hidden_context.borrow_mut();
258 if let Some(top) = frames.last_mut() {
259 top.insert(key, value);
260 }
261 }
262
263 pub fn hidden_has(&self, key: &Value) -> bool {
264 let frames = self.hidden_context.borrow();
265 frames.iter().any(|frame| frame.contains_key(key))
266 }
267
268 pub fn hidden_push_frame(&self) {
269 self.hidden_context.borrow_mut().push(BTreeMap::new());
270 }
271
272 pub fn hidden_pop_frame(&self) {
273 let mut frames = self.hidden_context.borrow_mut();
274 if frames.len() > 1 {
275 frames.pop();
276 }
277 }
278
279 pub fn context_stack_push(&self, key: Value, value: Value) {
282 self.context_stacks
283 .borrow_mut()
284 .entry(key)
285 .or_default()
286 .push(value);
287 }
288
289 pub fn context_stack_get(&self, key: &Value) -> Vec<Value> {
290 self.context_stacks
291 .borrow()
292 .get(key)
293 .cloned()
294 .unwrap_or_default()
295 }
296
297 pub fn context_stack_pop(&self, key: &Value) -> Option<Value> {
298 let mut stacks = self.context_stacks.borrow_mut();
299 let stack = stacks.get_mut(key)?;
300 let val = stack.pop();
301 if stack.is_empty() {
302 stacks.remove(key);
303 }
304 val
305 }
306}
307
308impl Default for EvalContext {
309 fn default() -> Self {
310 Self::new()
311 }
312}
313
314thread_local! {
315 static STDLIB_CTX: EvalContext = EvalContext::new();
316}
317
318pub fn with_stdlib_ctx<F, R>(f: F) -> R
321where
322 F: FnOnce(&EvalContext) -> R,
323{
324 STDLIB_CTX.with(f)
325}
326
327pub fn set_eval_callback(ctx: &EvalContext, f: EvalCallbackFn) {
331 ctx.eval_fn.set(Some(f));
332 STDLIB_CTX.with(|stdlib| stdlib.eval_fn.set(Some(f)));
333}
334
335pub fn set_call_callback(ctx: &EvalContext, f: CallCallbackFn) {
338 ctx.call_fn.set(Some(f));
339 STDLIB_CTX.with(|stdlib| stdlib.call_fn.set(Some(f)));
340}
341
342pub fn eval_callback(ctx: &EvalContext, expr: &Value, env: &Env) -> Result<Value, SemaError> {
345 let f = ctx
346 .eval_fn
347 .get()
348 .expect("eval callback not registered — Interpreter::new() must be called first");
349 f(ctx, expr, env)
350}
351
352pub fn call_callback(ctx: &EvalContext, func: &Value, args: &[Value]) -> Result<Value, SemaError> {
355 let f = ctx
356 .call_fn
357 .get()
358 .expect("call callback not registered — Interpreter::new() must be called first");
359 f(ctx, func, args)
360}