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
193// Re-export commonly used types
194pub use ast::{Expr, Program, Stmt};
195pub use builtins::{BuiltInRegistry, IOPermissions};
196pub use cache::{ASTCache, CacheStats};
197pub use environment::Environment;
198pub use evaluator::{ErrorReport, EvalResult, Evaluator, RuntimeError};
199pub use lexer::Lexer;
200pub use module_system::{DisabledModuleResolver, FileSystemModuleResolver, ModuleResolver};
201pub use optimizer::Optimizer;
202pub use parser::{ParseError, Parser};
203pub use runtime::{
204    ExecutionLimitError, ExecutionLimits, TraceEntry, TraceFilter, TraceLevel, TraceStats,
205};
206pub use sandbox::{
207    ExecutionMetrics, MetricsCollector, MetricsSnapshot, ModuleCacheManager, ModuleCacheStats,
208    ModuleMetrics, PathRestriction, PathValidationError, PathValidator, SandboxConfig,
209    SandboxPolicy, ScopedValidator,
210};
211pub use token::Token;
212pub use value::Value;
213
214/// Main Aether engine struct
215pub struct Aether {
216    evaluator: Evaluator,
217    cache: ASTCache,
218    optimizer: Optimizer,
219}
220
221impl Aether {
222    /// Create a new Aether engine instance
223    ///
224    /// **For DSL embedding**: IO operations are disabled by default for security.
225    /// Use `with_permissions()` or `with_all_permissions()` to enable IO.
226    ///
227    /// **For CLI usage**: The command-line tool uses `with_all_permissions()` by default.
228    pub fn new() -> Self {
229        Self::with_permissions(IOPermissions::default())
230    }
231
232    /// Create a new Aether engine with custom IO permissions
233    pub fn with_permissions(permissions: IOPermissions) -> Self {
234        Aether {
235            evaluator: Evaluator::with_permissions(permissions),
236            cache: ASTCache::new(),
237            optimizer: Optimizer::new(),
238        }
239    }
240
241    /// Create a new Aether engine with all IO permissions enabled
242    pub fn with_all_permissions() -> Self {
243        Self::with_permissions(IOPermissions::allow_all())
244    }
245
246    /// Create a new Aether engine with standard library preloaded
247    ///
248    /// This creates an engine with all permissions and automatically loads
249    /// all standard library modules (string_utils, array_utils, validation, datetime, testing).
250    pub fn with_stdlib() -> Result<Self, String> {
251        let mut engine = Self::with_all_permissions();
252        stdlib::preload_stdlib(&mut engine)?;
253        Ok(engine)
254    }
255
256    /// Load a specific standard library module
257    ///
258    /// Available modules: "string_utils", "array_utils", "validation", "datetime", "testing"
259    pub fn load_stdlib_module(&mut self, module_name: &str) -> Result<(), String> {
260        if let Some(code) = stdlib::get_module(module_name) {
261            self.eval(code)?;
262            Ok(())
263        } else {
264            Err(format!("Unknown stdlib module: {}", module_name))
265        }
266    }
267
268    /// Load all standard library modules
269    pub fn load_all_stdlib(&mut self) -> Result<(), String> {
270        stdlib::preload_stdlib(self)
271    }
272
273    // ============================================================
274    // Execution Limits
275    // ============================================================
276
277    /// Create a new Aether engine with execution limits
278    pub fn with_limits(mut self, limits: ExecutionLimits) -> Self {
279        self.evaluator.set_limits(limits);
280        self
281    }
282
283    /// Set execution limits
284    pub fn set_limits(&mut self, limits: ExecutionLimits) {
285        self.evaluator.set_limits(limits);
286    }
287
288    /// Get current execution limits
289    pub fn limits(&self) -> &ExecutionLimits {
290        self.evaluator.limits()
291    }
292
293    // ============================================================
294    // Chainable stdlib module loading methods
295    // ============================================================
296
297    /// Load string utilities module (chainable)
298    pub fn with_stdlib_string_utils(mut self) -> Result<Self, String> {
299        if let Some(code) = stdlib::get_module("string_utils") {
300            self.eval(code)?;
301        }
302        Ok(self)
303    }
304
305    /// Load array utilities module (chainable)
306    pub fn with_stdlib_array_utils(mut self) -> Result<Self, String> {
307        if let Some(code) = stdlib::get_module("array_utils") {
308            self.eval(code)?;
309        }
310        Ok(self)
311    }
312
313    /// Load validation module (chainable)
314    pub fn with_stdlib_validation(mut self) -> Result<Self, String> {
315        if let Some(code) = stdlib::get_module("validation") {
316            self.eval(code)?;
317        }
318        Ok(self)
319    }
320
321    /// Load datetime module (chainable)
322    pub fn with_stdlib_datetime(mut self) -> Result<Self, String> {
323        if let Some(code) = stdlib::get_module("datetime") {
324            self.eval(code)?;
325        }
326        Ok(self)
327    }
328
329    /// Load testing framework module (chainable)
330    pub fn with_stdlib_testing(mut self) -> Result<Self, String> {
331        if let Some(code) = stdlib::get_module("testing") {
332            self.eval(code)?;
333        }
334        Ok(self)
335    }
336
337    /// Load set data structure module (chainable)
338    pub fn with_stdlib_set(mut self) -> Result<Self, String> {
339        if let Some(code) = stdlib::get_module("set") {
340            self.eval(code)?;
341        }
342        Ok(self)
343    }
344
345    /// Load queue data structure module (chainable)
346    pub fn with_stdlib_queue(mut self) -> Result<Self, String> {
347        if let Some(code) = stdlib::get_module("queue") {
348            self.eval(code)?;
349        }
350        Ok(self)
351    }
352
353    /// Load stack data structure module (chainable)
354    pub fn with_stdlib_stack(mut self) -> Result<Self, String> {
355        if let Some(code) = stdlib::get_module("stack") {
356            self.eval(code)?;
357        }
358        Ok(self)
359    }
360
361    /// Load heap data structure module (chainable)
362    pub fn with_stdlib_heap(mut self) -> Result<Self, String> {
363        if let Some(code) = stdlib::get_module("heap") {
364            self.eval(code)?;
365        }
366        Ok(self)
367    }
368
369    /// Load sorting algorithms module (chainable)
370    pub fn with_stdlib_sorting(mut self) -> Result<Self, String> {
371        if let Some(code) = stdlib::get_module("sorting") {
372            self.eval(code)?;
373        }
374        Ok(self)
375    }
376
377    /// Load JSON processing module (chainable)
378    pub fn with_stdlib_json(mut self) -> Result<Self, String> {
379        if let Some(code) = stdlib::get_module("json") {
380            self.eval(code)?;
381        }
382        Ok(self)
383    }
384
385    /// Load CSV processing module (chainable)
386    pub fn with_stdlib_csv(mut self) -> Result<Self, String> {
387        if let Some(code) = stdlib::get_module("csv") {
388            self.eval(code)?;
389        }
390        Ok(self)
391    }
392
393    /// Load functional programming utilities module (chainable)
394    pub fn with_stdlib_functional(mut self) -> Result<Self, String> {
395        if let Some(code) = stdlib::get_module("functional") {
396            self.eval(code)?;
397        }
398        Ok(self)
399    }
400
401    /// Load CLI utilities module (chainable)
402    pub fn with_stdlib_cli_utils(mut self) -> Result<Self, String> {
403        if let Some(code) = stdlib::get_module("cli_utils") {
404            self.eval(code)?;
405        }
406        Ok(self)
407    }
408
409    /// Load text template engine module (chainable)
410    pub fn with_stdlib_text_template(mut self) -> Result<Self, String> {
411        if let Some(code) = stdlib::get_module("text_template") {
412            self.eval(code)?;
413        }
414        Ok(self)
415    }
416
417    /// Load regex utilities module (chainable)
418    pub fn with_stdlib_regex_utils(mut self) -> Result<Self, String> {
419        if let Some(code) = stdlib::get_module("regex_utils") {
420            self.eval(code)?;
421        }
422        Ok(self)
423    }
424
425    /// Evaluate Aether code and return the result
426    pub fn eval(&mut self, code: &str) -> Result<Value, String> {
427        // Clear any previous call stack frames before starting a new top-level evaluation.
428        self.evaluator.clear_call_stack();
429
430        // 尝试从缓存获取AST
431        let program = if let Some(cached_program) = self.cache.get(code) {
432            cached_program
433        } else {
434            // Parse the code
435            let mut parser = Parser::new(code);
436            let program = parser
437                .parse_program()
438                .map_err(|e| format!("Parse error: {}", e))?;
439
440            // 优化AST
441            let optimized = self.optimizer.optimize_program(&program);
442
443            // 将优化后的结果存入缓存
444            self.cache.insert(code, optimized.clone());
445            optimized
446        };
447
448        // Evaluate the program
449        self.evaluator
450            .eval_program(&program)
451            .map_err(|e| format!("Runtime error: {}", e))
452    }
453
454    /// Evaluate Aether code and return a structured error report on failure.
455    ///
456    /// This is intended for integrations that need machine-readable diagnostics.
457    pub fn eval_report(&mut self, code: &str) -> Result<Value, ErrorReport> {
458        // Clear any previous call stack frames before starting a new top-level evaluation.
459        self.evaluator.clear_call_stack();
460
461        // Try AST cache first
462        let program = if let Some(cached_program) = self.cache.get(code) {
463            cached_program
464        } else {
465            let mut parser = Parser::new(code);
466            let program = parser
467                .parse_program()
468                .map_err(|e| ErrorReport::parse_error(e.to_string()))?;
469
470            let optimized = self.optimizer.optimize_program(&program);
471            self.cache.insert(code, optimized.clone());
472            optimized
473        };
474
475        self.evaluator
476            .eval_program(&program)
477            .map_err(|e| e.to_error_report())
478    }
479
480    /// Drain the in-memory TRACE buffer.
481    ///
482    /// This is designed for DSL-safe debugging: scripts call `TRACE(...)` to record
483    /// values, and the host application reads them out-of-band via this method.
484    pub fn take_trace(&mut self) -> Vec<String> {
485        self.evaluator.take_trace()
486    }
487
488    /// Clear the TRACE buffer without returning it.
489    pub fn clear_trace(&mut self) {
490        self.evaluator.clear_trace();
491    }
492
493    /// Get all structured trace entries (Stage 3.2)
494    ///
495    /// Returns a vector of structured trace entries with levels, categories, timestamps, etc.
496    pub fn trace_records(&self) -> Vec<crate::runtime::TraceEntry> {
497        self.evaluator.trace_records()
498    }
499
500    /// Filter trace entries by level (Stage 3.2)
501    ///
502    /// # Example
503    /// ```ignore
504    /// let error_traces = engine.trace_by_level(crate::runtime::TraceLevel::Error);
505    /// ```
506    pub fn trace_by_level(
507        &self,
508        level: crate::runtime::TraceLevel,
509    ) -> Vec<crate::runtime::TraceEntry> {
510        self.evaluator.trace_by_level(level)
511    }
512
513    /// Filter trace entries by category (Stage 3.2)
514    ///
515    /// # Example
516    /// ```ignore
517    /// let api_traces = engine.trace_by_category("api_call");
518    /// ```
519    pub fn trace_by_category(&self, category: &str) -> Vec<crate::runtime::TraceEntry> {
520        self.evaluator.trace_by_category(category)
521    }
522
523    /// Filter trace entries by label (Stage 3.2)
524    ///
525    /// # Example
526    /// ```ignore
527    /// let slow_traces = engine.trace_by_label("slow_request");
528    /// ```
529    pub fn trace_by_label(&self, label: &str) -> Vec<crate::runtime::TraceEntry> {
530        self.evaluator.trace_by_label(label)
531    }
532
533    /// Apply complex filter to trace entries (Stage 3.2)
534    ///
535    /// # Example
536    /// ```ignore
537    /// use crate::runtime::{TraceFilter, TraceLevel};
538    /// use std::time::Instant;
539    ///
540    /// let filter = TraceFilter::new()
541    ///     .with_min_level(TraceLevel::Warn)
542    ///     .with_category("api".to_string());
543    /// let filtered = engine.trace_filter(&filter);
544    /// ```
545    pub fn trace_filter(
546        &self,
547        filter: &crate::runtime::TraceFilter,
548    ) -> Vec<crate::runtime::TraceEntry> {
549        self.evaluator.trace_filter(filter)
550    }
551
552    /// Get trace statistics (Stage 3.2)
553    ///
554    /// Returns statistics about trace entries, including counts by level and category.
555    pub fn trace_stats(&self) -> crate::runtime::TraceStats {
556        self.evaluator.trace_stats()
557    }
558
559    /// Set TRACE buffer size (Stage 3.2)
560    ///
561    /// Note: This method is a placeholder for future implementation.
562    /// Currently, the buffer size is fixed at 1024 entries.
563    #[allow(dead_code)]
564    pub fn set_trace_buffer_size(&mut self, _size: usize) {
565        // TODO: Implement configurable buffer size
566        // For now, buffer size is fixed at TRACE_MAX_ENTRIES (1024)
567    }
568
569    /// Configure the module resolver used for `Import/Export`.
570    ///
571    /// By default (DSL embedding), the resolver is disabled for safety.
572    pub fn set_module_resolver(&mut self, resolver: Box<dyn crate::module_system::ModuleResolver>) {
573        self.evaluator.set_module_resolver(resolver);
574    }
575
576    /// Push a base directory context for resolving relative imports.
577    ///
578    /// This is typically used by a file-based runner (CLI) before calling `eval()`.
579    pub fn push_import_base(&mut self, module_id: String, base_dir: Option<std::path::PathBuf>) {
580        self.evaluator.push_import_base(module_id, base_dir);
581    }
582
583    /// Pop the most recent base directory context.
584    pub fn pop_import_base(&mut self) {
585        self.evaluator.pop_import_base();
586    }
587
588    /// Evaluate an Aether script from a file path.
589    ///
590    /// This is a convenience wrapper that:
591    /// - reads the file
592    /// - pushes an import base context (module_id = canonical path; base_dir = parent dir)
593    /// - evaluates the code
594    /// - pops the import base context
595    ///
596    /// Note: this does **not** enable any module resolver. For DSL safety, module loading
597    /// remains disabled unless you explicitly call `set_module_resolver(...)`.
598    pub fn eval_file(&mut self, path: impl AsRef<std::path::Path>) -> Result<Value, String> {
599        let path = path.as_ref();
600
601        let code = std::fs::read_to_string(path).map_err(|e| format!("IO error: {}", e))?;
602
603        let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
604        let base_dir = canon.parent().map(|p| p.to_path_buf());
605
606        self.push_import_base(canon.display().to_string(), base_dir);
607        let res = self.eval(&code);
608        self.pop_import_base();
609        res
610    }
611
612    /// Evaluate an Aether script from a file path, returning a structured error report on failure.
613    pub fn eval_file_report(
614        &mut self,
615        path: impl AsRef<std::path::Path>,
616    ) -> Result<Value, ErrorReport> {
617        let path = path.as_ref();
618
619        let code = std::fs::read_to_string(path)
620            .map_err(|e| ErrorReport::io_error(format!("IO error: {e}")))?;
621
622        let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
623        let base_dir = canon.parent().map(|p| p.to_path_buf());
624
625        self.push_import_base(canon.display().to_string(), base_dir);
626        let res = self.eval_report(&code);
627        self.pop_import_base();
628        res
629    }
630
631    /// Set a global variable from the host application without using `eval()`.
632    ///
633    /// This is useful when you already have Rust-side data and want to inject it
634    /// as `Value` into the script environment.
635    pub fn set_global(&mut self, name: &str, value: Value) {
636        self.evaluator.set_global(name.to_string(), value);
637    }
638
639    /// Reset the runtime environment (variables/functions) while keeping built-ins registered.
640    ///
641    /// Note: this clears anything that was introduced via `eval()` (including stdlib code).
642    pub fn reset_env(&mut self) {
643        self.evaluator.reset_env();
644    }
645
646    /// Run a closure inside an isolated child scope.
647    ///
648    /// All variables/functions you inject or define inside the closure will be dropped
649    /// when it returns, while the outer environment is preserved.
650    ///
651    /// This is designed for the "DSL host" scenario: inject Rust data + load per-request
652    /// Aether functions (e.g. from DB) + run the script, without cross-request pollution.
653    pub fn with_isolated_scope<R>(
654        &mut self,
655        f: impl FnOnce(&mut Aether) -> Result<R, String>,
656    ) -> Result<R, String> {
657        let prev_env = self.evaluator.enter_child_scope();
658        let result = f(self);
659        self.evaluator.restore_env(prev_env);
660        result
661    }
662
663    /// Evaluate Aether code asynchronously (requires "async" feature)
664    ///
665    /// This is a convenience wrapper around `eval()` that runs in a background task.
666    /// Useful for integrating Aether into async Rust applications.
667    ///
668    /// # Example
669    ///
670    /// ```no_run
671    /// use aether::Aether;
672    ///
673    /// #[tokio::main]
674    /// async fn main() {
675    ///     let mut engine = Aether::new();
676    ///     let result = engine.eval_async("Set X 10\n(X + 20)").await.unwrap();
677    ///     println!("Result: {}", result);
678    /// }
679    /// ```
680    #[cfg(feature = "async")]
681    pub async fn eval_async(&mut self, code: &str) -> Result<Value, String> {
682        // 由于 Aether 内部使用 Rc (非 Send),我们在当前线程执行
683        // 但通过 tokio::task::yield_now() 让出执行权,避免阻塞事件循环
684        tokio::task::yield_now().await;
685        self.eval(code)
686    }
687
688    /// 获取缓存统计信息
689    pub fn cache_stats(&self) -> CacheStats {
690        self.cache.stats()
691    }
692
693    /// 清空缓存
694    pub fn clear_cache(&mut self) {
695        self.cache.clear();
696    }
697
698    /// 设置优化选项
699    pub fn set_optimization(
700        &mut self,
701        constant_folding: bool,
702        dead_code: bool,
703        tail_recursion: bool,
704    ) {
705        self.optimizer.constant_folding = constant_folding;
706        self.optimizer.dead_code_elimination = dead_code;
707        self.optimizer.tail_recursion = tail_recursion;
708    }
709}
710
711impl Default for Aether {
712    fn default() -> Self {
713        Self::new()
714    }
715}