amberjs 1.17.0

JavaScript/TypeScript runtime built with Rust and V8
Documentation
use std::path::{PathBuf, Path};
use anyhow::{Result, Context, anyhow};
use std::fs;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use rquickjs::{Value, function::{Function, Rest}, Ctx, Runtime as QjsRuntime, Context as QjsContext};

mod typescript;
mod nodejs;

/// Amber Runtime - High-performance JavaScript/TypeScript execution engine
pub struct Runtime {
    stack_size: usize,
    max_heap: usize,
    execution_count: Arc<AtomicUsize>,
    verbose: bool,
    /// Reused QuickJS runtime for performance optimization
    qjs_runtime: QjsRuntime,
    /// Reused QuickJS context for performance optimization
    qjs_context: QjsContext,
}

impl Runtime {
    /// Create a new Amber runtime instance
    pub fn new(
        stack_size: usize,
        max_heap: usize,
        verbose: bool,
    ) -> Result<Self> {
        if verbose {
            println!("Runtime created with:");
            println!("  Stack size: {} bytes", stack_size);
            println!("  Max heap: {} bytes", max_heap);
            println!("  QuickJS Engine: Initializing...");
        }

        // Create a reusable QuickJS runtime for performance
        let qjs_runtime = QjsRuntime::new()
            .map_err(|e| anyhow!("Failed to create QuickJS runtime: {}", e))?;

        // Create a reusable context for performance optimization
        let qjs_context = QjsContext::full(&qjs_runtime)
            .map_err(|e| anyhow!("Failed to create QuickJS context: {}", e))?;

        Ok(Self {
            stack_size,
            max_heap,
            execution_count: Arc::new(AtomicUsize::new(0)),
            verbose,
            qjs_runtime,
            qjs_context,
        })
    }

    /// Execute a JavaScript/TypeScript file
    pub fn execute_file(&self, path: &PathBuf) -> Result<String> {
        if self.verbose {
            println!("Executing file: {}", path.display());
        }

        let code = fs::read_to_string(path)
            .context(format!("Failed to read file: {}", path.display()))?;

        let base_dir = path.parent()
            .unwrap_or_else(|| Path::new("."))
            .to_path_buf();

        self.execute_code_with_context(&code, base_dir)
    }

    /// Execute JavaScript/TypeScript code with a specific base directory
    fn execute_code_with_context(&self, code: &str, _base_dir: PathBuf) -> Result<String> {
        if self.verbose {
            println!("Executing code: {} bytes", code.len());
        }

        // Check if this is TypeScript code
        let is_typescript = code.contains(':')
            || code.contains("interface ")
            || code.contains("enum ")
            || code.contains("type ")
            || code.contains("namespace ");

        let code_to_execute = if is_typescript {
            // Compile TypeScript to JavaScript
            if self.verbose {
                println!("Detected TypeScript code, compiling to JavaScript...");
            }
            let mut compiler = typescript::TypeScriptCompiler::new();
            match compiler.compile(code) {
                Ok(js_code) => {
                    if self.verbose {
                        println!("TypeScript compilation successful");
                    }
                    js_code
                }
                Err(e) => {
                    return Err(anyhow!("TypeScript compilation error: {}", e));
                }
            }
        } else {
            code.to_string()
        };

        // Reuse the existing QuickJS runtime and context for better performance
        // Note: Module system is initialized in nodejs::setup_nodejs_apis()

        // Execute in the context
        self.qjs_context.with(|ctx| {
            // Set up Node.js compatibility APIs
            nodejs::setup_nodejs_apis(&ctx)?;

            // Set up complete console API
            let console = rquickjs::Object::new(ctx.clone())?;

            // console.log - standard output
            let console_log = Function::new(ctx.clone(), |_this: Ctx, args: Rest<Value>| {
                let mut output = String::new();
                for (i, arg) in args.iter().enumerate() {
                    if i > 0 {
                        output.push(' ');
                    }
                    // Convert Value to string manually
                    output.push_str(&format!("{:?}", arg));
                }
                println!("{}", output);
                // Explicitly return undefined to match JS console.log behavior
                rquickjs::Undefined
            }).map_err(|e| anyhow!("Failed to create console.log: {}", e))?;

            // console.error - error output (stderr)
            let console_error = Function::new(ctx.clone(), |_this: Ctx, args: Rest<Value>| {
                let mut output = String::new();
                for (i, arg) in args.iter().enumerate() {
                    if i > 0 {
                        output.push(' ');
                    }
                    output.push_str(&format!("{:?}", arg));
                }
                eprintln!("{}", output);
                rquickjs::Undefined
            }).map_err(|e| anyhow!("Failed to create console.error: {}", e))?;

            // console.warn - warning output (stderr)
            let console_warn = Function::new(ctx.clone(), |_this: Ctx, args: Rest<Value>| {
                let mut output = String::new();
                for (i, arg) in args.iter().enumerate() {
                    if i > 0 {
                        output.push(' ');
                    }
                    output.push_str(&format!("{:?}", arg));
                }
                eprintln!("{}", output);
                rquickjs::Undefined
            }).map_err(|e| anyhow!("Failed to create console.warn: {}", e))?;

            // console.info - info output (stdout, same as log)
            let console_info = Function::new(ctx.clone(), |_this: Ctx, args: Rest<Value>| {
                let mut output = String::new();
                for (i, arg) in args.iter().enumerate() {
                    if i > 0 {
                        output.push(' ');
                    }
                    output.push_str(&format!("{:?}", arg));
                }
                println!("{}", output);
                rquickjs::Undefined
            }).map_err(|e| anyhow!("Failed to create console.info: {}", e))?;

            // console.debug - debug output (always outputs, simple implementation)
            let console_debug = Function::new(ctx.clone(), |_this: Ctx, args: Rest<Value>| {
                let mut output = String::new();
                for (i, arg) in args.iter().enumerate() {
                    if i > 0 {
                        output.push(' ');
                    }
                    output.push_str(&format!("{:?}", arg));
                }
                println!("[DEBUG] {}", output);
                rquickjs::Undefined
            }).map_err(|e| anyhow!("Failed to create console.debug: {}", e))?;

            // Set all console methods
            console.set("log", console_log)?;
            console.set("error", console_error)?;
            console.set("warn", console_warn)?;
            console.set("info", console_info)?;
            console.set("debug", console_debug)?;

            ctx.globals().set("console", console)?;

            // Evaluate the code
            let result: Result<Option<Value>, _> = ctx.eval(&*code_to_execute);

            match result {
                Ok(result) => {
                    // Increment execution count
                    self.execution_count.fetch_add(1, Ordering::SeqCst);

                    if self.verbose {
                        println!("Execution completed successfully");
                    }

                    // Convert result to string (improved formatting)
                    let result_str = match result {
                        Some(v) => {
                            // Use a more concise Debug format that removes the type wrapper
                            let mut s = format!("{:?}", v);
                            // Remove "String(" prefix and ")" suffix if present
                            if s.starts_with("String(") && s.ends_with(')') {
                                s = s[7..s.len() - 1].to_string();
                            }
                            // Remove "Int(" prefix and ")" suffix if present
                            else if s.starts_with("Int(") && s.ends_with(')') {
                                s = s[4..s.len() - 1].to_string();
                            }
                            // Remove "Bool(" prefix and ")" suffix if present
                            else if s.starts_with("Bool(") && s.ends_with(')') {
                                s = s[5..s.len() - 1].to_string();
                            }
                            s
                        }
                        None => "undefined".to_string(),
                    };

                    Ok(result_str)
                }
                Err(e) => {
                    Err(anyhow!("JavaScript execution error: {}", e))
                }
            }
        })
    }

    /// Execute JavaScript/TypeScript code
    pub fn execute_code(&self, code: &str) -> Result<String> {
        let base_dir = PathBuf::from(".");
        self.execute_code_with_context(code, base_dir)
    }

    /// Get execution count
    pub fn execution_count(&self) -> usize {
        self.execution_count.load(Ordering::SeqCst)
    }

    /// Check if runtime is initialized
    pub fn is_initialized(&self) -> bool {
        true
    }
}

impl Drop for Runtime {
    fn drop(&mut self) {
        if self.verbose {
            let count = self.execution_count.load(Ordering::SeqCst);
            println!("Runtime shutting down. Total executions: {}", count);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::NamedTempFile;
    use std::io::Write;

    #[test]
    fn test_runtime_creation() {
        let runtime = Runtime::new(67108864, 1073741824, false);
        assert!(runtime.is_ok());
        assert!(runtime.unwrap().is_initialized());
    }

    #[test]
    fn test_simple_code_execution() {
        let runtime = Runtime::new(67108864, 1073741824, false).unwrap();
        let result = runtime.execute_code("1 + 1");
        assert!(result.is_ok());
        assert!(result.unwrap().contains("2"));
    }

    #[test]
    fn test_file_execution() {
        let runtime = Runtime::new(67108864, 1073741824, false).unwrap();

        // Create a temporary file with JavaScript code
        let mut file = NamedTempFile::new().unwrap();
        writeln!(file, "const x = 42; x * 2;").unwrap();

        let result = runtime.execute_file(&file.path().to_path_buf());
        assert!(result.is_ok());
        assert!(result.unwrap().contains("84"));
    }

    #[test]
    fn test_execution_count() {
        let runtime = Runtime::new(67108864, 1073741824, false).unwrap();
        assert_eq!(runtime.execution_count(), 0);

        runtime.execute_code("1").unwrap();
        assert_eq!(runtime.execution_count(), 1);

        runtime.execute_code("2").unwrap();
        assert_eq!(runtime.execution_count(), 2);
    }
}