1use crate::cache::ArtifactCache;
7use crate::compiler::CachedCompiler;
8use crate::eval::{output_capture::OutputCapturer, LispEvaluator, SexprEvaluator};
9use crate::executor::SubprocessExecutor;
10use crate::metrics::EvalMetrics;
11use crate::protocol::{ReplMode, SessionId};
12use crate::session::SessionDir;
13use crate::type_inference::TypeInference;
14use crate::wrapper::RustAstWrapper;
15use oxur_smap::SourcePos;
16use std::collections::HashMap;
17use std::sync::{Arc, Mutex};
18use std::time::Instant;
19use thiserror::Error;
20
21#[derive(Debug, Error)]
26pub enum EvalError {
27 #[error("Syntax error at {pos}: {msg}")]
29 SyntaxError { msg: String, pos: SourcePos },
30
31 #[error("Type error at {pos}: {msg}")]
33 TypeError { msg: String, pos: SourcePos },
34
35 #[error("Runtime error at {pos}: {msg}")]
37 RuntimeError { msg: String, pos: SourcePos },
38
39 #[error("Compilation failed at {pos}: {msg}")]
41 CompilationError { msg: String, pos: SourcePos },
42
43 #[error("Unsupported operation at {pos}: {msg}")]
45 UnsupportedOperation { msg: String, pos: SourcePos },
46}
47
48pub type Result<T> = std::result::Result<T, EvalError>;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[non_exhaustive]
58pub enum ExecutionTier {
59 Calculator,
64
65 CachedLoaded,
70
71 JustInTime,
76}
77
78impl ExecutionTier {
79 pub fn as_label(&self) -> &'static str {
83 match self {
84 Self::Calculator => "calculator",
85 Self::CachedLoaded => "cached",
86 Self::JustInTime => "jit",
87 }
88 }
89
90 pub fn display_name(&self) -> &'static str {
92 match self {
93 Self::Calculator => "Calculator",
94 Self::CachedLoaded => "Cached",
95 Self::JustInTime => "JIT",
96 }
97 }
98}
99
100#[derive(Debug, Clone, PartialEq)]
102pub struct EvalResult {
103 pub value: String,
105
106 pub tier: ExecutionTier,
108
109 pub cached: bool,
111
112 pub duration_ms: u64,
114
115 pub stdout: Option<String>,
117
118 pub stderr: Option<String>,
120}
121
122#[derive(Debug, Clone)]
134pub struct EvalContext {
135 session_id: SessionId,
137
138 mode: ReplMode,
140
141 lisp_eval: LispEvaluator,
143
144 sexpr_eval: SexprEvaluator,
146
147 cache: HashMap<String, String>,
149
150 stats_collector: Arc<Mutex<EvalMetrics>>,
152
153 usage_metrics: Arc<Mutex<crate::metrics::UsageMetrics>>,
155
156 artifact_cache: Option<Arc<Mutex<ArtifactCache>>>,
158
159 compiler: Option<Arc<Mutex<CachedCompiler>>>,
161
162 executor: Option<Arc<Mutex<SubprocessExecutor>>>,
164
165 session_dir: Option<Arc<SessionDir>>,
167
168 wrapper: RustAstWrapper,
170
171 type_inference: TypeInference,
173
174 source_map: oxur_smap::SourceMap,
179}
180
181impl EvalContext {
182 pub fn new(session_id: SessionId, mode: ReplMode) -> Self {
193 Self {
194 session_id: session_id.clone(),
195 mode,
196 lisp_eval: LispEvaluator::new(),
197 sexpr_eval: SexprEvaluator::new(),
198 cache: HashMap::new(),
199 stats_collector: Arc::new(Mutex::new(EvalMetrics::new(session_id.as_str()))),
200 usage_metrics: Arc::new(Mutex::new(crate::metrics::UsageMetrics::new(
201 session_id.as_str(),
202 ))),
203 artifact_cache: None,
204 compiler: None,
205 executor: None,
206 session_dir: None,
207 wrapper: RustAstWrapper::new(),
208 type_inference: TypeInference::new(),
209 source_map: oxur_smap::SourceMap::new(),
210 }
211 }
212
213 pub fn with_compilation(session_id: SessionId, mode: ReplMode) -> Result<Self> {
224 let session_dir =
226 SessionDir::new(&session_id).map_err(|e| EvalError::CompilationError {
227 msg: format!("Failed to create session directory: {}", e),
228 pos: SourcePos::repl(1, 1, 1),
229 })?;
230 let session_dir = Arc::new(session_dir);
231
232 let artifact_cache = ArtifactCache::new().map_err(|e| EvalError::CompilationError {
234 msg: format!("Failed to create artifact cache: {}", e),
235 pos: SourcePos::repl(1, 1, 1),
236 })?;
237 let artifact_cache = Arc::new(Mutex::new(artifact_cache));
238
239 let cache_clone = {
241 let guard = artifact_cache.lock().expect("Artifact cache mutex poisoned");
242 (*guard).clone()
243 };
244 let compiler = CachedCompiler::new(cache_clone, Arc::clone(&session_dir));
245 let compiler = Arc::new(Mutex::new(compiler));
246
247 let executor = SubprocessExecutor::new().map_err(|e| EvalError::CompilationError {
249 msg: format!("Failed to create subprocess executor: {}", e),
250 pos: SourcePos::repl(1, 1, 1),
251 })?;
252 let executor = Arc::new(Mutex::new(executor));
253
254 Ok(Self {
255 session_id: session_id.clone(),
256 mode,
257 lisp_eval: LispEvaluator::new(),
258 sexpr_eval: SexprEvaluator::new(),
259 cache: HashMap::new(),
260 stats_collector: Arc::new(Mutex::new(EvalMetrics::new(session_id.as_str()))),
261 usage_metrics: Arc::new(Mutex::new(crate::metrics::UsageMetrics::new(
262 session_id.as_str(),
263 ))),
264 artifact_cache: Some(artifact_cache),
265 compiler: Some(compiler),
266 executor: Some(executor),
267 session_dir: Some(session_dir),
268 wrapper: RustAstWrapper::new(),
269 type_inference: TypeInference::new(),
270 source_map: oxur_smap::SourceMap::new(),
271 })
272 }
273
274 pub fn session_id(&self) -> &SessionId {
276 &self.session_id
277 }
278
279 pub fn mode(&self) -> ReplMode {
281 self.mode
282 }
283
284 pub fn stats(&self) -> (u64, u64, u64) {
289 let collector = self.stats_collector.lock().unwrap();
290 let cache_stats = collector.cache_stats();
291 let tier1_count =
293 collector.percentiles(ExecutionTier::Calculator).map(|p| p.count as u64).unwrap_or(0);
294 let tier2_count =
295 collector.percentiles(ExecutionTier::CachedLoaded).map(|p| p.count as u64).unwrap_or(0);
296 let tier3_count =
297 collector.percentiles(ExecutionTier::JustInTime).map(|p| p.count as u64).unwrap_or(0);
298 (tier1_count, tier2_count + tier3_count, cache_stats.hits)
300 }
301
302 pub fn stats_collector(&self) -> Arc<Mutex<EvalMetrics>> {
306 Arc::clone(&self.stats_collector)
307 }
308
309 pub fn usage_metrics(&self) -> Arc<Mutex<crate::metrics::UsageMetrics>> {
313 Arc::clone(&self.usage_metrics)
314 }
315
316 pub fn clone_to(&self, new_session_id: SessionId) -> Self {
324 Self {
325 session_id: new_session_id.clone(),
326 mode: self.mode,
327 lisp_eval: LispEvaluator::new(),
328 sexpr_eval: SexprEvaluator::new(),
329 cache: self.cache.clone(),
330 stats_collector: Arc::new(Mutex::new(EvalMetrics::new(new_session_id.as_str()))),
331 usage_metrics: Arc::new(Mutex::new(crate::metrics::UsageMetrics::new(
332 new_session_id.as_str(),
333 ))),
334 artifact_cache: self.artifact_cache.clone(),
335 compiler: self.compiler.clone(),
336 executor: self.executor.clone(),
337 session_dir: self.session_dir.clone(),
338 wrapper: RustAstWrapper::new(),
339 type_inference: TypeInference::new(),
340 source_map: oxur_smap::SourceMap::new(), }
342 }
343
344 pub async fn eval(&mut self, code: impl AsRef<str>) -> Result<EvalResult> {
360 let code = code.as_ref();
361 let start = Instant::now();
362
363 self.source_map = oxur_smap::SourceMap::new();
365
366 match self.try_calculator_with_errors(code) {
368 Ok(Some(result)) => {
369 let duration = start.elapsed();
371 let duration_ms = duration.as_millis() as u64;
372
373 self.stats_collector.lock().unwrap().record(
375 ExecutionTier::Calculator,
376 false,
377 duration,
378 );
379
380 return Ok(EvalResult {
381 value: result,
382 tier: ExecutionTier::Calculator,
383 cached: false,
384 duration_ms,
385 stdout: None,
386 stderr: None,
387 });
388 }
389 Err(e) => {
390 return Err(e);
392 }
393 Ok(None) => {
394 }
396 }
397
398 self.eval_tier2_with_variables(code, start).await
401 }
402
403 #[allow(dead_code)]
413 fn try_calculator(&mut self, code: &str) -> Option<String> {
414 match self.mode {
415 ReplMode::Lisp => self.lisp_eval.try_eval_calculator(code),
416 ReplMode::Sexpr => self.sexpr_eval.try_eval_calculator(code),
417 }
418 }
419
420 fn try_calculator_with_errors(&mut self, code: &str) -> Result<Option<String>> {
427 match self.mode {
428 ReplMode::Lisp => self.lisp_eval.try_eval_with_errors(code),
429 ReplMode::Sexpr => Ok(self.sexpr_eval.try_eval_calculator(code)), }
431 }
432
433 async fn eval_tier2_with_variables(
441 &mut self,
442 code: &str,
443 start: Instant,
444 ) -> Result<EvalResult> {
445 let cache_key = self.hash_code(code);
447
448 if let Some(executor) = &self.executor {
450 let is_loaded = executor.lock().expect("Executor mutex poisoned").is_loaded(&cache_key);
451
452 if is_loaded {
453 let exec_result =
455 executor.lock().expect("Executor mutex poisoned").execute(&cache_key).map_err(
456 |e| EvalError::RuntimeError {
457 msg: format!("Execution failed: {}", e),
458 pos: SourcePos::repl(1, 1, code.len() as u32),
459 },
460 )?;
461
462 let duration = start.elapsed();
463 let duration_ms = duration.as_millis() as u64;
464
465 self.stats_collector.lock().unwrap().record(
467 ExecutionTier::CachedLoaded,
468 true,
469 duration,
470 );
471
472 let result_value = match exec_result {
473 crate::executor::ExecutionResult::Success { output } => output,
474 crate::executor::ExecutionResult::RuntimeError { message } => {
475 return Err(EvalError::RuntimeError {
476 msg: message,
477 pos: SourcePos::repl(1, 1, code.len() as u32),
478 });
479 }
480 crate::executor::ExecutionResult::Panic { location, message } => {
481 return Err(EvalError::RuntimeError {
482 msg: format!("Panic at {}: {}", location, message),
483 pos: SourcePos::repl(1, 1, code.len() as u32),
484 });
485 }
486 };
487
488 self.cache.insert(cache_key, result_value.clone());
490
491 return Ok(EvalResult {
492 value: result_value,
493 tier: ExecutionTier::CachedLoaded,
494 cached: true,
495 duration_ms,
496 stdout: None,
497 stderr: None,
498 });
499 }
500 } else {
501 if let Some(cached_result) = self.cache.get(&cache_key) {
503 let duration = start.elapsed();
504 let duration_ms = duration.as_millis() as u64;
505
506 self.stats_collector.lock().unwrap().record(
508 ExecutionTier::CachedLoaded,
509 true,
510 duration,
511 );
512
513 return Ok(EvalResult {
514 value: cached_result.clone(),
515 tier: ExecutionTier::CachedLoaded,
516 cached: true,
517 duration_ms,
518 stdout: None,
519 stderr: None,
520 });
521 }
522 }
523
524 let (result, stdout, stderr) = self.compile_and_execute(code).await?;
527 let duration = start.elapsed();
528 let duration_ms = duration.as_millis() as u64;
529
530 self.stats_collector.lock().unwrap().record(ExecutionTier::JustInTime, false, duration);
532
533 self.cache.insert(cache_key, result.clone());
535
536 Ok(EvalResult {
537 value: result,
538 tier: ExecutionTier::JustInTime,
539 cached: false,
540 duration_ms,
541 stdout,
542 stderr,
543 })
544 }
545
546 async fn compile_and_execute(
558 &mut self,
559 code: &str,
560 ) -> Result<(String, Option<String>, Option<String>)> {
561 let core_forms = match self.mode {
564 ReplMode::Lisp => {
565 let forms = self.lisp_eval.parse(code).map_err(|e| EvalError::SyntaxError {
567 msg: format!("Lisp parse error: {}", e),
568 pos: SourcePos::repl(1, 1, code.len() as u32),
569 })?;
570
571 for (idx, _form) in forms.iter().enumerate() {
574 let node = oxur_smap::new_node_id();
575 let pos = oxur_smap::SourcePos::repl(1, 1, code.len() as u32);
576 self.source_map.record_surface_node(node, pos);
577
578 let _ = idx; }
582
583 forms
584 }
585 ReplMode::Sexpr => {
586 let form =
588 self.sexpr_eval.parse_to_core(code).map_err(|e| EvalError::SyntaxError {
589 msg: format!("S-expression parse error: {}", e),
590 pos: SourcePos::repl(1, 1, code.len() as u32),
591 })?;
592
593 let node = oxur_smap::new_node_id();
595 let pos = oxur_smap::SourcePos::repl(1, 1, code.len() as u32);
596 self.source_map.record_surface_node(node, pos);
597
598 vec![form]
599 }
600 };
601
602 let capturer = OutputCapturer::new();
604
605 if core_forms.is_empty() {
607 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
610
611 let result = format!("compiled(placeholder, mode: {:?})", self.mode);
612 let output = capturer.get_output();
613 return Ok((result, output.stdout_option(), output.stderr_option()));
614 }
615
616 let (compiler, executor) = match (&self.compiler, &self.executor) {
619 (Some(c), Some(e)) => (c, e),
620 _ => {
621 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
623 let result = format!("compiled(placeholder, mode: {:?})", self.mode);
624 let output = capturer.get_output();
625 return Ok((result, output.stdout_option(), output.stderr_option()));
626 }
627 };
628
629 let rust_source = match self.mode {
633 ReplMode::Lisp => {
634 let source_map = oxur_smap::SourceMap::new();
637 let mut lowerer = oxur_comp::lowering::Lowerer::new(source_map);
638 let (rust_ast, _source_map) =
639 lowerer.lower(core_forms).map_err(|e| EvalError::CompilationError {
640 msg: format!("Lowering error: {}", e),
641 pos: SourcePos::repl(1, 1, code.len() as u32),
642 })?;
643
644 let codegen = oxur_comp::codegen::CodeGenerator::new();
645 codegen.generate(&rust_ast).map_err(|e| EvalError::CompilationError {
646 msg: format!("Code generation error: {}", e),
647 pos: SourcePos::repl(1, 1, code.len() as u32),
648 })?
649 }
650 ReplMode::Sexpr => {
651 code.to_string()
653 }
654 };
655
656 let cache_key = self.hash_code(code);
658
659 let def_variables = match self.mode {
664 ReplMode::Lisp => self.lisp_eval.get_variables(),
665 ReplMode::Sexpr => vec![], };
667
668 let wrapped_code = if def_variables.is_empty() {
669 self.wrapper.wrap(&cache_key, &rust_source, Some(&self.source_map)).map_err(|e| {
671 EvalError::CompilationError {
672 msg: format!("Failed to wrap code: {}", e),
673 pos: SourcePos::repl(1, 1, code.len() as u32),
674 }
675 })?
676 } else {
677 let mut var_decls = String::new();
680 for (name, type_name) in &def_variables {
681 if let Some(value) = match self.mode {
682 ReplMode::Lisp => self.lisp_eval.get_variable_value(name),
683 ReplMode::Sexpr => None,
684 } {
685 var_decls.push_str(&format!(" let {}: {} = {};\n", name, type_name, value));
686 }
687 }
688
689 let rust_with_vars = format!("{}\n{}", var_decls, rust_source);
691
692 self.wrapper.wrap(&cache_key, &rust_with_vars, Some(&self.source_map)).map_err(|e| {
693 EvalError::CompilationError {
694 msg: format!("Failed to wrap code: {}", e),
695 pos: SourcePos::repl(1, 1, code.len() as u32),
696 }
697 })?
698 };
699
700 let lib_path = compiler
702 .lock()
703 .expect("Compiler mutex poisoned")
704 .compile(&cache_key, &wrapped_code, 2)
705 .map_err(|e| EvalError::CompilationError {
706 msg: format!("Compilation failed: {}", e),
707 pos: SourcePos::repl(1, 1, code.len() as u32),
708 })?;
709
710 executor
712 .lock()
713 .expect("Executor mutex poisoned")
714 .load_library(&lib_path, &cache_key)
715 .map_err(|e| EvalError::CompilationError {
716 msg: format!("Failed to load library: {}", e),
717 pos: SourcePos::repl(1, 1, code.len() as u32),
718 })?;
719
720 let exec_result =
722 executor.lock().expect("Executor mutex poisoned").execute(&cache_key).map_err(|e| {
723 EvalError::RuntimeError {
724 msg: format!("Execution failed: {}", e),
725 pos: SourcePos::repl(1, 1, code.len() as u32),
726 }
727 })?;
728
729 let (result, stdout, stderr) = match exec_result {
731 crate::executor::ExecutionResult::Success { output } => (output, None, None),
732 crate::executor::ExecutionResult::RuntimeError { message } => {
733 return Err(EvalError::RuntimeError {
734 msg: message,
735 pos: SourcePos::repl(1, 1, code.len() as u32),
736 });
737 }
738 crate::executor::ExecutionResult::Panic { location, message } => {
739 return Err(EvalError::RuntimeError {
740 msg: format!("Panic at {}: {}", location, message),
741 pos: SourcePos::repl(1, 1, code.len() as u32),
742 });
743 }
744 };
745
746 Ok((result, stdout, stderr))
747 }
748
749 fn hash_code(&self, code: &str) -> String {
751 use std::collections::hash_map::DefaultHasher;
753 use std::hash::{Hash, Hasher};
754
755 let mut hasher = DefaultHasher::new();
756 code.hash(&mut hasher);
757 format!("h{:x}", hasher.finish())
759 }
760
761 pub fn clear_cache(&mut self) {
763 self.cache.clear();
764 }
765
766 pub fn cache_size(&self) -> usize {
768 self.cache.len()
769 }
770
771 pub fn session_dir_stats(&self) -> Option<crate::session::DirStats> {
780 self.session_dir.as_ref().and_then(|dir| dir.get_stats().ok())
781 }
782
783 pub fn artifact_cache_stats(&self) -> Option<crate::cache::CacheStats> {
788 self.artifact_cache.as_ref().map(|cache| cache.lock().unwrap().detailed_stats())
789 }
790
791 pub fn subprocess_stats(&self) -> Option<crate::metrics::SubprocessMetricsSnapshot> {
796 self.executor.as_ref().map(|exec| exec.lock().unwrap().metrics().snapshot())
797 }
798
799 pub fn infer_types(&self, code: impl AsRef<str>) -> Vec<(String, String, bool)> {
836 self.type_inference
837 .infer_from_code(code)
838 .into_iter()
839 .map(|(name, info)| (name, info.type_name, info.is_mutable))
840 .collect()
841 }
842
843 pub fn register_variable_type(
867 &mut self,
868 name: impl Into<String>,
869 type_name: impl Into<String>,
870 is_mutable: bool,
871 ) {
872 use crate::type_inference::TypeInfo;
873 let type_info = TypeInfo::new(type_name).with_mutable(is_mutable);
874 self.type_inference.register_variable(name, type_info);
875 }
876
877 pub fn get_variable_type(&self, name: impl AsRef<str>) -> Option<String> {
902 self.type_inference.get_variable_type(name).ok().map(|info| info.type_name.clone())
903 }
904
905 pub fn get_all_variable_types(&self) -> Vec<(String, String, bool)> {
928 self.type_inference
929 .all_variables()
930 .map(|(name, info)| (name.clone(), info.type_name.clone(), info.is_mutable))
931 .collect()
932 }
933
934 pub fn clear_type_information(&mut self) {
953 self.type_inference.clear();
954 }
955}
956
957#[cfg(test)]
958mod tests {
959 use super::*;
960
961 #[test]
962 fn test_new_context() {
963 let ctx = EvalContext::new(SessionId::new("test-session"), ReplMode::Lisp);
964 assert_eq!(ctx.session_id(), &SessionId::new("test-session"));
965 assert_eq!(ctx.mode(), ReplMode::Lisp);
966
967 let (tier1, tier2, hits) = ctx.stats();
968 assert_eq!(tier1, 0);
969 assert_eq!(tier2, 0);
970 assert_eq!(hits, 0);
971 }
972
973 #[test]
974 fn test_clone_to() {
975 use std::time::Duration;
976
977 let mut ctx = EvalContext::new(SessionId::new("session-1"), ReplMode::Lisp);
978
979 ctx.stats_collector.lock().unwrap().record(
981 ExecutionTier::Calculator,
982 false,
983 Duration::from_millis(1),
984 );
985
986 ctx.cache.insert("key".to_string(), "value".to_string());
987
988 let cloned = ctx.clone_to(SessionId::new("session-2"));
989 assert_eq!(cloned.session_id(), &SessionId::new("session-2"));
990 assert_eq!(cloned.cache.len(), 1);
991
992 let (tier1, tier2, hits) = cloned.stats();
994 assert_eq!(tier1, 0);
995 assert_eq!(tier2, 0);
996 assert_eq!(hits, 0);
997 }
998
999 #[tokio::test]
1000 async fn test_eval_tier1_calculator() {
1001 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1002
1003 let result = ctx.eval("(+ 1 2)").await.unwrap();
1004 assert_eq!(result.value, "3");
1005 assert_eq!(result.tier, ExecutionTier::Calculator);
1006 assert!(!result.cached);
1007 assert!(result.duration_ms < 10); let (tier1, tier2, _) = ctx.stats();
1010 assert_eq!(tier1, 1);
1011 assert_eq!(tier2, 0);
1012 }
1013
1014 #[tokio::test]
1015 async fn test_eval_tier3_jit_compilation() {
1016 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1017
1018 let result = ctx.eval("(deffn foo (x) (* x 2))").await.unwrap();
1019 assert!(result.value.contains("compiled"));
1020 assert_eq!(result.tier, ExecutionTier::JustInTime);
1021 assert!(!result.cached);
1022
1023 let (tier1, tier2, _) = ctx.stats();
1024 assert_eq!(tier1, 0);
1025 assert_eq!(tier2, 1);
1026 }
1027
1028 #[tokio::test]
1029 async fn test_eval_caching() {
1030 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1031
1032 let result1 = ctx.eval("(deffn bar (x) x)").await.unwrap();
1034 assert!(!result1.cached);
1035 assert_eq!(result1.tier, ExecutionTier::JustInTime);
1036
1037 let result2 = ctx.eval("(deffn bar (x) x)").await.unwrap();
1039 assert!(result2.cached);
1040 assert_eq!(result2.tier, ExecutionTier::CachedLoaded);
1041 assert_eq!(result2.value, result1.value);
1042
1043 let (_, tier2, hits) = ctx.stats();
1044 assert_eq!(tier2, 2);
1045 assert_eq!(hits, 1);
1046 }
1047
1048 #[tokio::test]
1049 async fn test_cache_operations() {
1050 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1051
1052 ctx.eval("(+ x 1)").await.unwrap();
1053 assert_eq!(ctx.cache_size(), 1);
1054
1055 ctx.clear_cache();
1056 assert_eq!(ctx.cache_size(), 0);
1057 }
1058
1059 #[test]
1060 fn test_calculator_mode() {
1061 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1062
1063 assert_eq!(ctx.try_calculator("(+ 1 2)"), Some("3".to_string()));
1064 assert_eq!(ctx.try_calculator("(+ 10 20)"), Some("30".to_string()));
1065 assert_eq!(ctx.try_calculator("(deffn foo (x) x)"), None);
1066 assert_eq!(ctx.try_calculator("(+ 1 2 3)"), Some("6".to_string())); }
1068
1069 #[tokio::test]
1070 async fn test_tier3_lisp_mode() {
1071 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1072
1073 let result = ctx.eval("(deffn foo (x) x)").await.unwrap();
1075
1076 assert_eq!(result.tier, ExecutionTier::JustInTime);
1077 assert!(!result.cached);
1078 assert!(result.value.contains("mode: Lisp"));
1079 }
1080
1081 #[tokio::test]
1082 async fn test_tier3_sexpr_mode() {
1083 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Sexpr);
1084
1085 let result = ctx.eval("undefined-symbol").await.unwrap();
1088
1089 assert_eq!(result.tier, ExecutionTier::JustInTime);
1090 assert!(!result.cached);
1091 assert!(result.value.contains("placeholder"));
1093 }
1094
1095 #[tokio::test]
1096 async fn test_tier_fallback() {
1097 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1098
1099 let result1 = ctx.eval("(+ 1 2)").await.unwrap();
1101 assert_eq!(result1.tier, ExecutionTier::Calculator);
1102 assert_eq!(result1.value, "3");
1103
1104 let result2 = ctx.eval("(deffn add (a b) (+ a b))").await.unwrap();
1106 assert_eq!(result2.tier, ExecutionTier::JustInTime);
1107 }
1108
1109 #[tokio::test]
1110 async fn test_mode_specific_calculators() {
1111 let mut lisp_ctx = EvalContext::new(SessionId::new("lisp-test"), ReplMode::Lisp);
1113 let lisp_result = lisp_ctx.eval("(* 3 4)").await.unwrap();
1114 assert_eq!(lisp_result.tier, ExecutionTier::Calculator);
1115 assert_eq!(lisp_result.value, "12");
1116
1117 let mut sexpr_ctx = EvalContext::new(SessionId::new("sexpr-test"), ReplMode::Sexpr);
1119 let sexpr_result = sexpr_ctx.eval("(/ 10 2)").await.unwrap();
1120 assert_eq!(sexpr_result.tier, ExecutionTier::Calculator);
1121 assert_eq!(sexpr_result.value, "5");
1122 }
1123
1124 #[test]
1125 fn test_source_pos_creation() {
1126 let pos = SourcePos::repl(3, 15, 10);
1127 assert_eq!(pos.line, 3);
1128 assert_eq!(pos.column, 15);
1129 assert_eq!(pos.length, 10);
1130 assert_eq!(pos.file, "<repl>");
1131 }
1132
1133 #[test]
1134 fn test_source_pos_display() {
1135 let pos = SourcePos::repl(3, 15, 10);
1136 assert_eq!(pos.to_string(), "<repl>:3:15");
1137 }
1138
1139 #[test]
1140 fn test_source_pos_equality() {
1141 let pos1 = SourcePos::repl(3, 15, 10);
1142 let pos2 = SourcePos::repl(3, 15, 10);
1143 let pos3 = SourcePos::repl(4, 10, 5);
1144
1145 assert_eq!(pos1, pos2);
1146 assert_ne!(pos1, pos3);
1147 }
1148
1149 #[test]
1150 fn test_eval_error_includes_source_pos() {
1151 let err = EvalError::SyntaxError {
1152 msg: "Unexpected token".to_string(),
1153 pos: SourcePos::repl(2, 5, 1),
1154 };
1155
1156 let err_string = err.to_string();
1157 assert!(err_string.contains("<repl>:2:5"));
1158 assert!(err_string.contains("Unexpected token"));
1159 }
1160
1161 #[test]
1162 fn test_all_error_variants_include_source_pos() {
1163 let pos = SourcePos::repl(1, 1, 1);
1164
1165 let errors = vec![
1166 EvalError::SyntaxError { msg: "syntax".to_string(), pos: pos.clone() },
1167 EvalError::TypeError { msg: "type".to_string(), pos: pos.clone() },
1168 EvalError::RuntimeError { msg: "runtime".to_string(), pos: pos.clone() },
1169 EvalError::CompilationError { msg: "compilation".to_string(), pos: pos.clone() },
1170 EvalError::UnsupportedOperation { msg: "unsupported".to_string(), pos: pos.clone() },
1171 ];
1172
1173 for err in errors {
1174 let err_string = err.to_string();
1175 assert!(err_string.contains("<repl>:1:1"));
1176 }
1177 }
1178
1179 #[test]
1182 fn test_execution_tier_as_label() {
1183 assert_eq!(ExecutionTier::Calculator.as_label(), "calculator");
1184 assert_eq!(ExecutionTier::CachedLoaded.as_label(), "cached");
1185 assert_eq!(ExecutionTier::JustInTime.as_label(), "jit");
1186 }
1187
1188 #[test]
1189 fn test_execution_tier_display_name() {
1190 assert_eq!(ExecutionTier::Calculator.display_name(), "Calculator");
1191 assert_eq!(ExecutionTier::CachedLoaded.display_name(), "Cached");
1192 assert_eq!(ExecutionTier::JustInTime.display_name(), "JIT");
1193 }
1194
1195 #[test]
1196 fn test_eval_result_struct() {
1197 let result = EvalResult {
1198 value: "42".to_string(),
1199 tier: ExecutionTier::Calculator,
1200 cached: false,
1201 duration_ms: 5,
1202 stdout: Some("output".to_string()),
1203 stderr: Some("error".to_string()),
1204 };
1205
1206 assert_eq!(result.value, "42");
1207 assert_eq!(result.tier, ExecutionTier::Calculator);
1208 assert!(!result.cached);
1209 assert_eq!(result.duration_ms, 5);
1210 assert_eq!(result.stdout, Some("output".to_string()));
1211 assert_eq!(result.stderr, Some("error".to_string()));
1212 }
1213
1214 #[test]
1215 fn test_eval_result_equality() {
1216 let result1 = EvalResult {
1217 value: "42".to_string(),
1218 tier: ExecutionTier::Calculator,
1219 cached: false,
1220 duration_ms: 5,
1221 stdout: None,
1222 stderr: None,
1223 };
1224
1225 let result2 = EvalResult {
1226 value: "42".to_string(),
1227 tier: ExecutionTier::Calculator,
1228 cached: false,
1229 duration_ms: 5,
1230 stdout: None,
1231 stderr: None,
1232 };
1233
1234 let result3 = EvalResult {
1235 value: "100".to_string(),
1236 tier: ExecutionTier::JustInTime,
1237 cached: true,
1238 duration_ms: 100,
1239 stdout: None,
1240 stderr: None,
1241 };
1242
1243 assert_eq!(result1, result2);
1244 assert_ne!(result1, result3);
1245 }
1246
1247 #[test]
1248 fn test_stats_collector_accessor() {
1249 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1250 let stats = ctx.stats_collector();
1251
1252 let guard = stats.lock().unwrap();
1254 let cache_stats = guard.cache_stats();
1255 assert_eq!(cache_stats.hits, 0);
1256 assert_eq!(cache_stats.misses, 0);
1257 }
1258
1259 #[test]
1260 fn test_usage_metrics_accessor() {
1261 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1262 let usage = ctx.usage_metrics();
1263
1264 let mut guard = usage.lock().unwrap();
1266 guard.record_eval();
1267 let snapshot = guard.snapshot();
1268 assert_eq!(snapshot.eval_count, 1);
1269 }
1270
1271 #[test]
1272 fn test_session_dir_stats_not_initialized() {
1273 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1274
1275 assert!(ctx.session_dir_stats().is_none());
1277 }
1278
1279 #[test]
1280 fn test_artifact_cache_stats_not_initialized() {
1281 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1282
1283 assert!(ctx.artifact_cache_stats().is_none());
1285 }
1286
1287 #[test]
1288 fn test_subprocess_stats_not_initialized() {
1289 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1290
1291 assert!(ctx.subprocess_stats().is_none());
1293 }
1294
1295 #[test]
1296 fn test_type_inference_register_and_get() {
1297 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1298
1299 assert_eq!(ctx.get_variable_type("x"), None);
1301
1302 ctx.register_variable_type("x", "i32", false);
1304 assert_eq!(ctx.get_variable_type("x"), Some("i32".to_string()));
1305
1306 ctx.register_variable_type("y", "String", true);
1308 assert_eq!(ctx.get_variable_type("y"), Some("String".to_string()));
1309
1310 assert_eq!(ctx.get_variable_type("z"), None);
1312 }
1313
1314 #[test]
1315 fn test_type_inference_get_all_variables() {
1316 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1317
1318 ctx.register_variable_type("x", "i32", false);
1319 ctx.register_variable_type("y", "String", true);
1320
1321 let vars = ctx.get_all_variable_types();
1322 assert_eq!(vars.len(), 2);
1323
1324 let has_x = vars.iter().any(|(name, ty, mutable)| name == "x" && ty == "i32" && !mutable);
1326 let has_y =
1327 vars.iter().any(|(name, ty, mutable)| name == "y" && ty == "String" && *mutable);
1328 assert!(has_x);
1329 assert!(has_y);
1330 }
1331
1332 #[test]
1333 fn test_type_inference_clear() {
1334 let mut ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1335
1336 ctx.register_variable_type("x", "i32", false);
1337 ctx.register_variable_type("y", "String", true);
1338 assert_eq!(ctx.get_all_variable_types().len(), 2);
1339
1340 ctx.clear_type_information();
1341 assert_eq!(ctx.get_all_variable_types().len(), 0);
1342 assert_eq!(ctx.get_variable_type("x"), None);
1343 }
1344
1345 #[test]
1346 fn test_infer_types_from_code() {
1347 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1348
1349 let code = r#"
1350 fn main() {
1351 let x: i32 = 42;
1352 let mut y: String = "hello".to_string();
1353 }
1354 "#;
1355
1356 let types = ctx.infer_types(code);
1357 assert!(types.len() >= 2);
1359
1360 let x_info = types.iter().find(|(name, _, _)| name == "x");
1362 assert!(x_info.is_some());
1363 if let Some((_, ty, mutable)) = x_info {
1364 assert_eq!(ty, "i32");
1365 assert!(!mutable);
1366 }
1367
1368 let y_info = types.iter().find(|(name, _, _)| name == "y");
1370 assert!(y_info.is_some());
1371 if let Some((_, ty, mutable)) = y_info {
1372 assert_eq!(ty, "String");
1373 assert!(*mutable);
1374 }
1375 }
1376
1377 #[test]
1378 fn test_infer_types_empty_code() {
1379 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1380
1381 let types = ctx.infer_types("");
1382 assert!(types.is_empty());
1383 }
1384
1385 #[test]
1386 fn test_infer_types_no_variables() {
1387 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1388
1389 let code = "fn main() { println!(\"hello\"); }";
1390 let types = ctx.infer_types(code);
1391 assert!(types.is_empty());
1392 }
1393
1394 #[test]
1395 fn test_hash_code_consistency() {
1396 let ctx = EvalContext::new(SessionId::new("test"), ReplMode::Lisp);
1397
1398 let hash1 = ctx.hash_code("(+ 1 2)");
1400 let hash2 = ctx.hash_code("(+ 1 2)");
1401 assert_eq!(hash1, hash2);
1402
1403 let hash3 = ctx.hash_code("(+ 1 3)");
1405 assert_ne!(hash1, hash3);
1406
1407 assert!(hash1.starts_with('h'));
1409 }
1410
1411 #[test]
1412 fn test_new_context_sexpr_mode() {
1413 let ctx = EvalContext::new(SessionId::new("test-sexpr"), ReplMode::Sexpr);
1414 assert_eq!(ctx.session_id(), &SessionId::new("test-sexpr"));
1415 assert_eq!(ctx.mode(), ReplMode::Sexpr);
1416 }
1417
1418 #[test]
1419 fn test_clone_to_preserves_mode() {
1420 let ctx = EvalContext::new(SessionId::new("session-1"), ReplMode::Sexpr);
1421 let cloned = ctx.clone_to(SessionId::new("session-2"));
1422
1423 assert_eq!(cloned.mode(), ReplMode::Sexpr);
1424 }
1425
1426 #[test]
1427 fn test_execution_tier_debug() {
1428 let tier = ExecutionTier::Calculator;
1430 let debug_str = format!("{:?}", tier);
1431 assert_eq!(debug_str, "Calculator");
1432
1433 let tier = ExecutionTier::CachedLoaded;
1434 let debug_str = format!("{:?}", tier);
1435 assert_eq!(debug_str, "CachedLoaded");
1436
1437 let tier = ExecutionTier::JustInTime;
1438 let debug_str = format!("{:?}", tier);
1439 assert_eq!(debug_str, "JustInTime");
1440 }
1441
1442 #[test]
1443 fn test_execution_tier_clone() {
1444 let tier1 = ExecutionTier::Calculator;
1445 let tier2 = tier1;
1446 assert_eq!(tier1, tier2);
1447 }
1448
1449 #[test]
1450 fn test_eval_result_clone() {
1451 let result = EvalResult {
1452 value: "42".to_string(),
1453 tier: ExecutionTier::Calculator,
1454 cached: false,
1455 duration_ms: 5,
1456 stdout: Some("out".to_string()),
1457 stderr: None,
1458 };
1459
1460 let cloned = result.clone();
1461 assert_eq!(result, cloned);
1462 }
1463
1464 #[test]
1465 fn test_eval_result_debug() {
1466 let result = EvalResult {
1467 value: "42".to_string(),
1468 tier: ExecutionTier::Calculator,
1469 cached: false,
1470 duration_ms: 5,
1471 stdout: None,
1472 stderr: None,
1473 };
1474
1475 let debug_str = format!("{:?}", result);
1476 assert!(debug_str.contains("EvalResult"));
1477 assert!(debug_str.contains("42"));
1478 assert!(debug_str.contains("Calculator"));
1479 }
1480
1481 #[test]
1482 fn test_eval_error_debug() {
1483 let err = EvalError::SyntaxError { msg: "test".to_string(), pos: SourcePos::repl(1, 1, 1) };
1484
1485 let debug_str = format!("{:?}", err);
1486 assert!(debug_str.contains("SyntaxError"));
1487 }
1488}