Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lang/examples/os_exec.modu
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "os" as os;

print(os.exec("mkdir test"));
145 changes: 82 additions & 63 deletions lang/src/packages/mod.rs
Original file line number Diff line number Diff line change
@@ -1,75 +1,94 @@
mod math;
mod file;
mod time;
mod os;

use crate::ast::AST;

pub fn get_package(name: &str) -> Option<AST> {
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::<Vec<String>>();

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::<Vec<String>>();

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

}
86 changes: 86 additions & 0 deletions lang/src/packages/os.rs
Original file line number Diff line number Diff line change
@@ -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<AST>, _context: &mut HashMap<String, AST>) -> Result<AST, String> {
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<String, AST> {
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")
}
}
}