diff --git a/src/ffi.rs b/src/ffi.rs index 79b72bd6..99e8be73 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -2,17 +2,22 @@ //! Primitive types are converted to their C counterparts. //! FIXME -use std::path::{Path, PathBuf}; - use crate::instruction::{FunctionCall, FunctionDec}; -use crate::{Context, ErrKind, Error, JkInt, ObjectInstance, ToObjectInstance}; +use crate::{ + Context, ErrKind, Error, FromObjectInstance, JkInt, JkString, ObjectInstance, ToObjectInstance, +}; + +use libloading::{Library, Symbol}; +use std::ffi::CString; +use std::mem::transmute; +use std::os::raw::c_char; +use std::path::{Path, PathBuf}; -fn find_lib(paths: &[PathBuf], lib_path: &Path) -> Result { +fn find_lib(paths: &[PathBuf], lib_path: &Path) -> Result { for dir_base in paths.iter() { let lib_path = PathBuf::from(dir_base).join(lib_path); - dbg!(&lib_path); if lib_path.exists() { - if let Ok(lib) = unsafe { libloading::Library::new(lib_path) } { + if let Ok(lib) = unsafe { Library::new(lib_path) } { return Ok(lib); } } @@ -21,7 +26,7 @@ fn find_lib(paths: &[PathBuf], lib_path: &Path) -> Result Result { +fn lib_from_root(base: &Path, lib_path: &Path) -> Result { // We search through all entries in the `base` directory as well as all the entries // in the directories contained in `base`. let mut dirs = vec![PathBuf::from(base)]; @@ -39,7 +44,7 @@ fn lib_from_root(base: &Path, lib_path: &Path) -> Result Result { +fn lib_from_path(lib_path: PathBuf) -> Result { if let Ok(lib) = lib_from_ld_library_path(&lib_path) { return Ok(lib); } @@ -56,13 +61,13 @@ fn lib_from_path(lib_path: PathBuf) -> Result { } /// Look through the paths specified in the LD_LIBRARY_PATH environment variable (if any) -fn lib_from_ld_library_path(lib_path: &Path) -> Result { +fn lib_from_ld_library_path(lib_path: &Path) -> Result { let paths = std::env::var("LD_LIBRARY_PATH")?; for dir in paths.split(':') { let path = PathBuf::from(dir).join(&lib_path); if path.exists() { - return unsafe { Ok(libloading::Library::new(path)?) }; + return unsafe { Ok(Library::new(path)?) }; } } @@ -71,7 +76,7 @@ fn lib_from_ld_library_path(lib_path: &Path) -> Result Result<(), Error> { let lib = if std::path::Path::new(&lib_path).exists() { - unsafe { libloading::Library::new(lib_path)? } + unsafe { Library::new(lib_path)? } } else { lib_from_path(lib_path)? }; @@ -85,45 +90,69 @@ pub fn execute( dec: &FunctionDec, call: &FunctionCall, ctx: &mut Context, -) -> Option { +) -> Result, Error> { ctx.debug("EXT CALL", call.name()); let sym = call.name().as_bytes(); + // FIXME: Don't unwrap + let args: Vec = call + .args() + .iter() + .map(|arg| arg.execute(ctx).unwrap()) + .collect(); + for lib in ctx.libs().iter() { - unsafe { - if lib.get::>(sym).is_ok() { - match call.args().len() { - 0 => { - match dec.ty() { - None => { - if let Ok(f) = lib.get::>(sym) { - f(); - return None; - } - } - Some(ty) => match ty.id() { - "int" => { - if let Ok(f) = lib.get:: i64>>(sym) { - let res = f(); - return Some(JkInt::from(res).to_instance()); - } - } - _ => unreachable!(), - }, - }; + let func = unsafe { lib.get::>(sym) }; + let func = match func { + Ok(func) => func, + Err(_) => continue, + }; + match call.args().len() { + 0 => match dec.ty() { + None => { + func(); + return Ok(None); + } + Some(ty) => match ty.id() { + "int" => { + let func = unsafe { transmute:: i64>(**func) }; + let res = func(); + return Ok(Some(JkInt::from(res).to_instance())); } _ => unreachable!(), - } - } + }, + }, + 1 => match dec.args()[0].get_type().id() { + "string" => match dec.ty() { + None => { + let func = unsafe { transmute::(**func) }; + let arg = JkString::from_instance(&args[0]).0; + let arg = CString::new(arg.as_str()).unwrap(); + let arg = arg.as_ptr(); + func(arg); + return Ok(None); + } + Some(ty) => match ty.id() { + "int" => { + let func = + unsafe { transmute:: i64>(**func) }; + let arg = JkString::from_instance(&args[0]).0; + let arg = CString::new(arg.as_str()).unwrap(); + let arg = arg.as_ptr(); + let res = func(arg); + return Ok(Some(JkInt::from(res).to_instance())); + } + _ => unreachable!(), + }, + }, + _ => unreachable!(), + }, + _ => unreachable!(), } } - ctx.error( - Error::new(ErrKind::ExternFunc) - .with_msg(format!("cannot call external function {}", call.name())), - ); - - None + Err(Error::new(ErrKind::ExternFunc) + .with_msg(format!("cannot call external function `{}`", call.name()))) } #[cfg(test)] @@ -161,7 +190,7 @@ mod tests { assert_eq!( execute(&dec, &call, &mut i), - Some(JkInt::from(15).to_instance()) + Ok(Some(JkInt::from(15).to_instance())) ); } @@ -176,7 +205,7 @@ mod tests { let call = Construct::instruction("print_something()").unwrap().1; let call = call.downcast_ref::().unwrap(); - assert_eq!(execute(&dec, &call, &mut i), None); + assert_eq!(execute(&dec, &call, &mut i), Ok(None)); } #[test] @@ -196,12 +225,5 @@ mod tests { jinko! { link_with("libc.so.6"); }; - - std::env::remove_var("LD_LIBRARY_PATH"); - - // Load libc without LD_LIBRARY_PATH - jinko! { - link_with("libc.so.6"); - }; } } diff --git a/src/instruction/function_call.rs b/src/instruction/function_call.rs index 0eddecab..c9535386 100644 --- a/src/instruction/function_call.rs +++ b/src/instruction/function_call.rs @@ -131,7 +131,13 @@ impl FunctionCall { } } } else { - crate::ffi::execute(dec, self, ctx) + match crate::ffi::execute(dec, self, ctx) { + Ok(value) => value, + Err(e) => { + ctx.error(e); + None + } + } } } } diff --git a/stdlib/lib.jk b/stdlib/lib.jk index ffdf08ce..8e11ea63 100644 --- a/stdlib/lib.jk +++ b/stdlib/lib.jk @@ -2,3 +2,4 @@ incl pair incl string incl maybe incl ffi +incl shell diff --git a/stdlib/shell.jk b/stdlib/shell.jk new file mode 100644 index 00000000..48ec3654 --- /dev/null +++ b/stdlib/shell.jk @@ -0,0 +1,27 @@ +link_with("libc.so.6"); + +ext func system(s: string) -> int; + +// FIXME: Should return a Result +func shell(s: string) -> int { + system(s) +} + +type Cmd(s: string); + +func cmd(command: string) -> Cmd { + // FIXME: Make sure `command` does not contain any spaces + Cmd { s = command } +} + +func with_arg(cmd: Cmd, arg: string) -> Cmd { + mut new_s = concat(cmd.s, " "); + new_s = new_s.concat(arg); + + Cmd { s = new_s } +} + +// FIXME: Should return a Result +func execute(cmd: Cmd) -> int { + shell(cmd.s) +} diff --git a/tests/ft/stdlib/shell.jk b/tests/ft/stdlib/shell.jk new file mode 100644 index 00000000..3382c4f4 --- /dev/null +++ b/tests/ft/stdlib/shell.jk @@ -0,0 +1,7 @@ +shell("echo 'from shell'") + +mut c = cmd("echo").with_arg("from"); +c = c.with_arg("cmd"); +c = c.with_arg("builder"); + +c.execute() diff --git a/tests/ft/stdlib/stdlib.yml b/tests/ft/stdlib/stdlib.yml index 03c25aef..1681b3ad 100644 --- a/tests/ft/stdlib/stdlib.yml +++ b/tests/ft/stdlib/stdlib.yml @@ -18,3 +18,8 @@ tests: args: - "tests/ft/stdlib/ffi.jk" stdout: "jinko called\n" + - name: "Test shell module" + binary: "target/debug/jinko" + args: + - "tests/ft/stdlib/shell.jk" + stdout: "from shell\nfrom cmd builder\n" diff --git a/tests/func_tests.jk b/tests/func_tests.jk new file mode 100644 index 00000000..9afd2dc2 --- /dev/null +++ b/tests/func_tests.jk @@ -0,0 +1,38 @@ +#!jinko + +ft_exists_exit_code = shell("ft --version"); + +ft_exists = if ft_exists_exit_code == 0 { + true +} else { + false +}; + +if !ft_exists { // ft_exists.not() + display_err("\n`ft` not found. Install it using the following command\n\n"); + display_err("\tcargo install --git https://github.com/cohenarthur/ft\n\n"); + + exit(1); +} + +shell("cargo build") + +if !args().empty() { + display("Test files:\n"); + for arg in args() { + display(arg); + + shell("ft -f {arg}"); + } +} else { + // Find all files somehow + files = shell("find tests -name '*.yml'").output(); + + display("Test files:\n{files}\n\n"); + + shell("ft -f {files}"); +} + +// FIXME: Parse arguments correctly +// FIXME: Add split method on string +// FIXME: Add string vector