diff --git a/extensions/vscode/modu-lang-0.0.1.vsix b/extensions/vscode/modu-lang-0.0.1.vsix deleted file mode 100644 index 1d1e702..0000000 Binary files a/extensions/vscode/modu-lang-0.0.1.vsix and /dev/null differ diff --git a/lang/Cargo.toml b/lang/Cargo.toml index f97b7d8..38d0e7f 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -13,6 +13,7 @@ keywords = ["programming-language", "cli", "interpreter", "language"] [dependencies] bat = "0.24.0" chrono = "0.4.39" +libloading = "0.8.6" logos = "0.15.0" rand = "0.8.5" reqwest = { version = "0.12.11", features = ["blocking", "json"] } diff --git a/lang/examples/advanced/ffi_http/http_client/Cargo.toml b/lang/examples/advanced/ffi_http/http_client/Cargo.toml new file mode 100644 index 0000000..cf57845 --- /dev/null +++ b/lang/examples/advanced/ffi_http/http_client/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "http_client" +version = "0.1.0" +edition = "2024" + +[dependencies] +reqwest = { version = "0.12.11", features = ["blocking"] } + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/lang/examples/advanced/ffi_http/http_client/src/lib.rs b/lang/examples/advanced/ffi_http/http_client/src/lib.rs new file mode 100644 index 0000000..9b3cd07 --- /dev/null +++ b/lang/examples/advanced/ffi_http/http_client/src/lib.rs @@ -0,0 +1,26 @@ +use reqwest::blocking::Client; + +#[unsafe(no_mangle)] +pub extern "C" fn get( + argc: std::ffi::c_int, + argv: *const *const std::ffi::c_char +) -> *mut std::ffi::c_char { + if argc != 1 { + panic!("function 'get' requires and takes 1 arguments"); + } + + let args = unsafe { + std::slice::from_raw_parts(argv, argc as usize) + }; + + let url = unsafe { + std::ffi::CStr::from_ptr(args[0]) + }; + let url = url.to_str().unwrap(); + + let client = Client::new(); + let res = client.get(url).send().unwrap(); + let body = res.text().unwrap(); + + std::ffi::CString::new(body).unwrap().into_raw() +} \ No newline at end of file diff --git a/lang/examples/advanced/ffi_http/libhttp_client.so b/lang/examples/advanced/ffi_http/libhttp_client.so new file mode 100755 index 0000000..a712723 Binary files /dev/null and b/lang/examples/advanced/ffi_http/libhttp_client.so differ diff --git a/lang/examples/advanced/ffi_http/main.modu b/lang/examples/advanced/ffi_http/main.modu new file mode 100644 index 0000000..035e718 --- /dev/null +++ b/lang/examples/advanced/ffi_http/main.modu @@ -0,0 +1,28 @@ +import "os" as os; +import "ffi" as ffi; + +fn check_comp() { + if os.name != "linux" { + print("libhttp only supports linux at the current moment"); + exit(); + } +} + +fn get(url) { + let res = ffi.call("./libhttp_client.so", "get", url); + + return res; +} + +check_comp(); +print(get("https://jsonplaceholder.typicode.com/todos/1")); + +// Expected output: +/* +{ + userId: 1, + id: 1, + title: delectus aut autem, + completed: false +} +*/ diff --git a/lang/examples/ffi/ffi_test/Cargo.toml b/lang/examples/ffi/ffi_test/Cargo.toml new file mode 100644 index 0000000..796735a --- /dev/null +++ b/lang/examples/ffi/ffi_test/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ffi_test" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/lang/examples/ffi/ffi_test/src/lib.rs b/lang/examples/ffi/ffi_test/src/lib.rs new file mode 100644 index 0000000..853be41 --- /dev/null +++ b/lang/examples/ffi/ffi_test/src/lib.rs @@ -0,0 +1,109 @@ +#[unsafe(no_mangle)] +pub extern "C" fn add( + argc: std::ffi::c_int, + argv: *const *const std::ffi::c_char +) -> i32 { + if argc != 2 { + panic!("add requires 2 arguments"); + } + + let args = unsafe { + std::slice::from_raw_parts(argv, argc as usize) + }; + + // modu ffi cant have numbers as args cause shit broke + let num1 = unsafe { + std::ffi::CString::from_raw(args[0] as *mut std::ffi::c_char) + }; + let num1 = num1.to_str().unwrap().parse::().unwrap(); + + let num2 = unsafe { + std::ffi::CString::from_raw(args[1] as *mut std::ffi::c_char) + }; + let num2 = num2.to_str().unwrap().parse::().unwrap(); + + num1 + num2 +} + +#[unsafe(no_mangle)] +pub extern "C" fn a( + argc: std::ffi::c_int, + argv: *const *const std::ffi::c_char +) -> i32 { + if argc != 1 { + panic!("a requires 1 argument"); + } + + let args = unsafe { + std::slice::from_raw_parts(argv, argc as usize) + }; + + let a = unsafe { + std::ffi::CString::from_raw(args[0] as *mut std::ffi::c_char) + }; + let a = a.to_str().unwrap(); + + a.parse().unwrap() +} + +#[unsafe(no_mangle)] +pub extern "C" fn one() -> i64 { + 1 +} + +#[unsafe(no_mangle)] +pub extern "C" fn string() -> *mut std::ffi::c_char { + std::ffi::CString::new("Hello, World!").unwrap().into_raw() +} + +#[unsafe(no_mangle)] +pub extern "C" fn print( + argc: std::ffi::c_int, + argv: *const *const std::ffi::c_char +) { + if argc != 1 { + panic!("print requires 1 argument"); + } + + let args = unsafe { + std::slice::from_raw_parts(argv, argc as usize) + }; + + let str = unsafe { + std::ffi::CStr::from_ptr(args[0]) + }; + + println!("{}", str.to_str().unwrap()); +} + +#[unsafe(no_mangle)] +pub extern "C" fn print2( + argc: std::ffi::c_int, + argv: *const *const std::ffi::c_char +) { + if argc != 2 { + panic!("print requires 2 arguments"); + } + + let args = unsafe { + std::slice::from_raw_parts(argv, argc as usize) + }; + + let str = unsafe { + std::ffi::CStr::from_ptr(args[0]) + }; + + let str2 = unsafe { + std::ffi::CStr::from_ptr(args[1]) + }; + + print!("{}", str.to_str().unwrap()); + print!("{}", str2.to_str().unwrap()); + println!(); +} + + +#[unsafe(no_mangle)] +pub extern "C" fn hello_world() { + println!("Hello, World!"); +} \ No newline at end of file diff --git a/lang/examples/ffi/libffi_test.dylib b/lang/examples/ffi/libffi_test.dylib new file mode 100755 index 0000000..e744c02 Binary files /dev/null and b/lang/examples/ffi/libffi_test.dylib differ diff --git a/lang/examples/ffi/libffi_test.so b/lang/examples/ffi/libffi_test.so new file mode 100755 index 0000000..1e3de60 Binary files /dev/null and b/lang/examples/ffi/libffi_test.so differ diff --git a/lang/examples/ffi/main.modu b/lang/examples/ffi/main.modu new file mode 100644 index 0000000..c47ba3a --- /dev/null +++ b/lang/examples/ffi/main.modu @@ -0,0 +1,33 @@ +import "ffi" as ffi; +import "os" as os; + +let path = "./libffi_test" + +if os.name == "linux" { + let path = path + ".so" +} + +if os.name == "macos" { + let path = path + ".dylib" +} + +if os.name == "windows" { + let path = path + ".dll" + print("This may error out if you have not built ffi_test and put the .dll in same folder as main.modu!"); + print("I will remove this message once i add a .dll myself there") +} + +ffi.call(path, "hello_world"); + +print(ffi.call(path, "a", "5")); +print(ffi.call(path, "one")); + +// due to some arg issues, ffi calls cant accept numbers +print(ffi.call(path, "add", str(5), str(2))); +print(ffi.call(path, "string")); + +ffi.call(path, "print", "test"); +ffi.call(path, "print2", "test1", " test2"); + + +// ffi.call(path, "add", 1, 2, 3); // panic test diff --git a/lang/examples/if_exists.modu b/lang/examples/if_exists.modu deleted file mode 100644 index 2dc8a6b..0000000 --- a/lang/examples/if_exists.modu +++ /dev/null @@ -1,13 +0,0 @@ -let b = 2; - -if a { - print(a); -} - -if b { - print(b); -} - -if null { - print("this shouldnt print"); -} \ No newline at end of file diff --git a/lang/examples/if_statements.modu b/lang/examples/if_statements.modu index cebbf9e..8110214 100644 --- a/lang/examples/if_statements.modu +++ b/lang/examples/if_statements.modu @@ -30,6 +30,11 @@ if c == "potato" { print("c == 'potato'"); } +let h; +if h { + print("oh nyo"); +} + // Expected Output: // // a == 1 diff --git a/lang/examples/undefined_var.modu b/lang/examples/undefined_var.modu deleted file mode 100644 index 16aec02..0000000 --- a/lang/examples/undefined_var.modu +++ /dev/null @@ -1,12 +0,0 @@ -let x = 10.5; -let y = x; - -print(x); -print(y); -print(z); - -// Expected Output: -// -// 10.5 -// 10.5 -// null \ No newline at end of file diff --git a/lang/input.modu b/lang/input.modu index b28696b..400c3de 100644 --- a/lang/input.modu +++ b/lang/input.modu @@ -1,16 +1,2 @@ -import "os" as os - - -fn fetch(url) { - let req = "curl "+ url; - let res = os.exec(req); - print(res); - return res; -} - -fn main() { - let response = fetch("https://jsonplaceholder.typicode.com/todos/1"); - print(response); -} - -main(); \ No newline at end of file +let a = 1+1 +a \ No newline at end of file diff --git a/lang/src/cli/publish.rs b/lang/src/cli/publish.rs index bd660bb..f9cd477 100644 --- a/lang/src/cli/publish.rs +++ b/lang/src/cli/publish.rs @@ -25,7 +25,6 @@ fn read_dir(dir: &std::path::Path, archive: &mut zip::ZipWriter) } let mut gitignore_content = String::new(); - let gitignore: Result<_, _> = std::fs::File::open(".gitignore"); match gitignore { @@ -43,6 +42,24 @@ fn read_dir(dir: &std::path::Path, archive: &mut zip::ZipWriter) Err(_) => {} } + let mut moduignore_content = String::new(); + let moduignore: Result<_, _> = std::fs::File::open(".moduignore"); + + match moduignore { + Ok(mut file) => { + file.read_to_string(&mut moduignore_content).unwrap(); + + for line in moduignore_content.lines() { + if path.to_str().unwrap().replace("\\", "/") == format!("./{}", line) { + println!("Ignoring {}", path.to_str().unwrap()); + do_break = true; + } + } + }, + + Err(_) => {} + } + if do_break { continue; } @@ -54,9 +71,9 @@ fn read_dir(dir: &std::path::Path, archive: &mut zip::ZipWriter) archive.start_file(name.to_str().unwrap(), zip::write::SimpleFileOptions::default()).unwrap(); let mut file = std::fs::File::open(path).unwrap(); - let mut contents = String::new(); + let mut contents = Vec::new(); - let r = file.read_to_string(&mut contents); + let r = file.read_to_end(&mut contents); match r { Ok(_) => {}, @@ -65,7 +82,7 @@ fn read_dir(dir: &std::path::Path, archive: &mut zip::ZipWriter) } } - archive.write_all(contents.as_bytes()).unwrap(); + archive.write_all(&contents).unwrap(); } } } @@ -95,6 +112,7 @@ pub fn publish() { std::fs::create_dir_all(".modu").unwrap(); + println!("Compressing package"); let mut archive = zip::ZipWriter::new(std::fs::File::create(".modu/package.zip").unwrap()); read_dir(std::path::Path::new("."), &mut archive); archive.finish().unwrap(); diff --git a/lang/src/eval.rs b/lang/src/eval.rs index 4d0187d..d67418e 100644 --- a/lang/src/eval.rs +++ b/lang/src/eval.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, path::PathBuf}; use crate::utils; use crate::packages::get_package; -static DISABLED_ON_SERVER: [&str; 2] = ["file", "os"]; +static DISABLED_ON_SERVER: [&str; 3] = ["file", "os", "ffi"]; pub fn eval(expr: AST, context: &mut HashMap) -> Result { match expr { @@ -35,7 +35,11 @@ pub fn eval(expr: AST, context: &mut HashMap) -> Result) -> Result) -> Result { if b { for expr in body { - match eval(expr, context) { - Ok(AST::Null) => { - continue; - } - - Ok(v) => { - return Ok(v); - }, - - Err(e) => { - return Err(e); - } + if let AST::Return { value, line } = expr { + return Ok(AST::Return { value, line }); } + + eval(expr, context)?; } } } diff --git a/lang/src/internal.rs b/lang/src/internal.rs index 3783c78..a1fe663 100644 --- a/lang/src/internal.rs +++ b/lang/src/internal.rs @@ -50,23 +50,71 @@ pub fn input(args: Vec, context: &mut HashMap) -> Result, context: &mut HashMap) -> Result { - if args.len() != 1 { - return Err("int() requires exactly one argument".to_string()); - } - match eval(args[0].clone(), context) { Ok(v) => { match v { AST::String(value) => { match value.parse::() { - Ok(value) => Ok(AST::Number(value)), - Err(_) => Err("Could not parse string to int".to_string()) + Ok(value) => {return Ok(AST::Number(value));}, + Err(_) => (), } + + match value.parse::() { + Ok(value) => {return Ok(AST::Number(value as i64));}, + Err(_) => (), + } + + return Err("int() requires a string or boolean".to_string()); } + + AST::Boolean(value) => Ok(AST::Number(if value {1} else {0})), AST::Number(value) => Ok(AST::Number(value)), - _ => Err("int() requires a string or number".to_string()) + _ => Err("int() requires a string or boolean".to_string()) + } + } + + Err(e) => Err(e) + } +} + +pub fn float(args: Vec, context: &mut HashMap) -> Result { + match eval(args[0].clone(), context) { + Ok(v) => { + match v { + AST::String(value) => { + match value.parse::() { + Ok(value) => Ok(AST::Float(value)), + Err(_) => Err("float() requires a string or boolean".to_string()) + } + } + + AST::Boolean(value) => Ok(AST::Float(if value {1.0} else {0.0})), + + AST::Number(value) => Ok(AST::Float(value as f64)), + AST::Float(value) => Ok(AST::Float(value)), + + _ => Err("float() requires a string or boolean".to_string()) + } + } + + Err(e) => Err(e) + } +} + +pub fn str(args: Vec, context: &mut HashMap) -> Result { + match eval(args[0].clone(), context) { + Ok(v) => { + match v { + AST::String(value) => Ok(AST::String(value)), + + AST::Number(value) => Ok(AST::String(value.to_string())), + AST::Float(value) => Ok(AST::String(value.to_string())), + AST::Boolean(value) => Ok(AST::String(value.to_string())), + AST::Null => Ok(AST::String("null".to_string())), + + _ => Err("str() requires a string, number or boolean".to_string()) } } diff --git a/lang/src/packages/ffi.rs b/lang/src/packages/ffi.rs new file mode 100644 index 0000000..89df847 --- /dev/null +++ b/lang/src/packages/ffi.rs @@ -0,0 +1,97 @@ +use std::collections::HashMap; +use crate::ast::AST; +use crate::eval::eval; + +pub fn call(mut args: Vec, context: &mut HashMap) -> Result { + // (path_to_lib, function_name, arg1, arg2, ...) + + if args.len() < 2 { + return Err("ffi.call requires at least 2 arguments".to_string()); + } + + let path = match eval(args[0].clone(), context) { + Ok(AST::String(v)) => v, + + _ => return Err("ffi.call first argument must be a string".to_string()), + }; + + let name = match eval(args[1].clone(), context) { + Ok(AST::String(v)) => v, + + _ => return Err("ffi.call second argument must be a string".to_string()), + }; + + unsafe { + let lib = match libloading::Library::new(path) { + Ok(lib) => lib, + Err(e) => return Err(format!("Failed to load library: {}", e)), + }; + + let func: libloading::Symbol *mut std::ffi::c_void> + = match lib.get(name.as_bytes()) { + Ok(func) => func, + Err(e) => return Err(format!("Failed to load function: {}", e)), + }; + + let mut args_ptr: Vec<*mut std::ffi::c_char> = Vec::new(); + + args.remove(0); + args.remove(0); + + for arg in args { + match eval(arg, context) { + Ok(AST::Number(_)) => { + //args_ptr.push(v as *mut std::ffi::c_void); + return Err("Cant use numbers in ffi, it was extremely broken, to be fixed\nSuggestion: turn int to str with str(int), then parse that to int in the lib".to_string()); + } + + Ok(AST::String(v)) => { + let c_str = std::ffi::CString::new(v.replace("\"", "")).unwrap(); + + args_ptr.push(c_str.into_raw() as *mut std::ffi::c_char); + } + + Ok(_) => return Err("ffi.call arguments must be numbers or strings".to_string()), + + Err(e) => return Err(e), + }; + } + + let result_ptr = func( + args_ptr.len() as std::ffi::c_int, + args_ptr.as_mut_ptr() as *mut std::ffi::c_char + ); + + // lib go bye (i think this prevents memory leaks or something) + lib.close().unwrap(); + + if result_ptr.is_null() { + return Ok(AST::Null); + }; + + if (result_ptr as i64) <= i32::MAX as i64 && (result_ptr as i64) >= i32::MIN as i64 { + return Ok(AST::Number(result_ptr as i64)); + } else { + let str = std::ffi::CStr::from_ptr(result_ptr as *const _); + return Ok(AST::String(str.to_string_lossy().into_owned())) + } + } +} + +pub fn get_object() -> HashMap { + let mut object = HashMap::new(); + + object.insert( + "call".to_string(), + AST::InternalFunction { + name: "call".to_string(), + args: vec!["__args__".to_string()], + call_fn: call, + } + ); + + object +} \ No newline at end of file diff --git a/lang/src/packages/mod.rs b/lang/src/packages/mod.rs index 20031e4..57280e5 100644 --- a/lang/src/packages/mod.rs +++ b/lang/src/packages/mod.rs @@ -2,6 +2,7 @@ mod math; mod file; mod time; mod os; +mod ffi; use crate::ast::AST; @@ -39,6 +40,11 @@ pub fn get_package(name: &str) -> Option { line: 0 }), + "ffi" => Some(AST::Object { + properties: ffi::get_object(), + line: 0 + }), + _ => None } } diff --git a/lang/src/parser.rs b/lang/src/parser.rs index 57d74fe..2bace66 100644 --- a/lang/src/parser.rs +++ b/lang/src/parser.rs @@ -3041,10 +3041,6 @@ pub fn parse(input: &str, context: &mut HashMap) -> Result<(), (Str } else { ast.append(&mut temp_ast); } - - if verbose { - //dbg!(&ast); - } } if verbose { @@ -3079,6 +3075,8 @@ pub fn parse(input: &str, context: &mut HashMap) -> Result<(), (Str if result.is_err() { return Err((result.err().unwrap(), line)); } + + print_res(result.unwrap()); } AST::PropertyCall { object, property, args, line } => { @@ -3089,6 +3087,8 @@ pub fn parse(input: &str, context: &mut HashMap) -> Result<(), (Str if result.is_err() { return Err((result.err().unwrap(), line)); } + + print_res(result.unwrap()); } AST::Function { name, args, body, line } => { @@ -3114,6 +3114,16 @@ pub fn parse(input: &str, context: &mut HashMap) -> Result<(), (Str AST::Semicolon => {} AST::Null => {} + AST::Identifer(name) => { + let result = eval(AST::Identifer(name), context); + + if result.is_err() { + return Err((result.err().unwrap(), 1)); + } + + print_res(result.unwrap()); + } + _ => { if verbose { println!("I'm not sure what to do with a {:?}", item); @@ -3125,6 +3135,28 @@ pub fn parse(input: &str, context: &mut HashMap) -> Result<(), (Str Ok(()) } +fn print_res(res: AST) { + match res { + AST::String(v) => { + println!("{}", v); + } + + AST::Number(v) => { + println!("{}", v); + } + + AST::Float(v) => { + println!("{}", v); + } + + AST::Boolean(v) => { + println!("{}", v); + } + + _ => {} + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lang/src/utils.rs b/lang/src/utils.rs index d192e4e..f1c6ced 100644 --- a/lang/src/utils.rs +++ b/lang/src/utils.rs @@ -34,11 +34,29 @@ pub fn create_context() -> HashMap { "int".to_string(), AST::InternalFunction { name: "int".to_string(), - args: vec!["__args__".to_string()], + args: vec!["val".to_string()], call_fn: crate::internal::int, } ); + context.insert( + "float".to_string(), + AST::InternalFunction { + name: "float".to_string(), + args: vec!["val".to_string()], + call_fn: crate::internal::float, + } + ); + + context.insert( + "str".to_string(), + AST::InternalFunction { + name: "str".to_string(), + args: vec!["val".to_string()], + call_fn: crate::internal::str, + } + ); + context.insert( "exit".to_string(), AST::InternalFunction { @@ -69,7 +87,7 @@ mod tests { fn create_context_test() { let context = create_context(); - assert_eq!(context.len(), 4); + assert_eq!(context.len(), 6); assert_eq!(context.contains_key("print"), true); assert_eq!(context.contains_key("exit"), true); assert_eq!(context.contains_key("input"), true); diff --git a/web/src/lib/docs/data.ts b/web/src/lib/docs/data.ts index 0448529..77a7152 100644 --- a/web/src/lib/docs/data.ts +++ b/web/src/lib/docs/data.ts @@ -1,4 +1,4 @@ -import { Home, Baseline, File, FileBox, Equal, TriangleAlert, Server, Library, AppWindowIcon } from "lucide-svelte" +import { Home, Baseline, File, FileBox, Equal, TriangleAlert, Server, Library, AppWindowIcon, Box } from "lucide-svelte" export default { pages: [ @@ -37,6 +37,11 @@ export default { "title": "OS Lib", "icon": AppWindowIcon, }, + { + "path": "ffi", + "title": "FFI", + "icon": Box, + }, /*{ "path": "limitations", "title": "Limitations", diff --git a/web/src/lib/docs/ffi.md b/web/src/lib/docs/ffi.md new file mode 100644 index 0000000..ec7b43f --- /dev/null +++ b/web/src/lib/docs/ffi.md @@ -0,0 +1,95 @@ +# Foreign Function Interface (FFI) +> Disabled on the server due to security >:D + +⚠️ Can only take strings (or variables that are strings) as arguments + +Using FFI is actually really simple, just import a **.dll/.so/.dylib** file and u can run its functions with ffi.call, here is an example: +```rust +import "ffi" as ffi; + +// Note that .so is the shared library extension on Linux +// On windows it would be .dll, and on MacOS it would be .dylib +// In actal code you would have to differentiate using os.name +// (returns windows/linux/macos/unknown) +// For info on the OS package see the "OS Lib" page +ffi.call("./libffi_test.so", "hello_world"); + +// Output: +// +// Hello, World +``` + +This is the **hello_world** function, written as a rust lib: +```rust +#[unsafe(no_mangle)] +pub extern "C" fn hello_world() { + println!("Hello, World!"); +} +``` + +Note: i am using rust cause i prefer that, you can write the libraries in any programming that exports to C-Style libraries. \ +Here are some examples: +- C (of course you can use C) +- Go (using CGO) +- Python (using ctypes) + + +## Arguments +To use arguments call the function like **ffi.call(path, function, arg1, arg2, ...)** + +Here is an example: +```rust +import "ffi" as ffi; + +// FFI currently only accept string args, +// so we have to parse it to int in the lib + +// You can either just provide a string with the number, +// or use the str() function + +// It does not make any diffrence which one you use, +// but the str(int) can be used for variable numbers +print(ffi.call("./libffi_test.so", "add", str(5), "2")); + +// Output: +// +// 7 +``` + +As you can see, we have to use string arguments, any other will cause errors. + +Here is the code for the library: +```rust +#[unsafe(no_mangle)] +pub extern "C" fn add( + // Amount of args in argv + argc: std::ffi::c_int, + // We are using char cause we are passing strings, + // we will turn this to int later + argv: *const *const std::ffi::c_char +) -> i32 { + // We can check argc to enforce arg requirements/limints + if argc != 2 { + panic!("add requires 2 arguments"); + } + + // Turn argv into an vec containing our args + let args = unsafe { + std::slice::from_raw_parts(argv, argc as usize) + }; + + // Parse the pointers to strings + let num1 = unsafe { + std::ffi::CString::from_raw(args[0] as *mut std::ffi::c_char) + }; + // Then we finally parse them to numbers + let num1 = num1.to_str().unwrap().parse::().unwrap(); + + let num2 = unsafe { + std::ffi::CString::from_raw(args[1] as *mut std::ffi::c_char) + }; + let num2 = num2.to_str().unwrap().parse::().unwrap(); + + num1 + num2 +} +``` \ No newline at end of file