1use super::*;
2
3impl Interpreter {
4 pub fn new() -> Self {
5 let mut global = HashMap::new();
6
7 console::register(&mut global);
8 http::register(&mut global);
9 http_server::register(&mut global);
10 disk::register(&mut global);
11 tcp::register(&mut global);
12 int::register(&mut global);
13 float::register(&mut global);
14 string::register(&mut global);
15 list::register(&mut global);
16 map::register(&mut global);
17 char::register(&mut global);
18 byte::register(&mut global);
19
20 {
22 let mut members = HashMap::new();
23 members.insert(
24 "Ok".to_string(),
25 Value::Builtin("__ctor:Result.Ok".to_string()),
26 );
27 members.insert(
28 "Err".to_string(),
29 Value::Builtin("__ctor:Result.Err".to_string()),
30 );
31 for (name, builtin_name) in result::extra_members() {
32 members.insert(name.to_string(), Value::Builtin(builtin_name));
33 }
34 global.insert(
35 "Result".to_string(),
36 Value::Namespace {
37 name: "Result".to_string(),
38 members,
39 },
40 );
41 }
42 {
43 let mut members = HashMap::new();
44 members.insert(
45 "Some".to_string(),
46 Value::Builtin("__ctor:Option.Some".to_string()),
47 );
48 members.insert("None".to_string(), Value::None);
49 for (name, builtin_name) in option::extra_members() {
50 members.insert(name.to_string(), Value::Builtin(builtin_name));
51 }
52 global.insert(
53 "Option".to_string(),
54 Value::Namespace {
55 name: "Option".to_string(),
56 members,
57 },
58 );
59 }
60
61 let rc_global = global
62 .into_iter()
63 .map(|(k, v)| (k, Rc::new(v)))
64 .collect::<HashMap<_, _>>();
65
66 let mut record_schemas = HashMap::new();
67 record_schemas.insert(
68 "HttpResponse".to_string(),
69 vec![
70 "status".to_string(),
71 "body".to_string(),
72 "headers".to_string(),
73 ],
74 );
75 record_schemas.insert(
76 "HttpRequest".to_string(),
77 vec![
78 "method".to_string(),
79 "path".to_string(),
80 "body".to_string(),
81 "headers".to_string(),
82 ],
83 );
84 record_schemas.insert(
85 "Header".to_string(),
86 vec!["name".to_string(), "value".to_string()],
87 );
88 record_schemas.insert(
89 "Tcp.Connection".to_string(),
90 vec!["id".to_string(), "host".to_string(), "port".to_string()],
91 );
92
93 Interpreter {
94 env: vec![EnvFrame::Owned(rc_global)],
95 module_cache: HashMap::new(),
96 record_schemas,
97 call_stack: Vec::new(),
98 effect_aliases: HashMap::new(),
99 active_local_slots: None,
100 memo_fns: HashSet::new(),
101 memo_cache: HashMap::new(),
102 execution_mode: ExecutionMode::Normal,
103 recorded_effects: Vec::new(),
104 replay_effects: Vec::new(),
105 replay_pos: 0,
106 validate_replay_args: false,
107 recording_sink: None,
108 }
109 }
110
111 pub fn execution_mode(&self) -> ExecutionMode {
112 self.execution_mode
113 }
114
115 pub fn set_execution_mode_normal(&mut self) {
116 self.execution_mode = ExecutionMode::Normal;
117 self.recorded_effects.clear();
118 self.replay_effects.clear();
119 self.replay_pos = 0;
120 self.validate_replay_args = false;
121 }
122
123 pub fn start_recording(&mut self) {
124 self.execution_mode = ExecutionMode::Record;
125 self.recorded_effects.clear();
126 self.replay_effects.clear();
127 self.replay_pos = 0;
128 self.validate_replay_args = false;
129 }
130
131 pub fn start_replay(&mut self, effects: Vec<EffectRecord>, validate_args: bool) {
132 self.execution_mode = ExecutionMode::Replay;
133 self.replay_effects = effects;
134 self.replay_pos = 0;
135 self.validate_replay_args = validate_args;
136 self.recorded_effects.clear();
137 }
138
139 pub fn take_recorded_effects(&mut self) -> Vec<EffectRecord> {
140 std::mem::take(&mut self.recorded_effects)
141 }
142
143 pub fn replay_progress(&self) -> (usize, usize) {
144 (self.replay_pos, self.replay_effects.len())
145 }
146
147 pub fn ensure_replay_consumed(&self) -> Result<(), RuntimeError> {
148 if self.execution_mode == ExecutionMode::Replay
149 && self.replay_pos < self.replay_effects.len()
150 {
151 return Err(RuntimeError::ReplayUnconsumed {
152 remaining: self.replay_effects.len() - self.replay_pos,
153 });
154 }
155 Ok(())
156 }
157
158 pub fn configure_recording_sink(
159 &mut self,
160 path: std::path::PathBuf,
161 request_id: String,
162 timestamp: String,
163 program_file: String,
164 module_root: String,
165 entry_fn: String,
166 input: JsonValue,
167 ) {
168 self.recording_sink = Some(RecordingSink {
169 path,
170 request_id,
171 timestamp,
172 program_file,
173 module_root,
174 entry_fn,
175 input,
176 });
177 }
178
179 pub fn recording_sink_path(&self) -> Option<std::path::PathBuf> {
180 self.recording_sink.as_ref().map(|s| s.path.clone())
181 }
182
183 pub fn persist_recording_snapshot(&self, output: RecordedOutcome) -> Result<(), RuntimeError> {
184 let Some(sink) = &self.recording_sink else {
185 return Ok(());
186 };
187
188 let recording = SessionRecording {
189 schema_version: 1,
190 request_id: sink.request_id.clone(),
191 timestamp: sink.timestamp.clone(),
192 program_file: sink.program_file.clone(),
193 module_root: sink.module_root.clone(),
194 entry_fn: sink.entry_fn.clone(),
195 input: sink.input.clone(),
196 effects: self.recorded_effects.clone(),
197 output,
198 };
199
200 let json = session_recording_to_string_pretty(&recording);
201 std::fs::write(&sink.path, json).map_err(|e| {
202 RuntimeError::Error(format!(
203 "Cannot write recording '{}': {}",
204 sink.path.display(),
205 e
206 ))
207 })?;
208 Ok(())
209 }
210
211 pub fn enable_memo(&mut self, fns: HashSet<String>) {
213 self.memo_fns = fns;
214 }
215
216 pub fn register_effect_set(&mut self, name: String, effects: Vec<String>) {
218 self.effect_aliases.insert(name, effects);
219 }
220
221 pub(super) fn expand_effects(&self, effects: &[String]) -> Vec<String> {
223 let mut result = Vec::new();
224 for e in effects {
225 if let Some(expanded) = self.effect_aliases.get(e) {
226 result.extend(expanded.iter().cloned());
227 } else {
228 result.push(e.clone());
229 }
230 }
231 result
232 }
233
234 pub(super) fn push_env(&mut self, frame: EnvFrame) {
238 self.env.push(frame);
239 }
240
241 pub(super) fn pop_env(&mut self) {
242 if self.env.len() > 1 {
243 self.env.pop();
244 }
245 }
246
247 pub(super) fn last_owned_scope_mut(
248 &mut self,
249 ) -> Result<&mut HashMap<String, Rc<Value>>, RuntimeError> {
250 let frame = self
251 .env
252 .last_mut()
253 .ok_or_else(|| RuntimeError::Error("No active scope".to_string()))?;
254 match frame {
255 EnvFrame::Owned(scope) => Ok(scope),
256 EnvFrame::Shared(_) | EnvFrame::Slots(_) => Err(RuntimeError::Error(
257 "Cannot define name in non-owned frame".to_string(),
258 )),
259 }
260 }
261
262 pub(super) fn lookup_rc(&self, name: &str) -> Result<&Rc<Value>, RuntimeError> {
263 for frame in self.env.iter().rev() {
264 let found = match frame {
265 EnvFrame::Owned(scope) => scope.get(name),
266 EnvFrame::Shared(scope) => scope.get(name),
267 EnvFrame::Slots(_) => None,
269 };
270 if let Some(v) = found {
271 return Ok(v);
272 }
273 }
274 Err(RuntimeError::Error(format!(
275 "Undefined variable: '{}'",
276 name
277 )))
278 }
279
280 pub(super) fn global_scope_clone(&self) -> Result<HashMap<String, Rc<Value>>, RuntimeError> {
281 let frame = self
282 .env
283 .first()
284 .ok_or_else(|| RuntimeError::Error("No global scope".to_string()))?;
285 match frame {
286 EnvFrame::Owned(scope) => Ok(scope.clone()),
287 EnvFrame::Shared(scope) => Ok((**scope).clone()),
288 EnvFrame::Slots(_) => Err(RuntimeError::Error(
289 "Invalid global scope frame: Slots".to_string(),
290 )),
291 }
292 }
293
294 pub fn lookup(&self, name: &str) -> Result<Value, RuntimeError> {
295 self.lookup_rc(name).map(|rc| (**rc).clone())
296 }
297
298 pub fn define(&mut self, name: String, val: Value) {
299 if let Ok(scope) = self.last_owned_scope_mut() {
300 scope.insert(name, Rc::new(val));
301 }
302 }
303
304 pub(super) fn lookup_slot(&self, slot: u16) -> Result<Value, RuntimeError> {
306 let idx = self.env.len() - 1;
307 match &self.env[idx] {
308 EnvFrame::Slots(v) => Ok(v[slot as usize].as_ref().clone()),
309 _ => {
310 Err(RuntimeError::Error(
312 "Resolved lookup on non-Slots frame".to_string(),
313 ))
314 }
315 }
316 }
317
318 pub(super) fn define_slot(&mut self, slot: u16, val: Value) {
320 let idx = self.env.len() - 1;
321 if let EnvFrame::Slots(v) = &mut self.env[idx] {
322 v[slot as usize] = Rc::new(val);
323 }
324 }
325
326 pub fn define_module_path(&mut self, path: &str, val: Value) -> Result<(), RuntimeError> {
327 let parts: Vec<&str> = path.split('.').filter(|s| !s.is_empty()).collect();
328 if parts.is_empty() {
329 return Err(RuntimeError::Error("Empty module path".to_string()));
330 }
331 if parts.len() == 1 {
332 self.define(parts[0].to_string(), val);
333 return Ok(());
334 }
335
336 let scope = self.last_owned_scope_mut()?;
337 let head = parts[0];
338 let tail = &parts[1..];
339
340 if let Some(rc_existing) = scope.remove(head) {
341 let existing = Rc::try_unwrap(rc_existing).unwrap_or_else(|rc| (*rc).clone());
342 match existing {
343 Value::Namespace { name, mut members } => {
344 Self::insert_namespace_path(&mut members, tail, val)?;
345 scope.insert(
346 head.to_string(),
347 Rc::new(Value::Namespace { name, members }),
348 );
349 Ok(())
350 }
351 _ => Err(RuntimeError::Error(format!(
352 "Cannot mount module '{}': '{}' is not a namespace",
353 parts.join("."),
354 head
355 ))),
356 }
357 } else {
358 let mut members = HashMap::new();
359 Self::insert_namespace_path(&mut members, tail, val)?;
360 scope.insert(
361 head.to_string(),
362 Rc::new(Value::Namespace {
363 name: head.to_string(),
364 members,
365 }),
366 );
367 Ok(())
368 }
369 }
370
371 pub(super) fn insert_namespace_path(
372 scope: &mut HashMap<String, Value>,
373 parts: &[&str],
374 val: Value,
375 ) -> Result<(), RuntimeError> {
376 if parts.len() == 1 {
377 scope.insert(parts[0].to_string(), val);
378 return Ok(());
379 }
380
381 let head = parts[0];
382 let tail = &parts[1..];
383
384 if let Some(existing) = scope.remove(head) {
385 match existing {
386 Value::Namespace { name, mut members } => {
387 Self::insert_namespace_path(&mut members, tail, val)?;
388 scope.insert(head.to_string(), Value::Namespace { name, members });
389 Ok(())
390 }
391 _ => Err(RuntimeError::Error(format!(
392 "Cannot mount module '{}': '{}' is not a namespace",
393 parts.join("."),
394 head
395 ))),
396 }
397 } else {
398 let mut members = HashMap::new();
399 Self::insert_namespace_path(&mut members, tail, val)?;
400 scope.insert(
401 head.to_string(),
402 Value::Namespace {
403 name: head.to_string(),
404 members,
405 },
406 );
407 Ok(())
408 }
409 }
410
411 pub(super) fn module_cache_key(path: &Path) -> String {
412 canonicalize_path(path).to_string_lossy().to_string()
413 }
414
415 pub(super) fn module_decl(items: &[TopLevel]) -> Option<&Module> {
416 items.iter().find_map(|item| {
417 if let TopLevel::Module(m) = item {
418 Some(m)
419 } else {
420 None
421 }
422 })
423 }
424
425 pub(super) fn exposed_set(items: &[TopLevel]) -> Option<HashSet<String>> {
426 Self::module_decl(items).and_then(|m| {
427 if m.exposes.is_empty() {
428 None
429 } else {
430 Some(m.exposes.iter().cloned().collect())
431 }
432 })
433 }
434
435 pub(super) fn cycle_display(loading: &[String], next: &str) -> String {
436 let mut chain = loading
437 .iter()
438 .map(|key| {
439 Path::new(key)
440 .file_stem()
441 .and_then(|s| s.to_str())
442 .unwrap_or(key)
443 .to_string()
444 })
445 .collect::<Vec<_>>();
446 chain.push(
447 Path::new(next)
448 .file_stem()
449 .and_then(|s| s.to_str())
450 .unwrap_or(next)
451 .to_string(),
452 );
453 chain.join(" -> ")
454 }
455
456 pub fn load_module(
457 &mut self,
458 name: &str,
459 base_dir: &str,
460 loading: &mut Vec<String>,
461 loading_set: &mut HashSet<String>,
462 ) -> Result<Value, RuntimeError> {
463 let path = find_module_file(name, base_dir).ok_or_else(|| {
464 RuntimeError::Error(format!("Module '{}' not found in '{}'", name, base_dir))
465 })?;
466 let cache_key = Self::module_cache_key(&path);
467
468 if let Some(cached) = self.module_cache.get(&cache_key) {
469 return Ok(cached.clone());
470 }
471
472 if loading_set.contains(&cache_key) {
473 return Err(RuntimeError::Error(format!(
474 "Circular import: {}",
475 Self::cycle_display(loading, &cache_key)
476 )));
477 }
478
479 loading.push(cache_key.clone());
480 loading_set.insert(cache_key.clone());
481 let result = (|| -> Result<Value, RuntimeError> {
482 let src = std::fs::read_to_string(&path).map_err(|e| {
483 RuntimeError::Error(format!("Cannot read '{}': {}", path.display(), e))
484 })?;
485 let mut items = parse_source(&src).map_err(|e| {
486 RuntimeError::Error(format!("Parse error in '{}': {}", path.display(), e))
487 })?;
488 crate::resolver::resolve_program(&mut items);
489
490 if let Some(module) = Self::module_decl(&items) {
491 let expected = name.rsplit('.').next().unwrap_or(name);
492 if module.name != expected {
493 return Err(RuntimeError::Error(format!(
494 "Module name mismatch: expected '{}' (from '{}'), found '{}' in '{}'",
495 expected,
496 name,
497 module.name,
498 path.display()
499 )));
500 }
501 }
502
503 let mut sub = Interpreter::new();
504
505 if let Some(module) = Self::module_decl(&items) {
506 for dep_name in &module.depends {
507 let dep_ns = self.load_module(dep_name, base_dir, loading, loading_set)?;
508 sub.define_module_path(dep_name, dep_ns)?;
509 }
510 }
511
512 for item in &items {
513 if let TopLevel::EffectSet { name, effects } = item {
514 sub.register_effect_set(name.clone(), effects.clone());
515 }
516 }
517 for item in &items {
518 if let TopLevel::TypeDef(td) = item {
519 sub.register_type_def(td);
520 }
521 }
522 for item in &items {
523 if let TopLevel::FnDef(fd) = item {
524 sub.exec_fn_def(fd)?;
525 }
526 }
527 let module_globals = Rc::new(sub.global_scope_clone()?);
528
529 let exposed = Self::exposed_set(&items);
530 let mut members = HashMap::new();
531 for item in &items {
532 match item {
533 TopLevel::FnDef(fd) => {
534 let include = match &exposed {
535 Some(set) => set.contains(&fd.name),
536 None => !fd.name.starts_with('_'),
537 };
538 if include {
539 let mut val = sub.lookup(&fd.name).map_err(|_| {
540 RuntimeError::Error(format!(
541 "Failed to export '{}.{}'",
542 name, fd.name
543 ))
544 })?;
545 if let Value::Fn { home_globals, .. } = &mut val {
546 *home_globals = Some(Rc::clone(&module_globals));
547 }
548 members.insert(fd.name.clone(), val);
549 }
550 }
551 TopLevel::TypeDef(TypeDef::Sum {
552 name: type_name, ..
553 }) => {
554 let include = match &exposed {
555 Some(set) => set.contains(type_name),
556 None => !type_name.starts_with('_'),
557 };
558 if include {
559 let val = sub.lookup(type_name).map_err(|_| {
560 RuntimeError::Error(format!(
561 "Failed to export '{}.{}'",
562 name, type_name
563 ))
564 })?;
565 members.insert(type_name.clone(), val);
566 }
567 }
568 _ => {}
569 }
570 }
571
572 Ok(Value::Namespace {
573 name: name.to_string(),
574 members,
575 })
576 })();
577 loading.pop();
578 loading_set.remove(&cache_key);
579
580 match result {
581 Ok(ns) => {
582 self.module_cache.insert(cache_key, ns.clone());
583 Ok(ns)
584 }
585 Err(e) => Err(e),
586 }
587 }
588}