aether/lib.rs
1//! Aether - A lightweight, embeddable domain-specific language
2//!
3//! This crate provides a complete implementation of the Aether language,
4//! including lexer, parser, evaluator, and standard library.
5//!
6//! # Quick Start
7//!
8//! ## As a DSL (Embedded in Your Application)
9//!
10//! When embedding Aether as a DSL, IO operations are **disabled by default** for security:
11//!
12//! ```
13//! use aether::Aether;
14//!
15//! // Default: IO disabled (safe for user scripts)
16//! let mut engine = Aether::new();
17//! let code = r#"
18//! Set X 10
19//! Set Y 20
20//! (X + Y)
21//! "#;
22//!
23//! match engine.eval(code) {
24//! Ok(result) => println!("Result: {}", result),
25//! Err(e) => eprintln!("Error: {}", e),
26//! }
27//! ```
28//!
29//! Enable IO only when needed:
30//!
31//! ```
32//! use aether::{Aether, IOPermissions};
33//!
34//! // Enable only filesystem
35//! let mut perms = IOPermissions::default();
36//! perms.filesystem_enabled = true;
37//! let mut engine = Aether::with_permissions(perms);
38//!
39//! // Or enable all IO
40//! let mut engine = Aether::with_all_permissions();
41//! ```
42//!
43//! ## High-Performance Engine Modes (New!)
44//!
45//! For **high-frequency, large-scale DSL execution**, Aether provides three optimized engine modes:
46//!
47//! ### 1. GlobalEngine - Global Singleton (Best for Single-Thread)
48//!
49//! ```rust
50//! use aether::engine::GlobalEngine;
51//!
52//! // Execute with isolated environment (variables cleared each time)
53//! let result = GlobalEngine::eval_isolated("Set X 10\n(X + 20)").unwrap();
54//! println!("Result: {}", result);
55//!
56//! // Benefits:
57//! // - ✅ Maximum performance (engine created only once)
58//! // - ✅ AST cache accumulates (up to 142x speedup!)
59//! // - ✅ Environment isolation (variables cleared between calls)
60//! // - ⚠️ Single-threaded (uses Mutex)
61//! ```
62//!
63//! ### 2. EnginePool - Engine Pool (Best for Multi-Thread)
64//!
65//! ```rust
66//! use aether::engine::EnginePool;
67//! use std::thread;
68//!
69//! // Create pool once (size = 2-4x CPU cores recommended)
70//! let pool = EnginePool::new(8);
71//!
72//! // Use across threads
73//! let handles: Vec<_> = (0..4).map(|i| {
74//! let pool = pool.clone();
75//! thread::spawn(move || {
76//! let mut engine = pool.acquire(); // Auto-acquire
77//! let code = format!("Set X {}\n(X * 2)", i);
78//! engine.eval(&code)
79//! }) // Auto-return on scope exit
80//! }).collect();
81//!
82//! // Benefits:
83//! // - ✅ Multi-thread safe (lock-free queue)
84//! // - ✅ RAII pattern (auto-return to pool)
85//! // - ✅ Environment isolation (cleared on acquire)
86//! // - ✅ AST cache per engine
87//! ```
88//!
89//! ### 3. ScopedEngine - Closure Style (Best for Simplicity)
90//!
91//! ```rust
92//! use aether::engine::ScopedEngine;
93//!
94//! // Closure style (like Py3o)
95//! let result = ScopedEngine::with(|engine| {
96//! engine.eval("Set X 10")?;
97//! engine.eval("(X + 20)")
98//! }).unwrap();
99//!
100//! // Or simplified version
101//! let result = ScopedEngine::eval("Set X 10\n(X + 20)").unwrap();
102//!
103//! // Benefits:
104//! // - ✅ Complete isolation (new engine each time)
105//! // - ✅ Clean API (auto lifetime management)
106//! // - ⚠️ Lower performance (no cache reuse)
107//! ```
108//!
109//! ### Mode Comparison
110//!
111//! | Feature | GlobalEngine | EnginePool | ScopedEngine |
112//! |---------|-------------|------------|--------------|
113//! | Performance | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
114//! | Multi-thread | ❌ | ✅ | ✅ |
115//! | Isolation | ✅ | ✅ | ✅ |
116//! | AST Cache | ✅ | ✅ | ❌ |
117//! | Use Case | Single-thread high-freq | Multi-thread | Occasional |
118//!
119//! ### Selective Standard Library Loading (Recommended for DSL)
120//!
121//! For better performance, load only the stdlib modules you need:
122//!
123//! ```
124//! use aether::Aether;
125//!
126//! // Load only string and array utilities
127//! let mut engine = Aether::new()
128//! .with_stdlib_string_utils()
129//! .unwrap()
130//! .with_stdlib_array_utils()
131//! .unwrap();
132//!
133//! // Or load data structures
134//! let mut engine2 = Aether::new()
135//! .with_stdlib_set()
136//! .unwrap()
137//! .with_stdlib_queue()
138//! .unwrap()
139//! .with_stdlib_stack()
140//! .unwrap();
141//!
142//! // Available modules:
143//! // - with_stdlib_string_utils()
144//! // - with_stdlib_array_utils()
145//! // - with_stdlib_validation()
146//! // - with_stdlib_datetime()
147//! // - with_stdlib_testing()
148//! // - with_stdlib_set()
149//! // - with_stdlib_queue()
150//! // - with_stdlib_stack()
151//! // - with_stdlib_heap()
152//! // - with_stdlib_sorting()
153//! // - with_stdlib_json()
154//! // - with_stdlib_csv()
155//! // - with_stdlib_functional()
156//! // - with_stdlib_cli_utils()
157//! // - with_stdlib_text_template()
158//! // - with_stdlib_regex_utils()
159//! ```
160//!
161//! ## As a Standalone Language (Command-Line Tool)
162//!
163//! The `aether` command-line tool automatically enables all IO permissions,
164//! allowing scripts to freely use file and network operations:
165//!
166//! ```bash
167//! # All IO operations work in CLI mode
168//! aether script.aether
169//! ```
170
171pub mod ast;
172pub mod builtins;
173pub mod cache;
174pub mod engine;
175pub mod environment;
176pub mod evaluator;
177pub mod lexer;
178pub mod module_system;
179pub mod optimizer;
180pub mod parser;
181pub mod runtime;
182pub mod sandbox;
183pub mod stdlib;
184pub mod token;
185pub mod value;
186
187// FFI and language bindings
188pub mod ffi;
189
190#[cfg(target_arch = "wasm32")]
191pub mod wasm;
192
193pub mod pytranspile;
194
195// Re-export commonly used types
196pub use ast::{Expr, Program, Stmt};
197pub use builtins::{BuiltInRegistry, IOPermissions};
198pub use cache::{ASTCache, CacheStats};
199pub use environment::Environment;
200pub use evaluator::{ErrorReport, EvalResult, Evaluator, RuntimeError};
201pub use lexer::Lexer;
202pub use module_system::{DisabledModuleResolver, FileSystemModuleResolver, ModuleResolver};
203pub use optimizer::Optimizer;
204pub use parser::{ParseError, Parser};
205pub use runtime::{
206 ExecutionLimitError, ExecutionLimits, TraceEntry, TraceFilter, TraceLevel, TraceStats,
207};
208pub use sandbox::{
209 ExecutionMetrics, MetricsCollector, MetricsSnapshot, ModuleCacheManager, ModuleCacheStats,
210 ModuleMetrics, PathRestriction, PathValidationError, PathValidator, SandboxConfig,
211 SandboxPolicy, ScopedValidator,
212};
213pub use token::Token;
214pub use value::Value;
215
216/// Main Aether engine struct
217pub struct Aether {
218 evaluator: Evaluator,
219 cache: ASTCache,
220 optimizer: Optimizer,
221}
222
223impl Aether {
224 /// Create a new Aether engine instance
225 ///
226 /// **For DSL embedding**: IO operations are disabled by default for security.
227 /// Use `with_permissions()` or `with_all_permissions()` to enable IO.
228 ///
229 /// **For CLI usage**: The command-line tool uses `with_all_permissions()` by default.
230 pub fn new() -> Self {
231 Self::with_permissions(IOPermissions::default())
232 }
233
234 /// Create a new Aether engine with custom IO permissions
235 pub fn with_permissions(permissions: IOPermissions) -> Self {
236 Aether {
237 evaluator: Evaluator::with_permissions(permissions),
238 cache: ASTCache::new(),
239 optimizer: Optimizer::new(),
240 }
241 }
242
243 /// Create a new Aether engine with all IO permissions enabled
244 pub fn with_all_permissions() -> Self {
245 Self::with_permissions(IOPermissions::allow_all())
246 }
247
248 /// Create a new Aether engine with standard library preloaded
249 ///
250 /// This creates an engine with all permissions and automatically loads
251 /// all standard library modules (string_utils, array_utils, validation, datetime, testing).
252 pub fn with_stdlib() -> Result<Self, String> {
253 let mut engine = Self::with_all_permissions();
254 stdlib::preload_stdlib(&mut engine)?;
255 Ok(engine)
256 }
257
258 /// Load a specific standard library module
259 ///
260 /// Available modules: "string_utils", "array_utils", "validation", "datetime", "testing"
261 pub fn load_stdlib_module(&mut self, module_name: &str) -> Result<(), String> {
262 if let Some(code) = stdlib::get_module(module_name) {
263 self.eval(code)?;
264 Ok(())
265 } else {
266 Err(format!("Unknown stdlib module: {}", module_name))
267 }
268 }
269
270 /// Load all standard library modules
271 pub fn load_all_stdlib(&mut self) -> Result<(), String> {
272 stdlib::preload_stdlib(self)
273 }
274
275 // ============================================================
276 // Execution Limits
277 // ============================================================
278
279 /// Create a new Aether engine with execution limits
280 pub fn with_limits(mut self, limits: ExecutionLimits) -> Self {
281 self.evaluator.set_limits(limits);
282 self
283 }
284
285 /// Set execution limits
286 pub fn set_limits(&mut self, limits: ExecutionLimits) {
287 self.evaluator.set_limits(limits);
288 }
289
290 /// Get current execution limits
291 pub fn limits(&self) -> &ExecutionLimits {
292 self.evaluator.limits()
293 }
294
295 // ============================================================
296 // Chainable stdlib module loading methods
297 // ============================================================
298
299 /// Load string utilities module (chainable)
300 pub fn with_stdlib_string_utils(mut self) -> Result<Self, String> {
301 if let Some(code) = stdlib::get_module("string_utils") {
302 self.eval(code)?;
303 }
304 Ok(self)
305 }
306
307 /// Load array utilities module (chainable)
308 pub fn with_stdlib_array_utils(mut self) -> Result<Self, String> {
309 if let Some(code) = stdlib::get_module("array_utils") {
310 self.eval(code)?;
311 }
312 Ok(self)
313 }
314
315 /// Load validation module (chainable)
316 pub fn with_stdlib_validation(mut self) -> Result<Self, String> {
317 if let Some(code) = stdlib::get_module("validation") {
318 self.eval(code)?;
319 }
320 Ok(self)
321 }
322
323 /// Load datetime module (chainable)
324 pub fn with_stdlib_datetime(mut self) -> Result<Self, String> {
325 if let Some(code) = stdlib::get_module("datetime") {
326 self.eval(code)?;
327 }
328 Ok(self)
329 }
330
331 /// Load testing framework module (chainable)
332 pub fn with_stdlib_testing(mut self) -> Result<Self, String> {
333 if let Some(code) = stdlib::get_module("testing") {
334 self.eval(code)?;
335 }
336 Ok(self)
337 }
338
339 /// Load set data structure module (chainable)
340 pub fn with_stdlib_set(mut self) -> Result<Self, String> {
341 if let Some(code) = stdlib::get_module("set") {
342 self.eval(code)?;
343 }
344 Ok(self)
345 }
346
347 /// Load queue data structure module (chainable)
348 pub fn with_stdlib_queue(mut self) -> Result<Self, String> {
349 if let Some(code) = stdlib::get_module("queue") {
350 self.eval(code)?;
351 }
352 Ok(self)
353 }
354
355 /// Load stack data structure module (chainable)
356 pub fn with_stdlib_stack(mut self) -> Result<Self, String> {
357 if let Some(code) = stdlib::get_module("stack") {
358 self.eval(code)?;
359 }
360 Ok(self)
361 }
362
363 /// Load heap data structure module (chainable)
364 pub fn with_stdlib_heap(mut self) -> Result<Self, String> {
365 if let Some(code) = stdlib::get_module("heap") {
366 self.eval(code)?;
367 }
368 Ok(self)
369 }
370
371 /// Load sorting algorithms module (chainable)
372 pub fn with_stdlib_sorting(mut self) -> Result<Self, String> {
373 if let Some(code) = stdlib::get_module("sorting") {
374 self.eval(code)?;
375 }
376 Ok(self)
377 }
378
379 /// Load JSON processing module (chainable)
380 pub fn with_stdlib_json(mut self) -> Result<Self, String> {
381 if let Some(code) = stdlib::get_module("json") {
382 self.eval(code)?;
383 }
384 Ok(self)
385 }
386
387 /// Load CSV processing module (chainable)
388 pub fn with_stdlib_csv(mut self) -> Result<Self, String> {
389 if let Some(code) = stdlib::get_module("csv") {
390 self.eval(code)?;
391 }
392 Ok(self)
393 }
394
395 /// Load functional programming utilities module (chainable)
396 pub fn with_stdlib_functional(mut self) -> Result<Self, String> {
397 if let Some(code) = stdlib::get_module("functional") {
398 self.eval(code)?;
399 }
400 Ok(self)
401 }
402
403 /// Load CLI utilities module (chainable)
404 pub fn with_stdlib_cli_utils(mut self) -> Result<Self, String> {
405 if let Some(code) = stdlib::get_module("cli_utils") {
406 self.eval(code)?;
407 }
408 Ok(self)
409 }
410
411 /// Load text template engine module (chainable)
412 pub fn with_stdlib_text_template(mut self) -> Result<Self, String> {
413 if let Some(code) = stdlib::get_module("text_template") {
414 self.eval(code)?;
415 }
416 Ok(self)
417 }
418
419 /// Load regex utilities module (chainable)
420 pub fn with_stdlib_regex_utils(mut self) -> Result<Self, String> {
421 if let Some(code) = stdlib::get_module("regex_utils") {
422 self.eval(code)?;
423 }
424 Ok(self)
425 }
426
427 /// Evaluate Aether code and return the result
428 pub fn eval(&mut self, code: &str) -> Result<Value, String> {
429 // Clear any previous call stack frames before starting a new top-level evaluation.
430 self.evaluator.clear_call_stack();
431
432 // 尝试从缓存获取AST
433 let program = if let Some(cached_program) = self.cache.get(code) {
434 cached_program
435 } else {
436 // Parse the code
437 let mut parser = Parser::new(code);
438 let program = parser
439 .parse_program()
440 .map_err(|e| format!("Parse error: {}", e))?;
441
442 // 优化AST
443 let optimized = self.optimizer.optimize_program(&program);
444
445 // 将优化后的结果存入缓存
446 self.cache.insert(code, optimized.clone());
447 optimized
448 };
449
450 // Evaluate the program
451 self.evaluator
452 .eval_program(&program)
453 .map_err(|e| format!("Runtime error: {}", e))
454 }
455
456 /// Evaluate Aether code and return a structured error report on failure.
457 ///
458 /// This is intended for integrations that need machine-readable diagnostics.
459 pub fn eval_report(&mut self, code: &str) -> Result<Value, ErrorReport> {
460 // Clear any previous call stack frames before starting a new top-level evaluation.
461 self.evaluator.clear_call_stack();
462
463 // Try AST cache first
464 let program = if let Some(cached_program) = self.cache.get(code) {
465 cached_program
466 } else {
467 let mut parser = Parser::new(code);
468 let program = parser
469 .parse_program()
470 .map_err(|e| ErrorReport::parse_error(e.to_string()))?;
471
472 let optimized = self.optimizer.optimize_program(&program);
473 self.cache.insert(code, optimized.clone());
474 optimized
475 };
476
477 self.evaluator
478 .eval_program(&program)
479 .map_err(|e| e.to_error_report())
480 }
481
482 /// Drain the in-memory TRACE buffer.
483 ///
484 /// This is designed for DSL-safe debugging: scripts call `TRACE(...)` to record
485 /// values, and the host application reads them out-of-band via this method.
486 pub fn take_trace(&mut self) -> Vec<String> {
487 self.evaluator.take_trace()
488 }
489
490 /// Clear the TRACE buffer without returning it.
491 pub fn clear_trace(&mut self) {
492 self.evaluator.clear_trace();
493 }
494
495 /// Get all structured trace entries (Stage 3.2)
496 ///
497 /// Returns a vector of structured trace entries with levels, categories, timestamps, etc.
498 pub fn trace_records(&self) -> Vec<crate::runtime::TraceEntry> {
499 self.evaluator.trace_records()
500 }
501
502 /// Filter trace entries by level (Stage 3.2)
503 ///
504 /// # Example
505 /// ```ignore
506 /// let error_traces = engine.trace_by_level(crate::runtime::TraceLevel::Error);
507 /// ```
508 pub fn trace_by_level(
509 &self,
510 level: crate::runtime::TraceLevel,
511 ) -> Vec<crate::runtime::TraceEntry> {
512 self.evaluator.trace_by_level(level)
513 }
514
515 /// Filter trace entries by category (Stage 3.2)
516 ///
517 /// # Example
518 /// ```ignore
519 /// let api_traces = engine.trace_by_category("api_call");
520 /// ```
521 pub fn trace_by_category(&self, category: &str) -> Vec<crate::runtime::TraceEntry> {
522 self.evaluator.trace_by_category(category)
523 }
524
525 /// Filter trace entries by label (Stage 3.2)
526 ///
527 /// # Example
528 /// ```ignore
529 /// let slow_traces = engine.trace_by_label("slow_request");
530 /// ```
531 pub fn trace_by_label(&self, label: &str) -> Vec<crate::runtime::TraceEntry> {
532 self.evaluator.trace_by_label(label)
533 }
534
535 /// Apply complex filter to trace entries (Stage 3.2)
536 ///
537 /// # Example
538 /// ```ignore
539 /// use crate::runtime::{TraceFilter, TraceLevel};
540 /// use std::time::Instant;
541 ///
542 /// let filter = TraceFilter::new()
543 /// .with_min_level(TraceLevel::Warn)
544 /// .with_category("api".to_string());
545 /// let filtered = engine.trace_filter(&filter);
546 /// ```
547 pub fn trace_filter(
548 &self,
549 filter: &crate::runtime::TraceFilter,
550 ) -> Vec<crate::runtime::TraceEntry> {
551 self.evaluator.trace_filter(filter)
552 }
553
554 /// Get trace statistics (Stage 3.2)
555 ///
556 /// Returns statistics about trace entries, including counts by level and category.
557 pub fn trace_stats(&self) -> crate::runtime::TraceStats {
558 self.evaluator.trace_stats()
559 }
560
561 /// Set TRACE buffer size (Stage 3.2)
562 ///
563 /// Note: This method is a placeholder for future implementation.
564 /// Currently, the buffer size is fixed at 1024 entries.
565 #[allow(dead_code)]
566 pub fn set_trace_buffer_size(&mut self, _size: usize) {
567 // TODO: Implement configurable buffer size
568 // For now, buffer size is fixed at TRACE_MAX_ENTRIES (1024)
569 }
570
571 /// Configure the module resolver used for `Import/Export`.
572 ///
573 /// By default (DSL embedding), the resolver is disabled for safety.
574 pub fn set_module_resolver(&mut self, resolver: Box<dyn crate::module_system::ModuleResolver>) {
575 self.evaluator.set_module_resolver(resolver);
576 }
577
578 /// Push a base directory context for resolving relative imports.
579 ///
580 /// This is typically used by a file-based runner (CLI) before calling `eval()`.
581 pub fn push_import_base(&mut self, module_id: String, base_dir: Option<std::path::PathBuf>) {
582 self.evaluator.push_import_base(module_id, base_dir);
583 }
584
585 /// Pop the most recent base directory context.
586 pub fn pop_import_base(&mut self) {
587 self.evaluator.pop_import_base();
588 }
589
590 /// Evaluate an Aether script from a file path.
591 ///
592 /// This is a convenience wrapper that:
593 /// - reads the file
594 /// - pushes an import base context (module_id = canonical path; base_dir = parent dir)
595 /// - evaluates the code
596 /// - pops the import base context
597 ///
598 /// Note: this does **not** enable any module resolver. For DSL safety, module loading
599 /// remains disabled unless you explicitly call `set_module_resolver(...)`.
600 pub fn eval_file(&mut self, path: impl AsRef<std::path::Path>) -> Result<Value, String> {
601 let path = path.as_ref();
602
603 let code = std::fs::read_to_string(path).map_err(|e| format!("IO error: {}", e))?;
604
605 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
606 let base_dir = canon.parent().map(|p| p.to_path_buf());
607
608 self.push_import_base(canon.display().to_string(), base_dir);
609 let res = self.eval(&code);
610 self.pop_import_base();
611 res
612 }
613
614 /// Evaluate an Aether script from a file path, returning a structured error report on failure.
615 pub fn eval_file_report(
616 &mut self,
617 path: impl AsRef<std::path::Path>,
618 ) -> Result<Value, ErrorReport> {
619 let path = path.as_ref();
620
621 let code = std::fs::read_to_string(path)
622 .map_err(|e| ErrorReport::io_error(format!("IO error: {e}")))?;
623
624 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
625 let base_dir = canon.parent().map(|p| p.to_path_buf());
626
627 self.push_import_base(canon.display().to_string(), base_dir);
628 let res = self.eval_report(&code);
629 self.pop_import_base();
630 res
631 }
632
633 /// Set a global variable from the host application without using `eval()`.
634 ///
635 /// This is useful when you already have Rust-side data and want to inject it
636 /// as `Value` into the script environment.
637 pub fn set_global(&mut self, name: &str, value: Value) {
638 self.evaluator.set_global(name.to_string(), value);
639 }
640
641 /// Reset the runtime environment (variables/functions) while keeping built-ins registered.
642 ///
643 /// Note: this clears anything that was introduced via `eval()` (including stdlib code).
644 pub fn reset_env(&mut self) {
645 self.evaluator.reset_env();
646 }
647
648 /// Run a closure inside an isolated child scope.
649 ///
650 /// All variables/functions you inject or define inside the closure will be dropped
651 /// when it returns, while the outer environment is preserved.
652 ///
653 /// This is designed for the "DSL host" scenario: inject Rust data + load per-request
654 /// Aether functions (e.g. from DB) + run the script, without cross-request pollution.
655 pub fn with_isolated_scope<R>(
656 &mut self,
657 f: impl FnOnce(&mut Aether) -> Result<R, String>,
658 ) -> Result<R, String> {
659 let prev_env = self.evaluator.enter_child_scope();
660 let result = f(self);
661 self.evaluator.restore_env(prev_env);
662 result
663 }
664
665 /// Evaluate Aether code asynchronously (requires "async" feature)
666 ///
667 /// This is a convenience wrapper around `eval()` that runs in a background task.
668 /// Useful for integrating Aether into async Rust applications.
669 ///
670 /// # Example
671 ///
672 /// ```no_run
673 /// use aether::Aether;
674 ///
675 /// #[tokio::main]
676 /// async fn main() {
677 /// let mut engine = Aether::new();
678 /// let result = engine.eval_async("Set X 10\n(X + 20)").await.unwrap();
679 /// println!("Result: {}", result);
680 /// }
681 /// ```
682 #[cfg(feature = "async")]
683 pub async fn eval_async(&mut self, code: &str) -> Result<Value, String> {
684 // 由于 Aether 内部使用 Rc (非 Send),我们在当前线程执行
685 // 但通过 tokio::task::yield_now() 让出执行权,避免阻塞事件循环
686 tokio::task::yield_now().await;
687 self.eval(code)
688 }
689
690 /// 获取缓存统计信息
691 pub fn cache_stats(&self) -> CacheStats {
692 self.cache.stats()
693 }
694
695 /// 清空缓存
696 pub fn clear_cache(&mut self) {
697 self.cache.clear();
698 }
699
700 /// 设置优化选项
701 pub fn set_optimization(
702 &mut self,
703 constant_folding: bool,
704 dead_code: bool,
705 tail_recursion: bool,
706 ) {
707 self.optimizer.constant_folding = constant_folding;
708 self.optimizer.dead_code_elimination = dead_code;
709 self.optimizer.tail_recursion = tail_recursion;
710 }
711}
712
713impl Default for Aether {
714 fn default() -> Self {
715 Self::new()
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722
723 #[test]
724 fn test_aether_creation() {
725 let _engine = Aether::new();
726 }
727
728 #[test]
729 fn test_cache_usage() {
730 let mut engine = Aether::new();
731 let code = "Set X 10\nX";
732
733 // 第一次执行会解析
734 let result1 = engine.eval(code).unwrap();
735 assert_eq!(result1, Value::Number(10.0));
736
737 // 第二次执行应该使用缓存
738 let result2 = engine.eval(code).unwrap();
739 assert_eq!(result2, Value::Number(10.0));
740
741 // 检查缓存统计
742 let stats = engine.cache_stats();
743 assert_eq!(stats.hits, 1);
744 assert_eq!(stats.misses, 1);
745 }
746}