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);
}
}