diff --git a/lang/examples/os_exec.modu b/lang/examples/os_exec.modu new file mode 100644 index 0000000..ebcc2c0 --- /dev/null +++ b/lang/examples/os_exec.modu @@ -0,0 +1,3 @@ +import "os" as os; + +print(os.exec("mkdir test")); diff --git a/lang/src/packages/mod.rs b/lang/src/packages/mod.rs index 77e534f..a943077 100644 --- a/lang/src/packages/mod.rs +++ b/lang/src/packages/mod.rs @@ -1,75 +1,94 @@ mod math; mod file; mod time; +mod os; use crate::ast::AST; pub fn get_package(name: &str) -> Option { - match name { - "math" => { - Some(AST::Object { - properties: math::get_object(), - line: 0, - }) - } - - "time" => { - Some(AST::Object { - properties: time::get_object(), - line: 0, - }) - } - - "file" => { - let args = std::env::args().collect::>(); - - if args.len() > 1 && args[1] == "server" { // fallback incase the stop in eval.rs explodes for some reason - return None; - } - - Some(AST::Object { - properties: file::get_object(), - line: 0, - }) - } - - _ => None - } + match name { + "math" => { + Some(AST::Object { + properties: math::get_object(), + line: 0, + }) + } + + "time" => { + Some(AST::Object { + properties: time::get_object(), + line: 0, + }) + } + + "file" => { + let args = std::env::args().collect::>(); + + if args.len() > 1 && args[1] == "server" { // fallback incase the stop in eval.rs explodes for some reason + return None; + } + + Some(AST::Object { + properties: file::get_object(), + line: 0, + }) + } + + "os" => Some(AST::Object { + properties: os::get_object(), + line: 0 + }), + + _ => None + } } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn get_math_package() { - let math = get_package("math").unwrap(); - match math { - AST::Object { properties, line: _ } => { - assert_eq!(properties.len(), 10); - assert_eq!(properties.contains_key("div"), true); - } - _ => panic!("Expected AST::Object") - } - } - - #[test] - fn get_file_package() { - let file = get_package("file").unwrap(); - - match file { - AST::Object { properties, line: _ } => { - assert_eq!(properties.len(), 3); - assert_eq!(properties.contains_key("write_append"), true); - } - - _ => panic!("Expected AST::Object") - } - } - - #[test] - fn get_unknown_package() { - let unknown = get_package("unknown"); - assert_eq!(unknown, None); - } + use super::*; + + #[test] + fn get_math_package() { + let math = get_package("math").unwrap(); + match math { + AST::Object { properties, line: _ } => { + assert_eq!(properties.len(), 10); + assert_eq!(properties.contains_key("div"), true); + } + _ => panic!("Expected AST::Object") + } + } + + #[test] + fn get_file_package() { + let file = get_package("file").unwrap(); + + match file { + AST::Object { properties, line: _ } => { + assert_eq!(properties.len(), 3); + assert_eq!(properties.contains_key("write_append"), true); + } + + _ => panic!("Expected AST::Object") + } + } + + #[test] + fn get_unknown_package() { + let unknown = get_package("unknown"); + assert_eq!(unknown, None); + } + + #[test] + fn get_os_package() { + let os = get_package("os").unwrap(); + match os { + AST::Object { properties, line: _ } => { + assert_eq!(properties.len(), 1); + assert_eq!(properties.contains_key("exec"), true); + } + _ => panic!("Expected AST::Object") + } + } + } \ No newline at end of file diff --git a/lang/src/packages/os.rs b/lang/src/packages/os.rs new file mode 100644 index 0000000..b457ffe --- /dev/null +++ b/lang/src/packages/os.rs @@ -0,0 +1,86 @@ +use std::collections::HashMap; +use std::process::Command; +use crate::ast::AST; +use std::os::windows::process::CommandExt; + +fn clean_command(cmd: &str) -> String { + let clean = cmd.trim() + .trim_matches('"') + .trim_matches('\'') + .to_string(); + + return clean; +} + +pub fn exec(args: Vec, _context: &mut HashMap) -> Result { + if args.len() != 1 { + return Err("os.exec requires exactly one argument".to_string()); + } + + let command = match &args[0] { + AST::String(value) => value, + _ => return Err("os.exec argument must be a string".to_string()), + }; + + let cleaned = clean_command(command); + + let output = { + #[cfg(windows)] { + Command::new("C:\\Windows\\System32\\cmd.exe") + .arg("/C") + .arg(&cleaned) + .creation_flags(0x08000000) + .output() + } + #[cfg(not(windows))] { + Command::new("sh") + .args(["-c", &cleaned]) + .output() + } + }; + + match output { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if output.status.success() { + Ok(AST::String(stdout)) + } else { + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + Err(stderr) + } + }, + Err(e) => Err(format!("Command execution failed: {}", e)) + } +} + +pub fn get_object() -> HashMap { + let mut object = HashMap::new(); + + object.insert( + "exec".to_string(), + AST::InternalFunction { + name: "exec".to_string(), + args: vec!["command".to_string()], + call_fn: exec, + } + ); + + object +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_exec_echo() { + let args = vec![AST::String("echo hello".to_string())]; + let result = exec(args, &mut HashMap::new()).unwrap(); + match result { + AST::String(value) => { + assert!(value.contains("hello")); + }, + _ => panic!("Expected string output") + } + } +} \ No newline at end of file