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