Skip to content

Commit

Permalink
Merge pull request #294 from CohenArthur/shell-library
Browse files Browse the repository at this point in the history
Add shell library abstraction
  • Loading branch information
CohenArthur authored Oct 15, 2021
2 parents ccf23bb + f49c1b9 commit 71e15ab
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 51 deletions.
122 changes: 72 additions & 50 deletions src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<libloading::Library, Error> {
fn find_lib(paths: &[PathBuf], lib_path: &Path) -> Result<Library, Error> {
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);
}
}
Expand All @@ -21,7 +26,7 @@ fn find_lib(paths: &[PathBuf], lib_path: &Path) -> Result<libloading::Library, E
Err(Error::new(ErrKind::ExternFunc))
}

fn lib_from_root(base: &Path, lib_path: &Path) -> Result<libloading::Library, Error> {
fn lib_from_root(base: &Path, lib_path: &Path) -> Result<Library, Error> {
// 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)];
Expand All @@ -39,7 +44,7 @@ fn lib_from_root(base: &Path, lib_path: &Path) -> Result<libloading::Library, Er
/// If the LD_LIBRARY_PATH variable is set and contains a colon, then assume it
/// contains a list of colon-separated directories and search through them.
/// Then, search through /lib/, and then finally through /usr/lib/
fn lib_from_path(lib_path: PathBuf) -> Result<libloading::Library, Error> {
fn lib_from_path(lib_path: PathBuf) -> Result<Library, Error> {
if let Ok(lib) = lib_from_ld_library_path(&lib_path) {
return Ok(lib);
}
Expand All @@ -56,13 +61,13 @@ fn lib_from_path(lib_path: PathBuf) -> Result<libloading::Library, Error> {
}

/// Look through the paths specified in the LD_LIBRARY_PATH environment variable (if any)
fn lib_from_ld_library_path(lib_path: &Path) -> Result<libloading::Library, Error> {
fn lib_from_ld_library_path(lib_path: &Path) -> Result<Library, Error> {
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)?) };
}
}

Expand All @@ -71,7 +76,7 @@ fn lib_from_ld_library_path(lib_path: &Path) -> Result<libloading::Library, Erro

pub fn link_with(ctx: &mut Context, lib_path: PathBuf) -> 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)?
};
Expand All @@ -85,45 +90,69 @@ pub fn execute(
dec: &FunctionDec,
call: &FunctionCall,
ctx: &mut Context,
) -> Option<ObjectInstance> {
) -> Result<Option<ObjectInstance>, Error> {
ctx.debug("EXT CALL", call.name());
let sym = call.name().as_bytes();

// FIXME: Don't unwrap
let args: Vec<ObjectInstance> = call
.args()
.iter()
.map(|arg| arg.execute(ctx).unwrap())
.collect();

for lib in ctx.libs().iter() {
unsafe {
if lib.get::<libloading::Symbol<()>>(sym).is_ok() {
match call.args().len() {
0 => {
match dec.ty() {
None => {
if let Ok(f) = lib.get::<libloading::Symbol<fn()>>(sym) {
f();
return None;
}
}
Some(ty) => match ty.id() {
"int" => {
if let Ok(f) = lib.get::<libloading::Symbol<fn() -> i64>>(sym) {
let res = f();
return Some(JkInt::from(res).to_instance());
}
}
_ => unreachable!(),
},
};
let func = unsafe { lib.get::<Symbol<fn()>>(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::<fn(), fn() -> 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::<fn(), fn(*const c_char)>(**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::<fn(), fn(*const c_char) -> 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)]
Expand Down Expand Up @@ -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()))
);
}

Expand All @@ -176,7 +205,7 @@ mod tests {
let call = Construct::instruction("print_something()").unwrap().1;
let call = call.downcast_ref::<FunctionCall>().unwrap();

assert_eq!(execute(&dec, &call, &mut i), None);
assert_eq!(execute(&dec, &call, &mut i), Ok(None));
}

#[test]
Expand All @@ -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");
};
}
}
8 changes: 7 additions & 1 deletion src/instruction/function_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions stdlib/lib.jk
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ incl pair
incl string
incl maybe
incl ffi
incl shell
27 changes: 27 additions & 0 deletions stdlib/shell.jk
Original file line number Diff line number Diff line change
@@ -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)
}
7 changes: 7 additions & 0 deletions tests/ft/stdlib/shell.jk
Original file line number Diff line number Diff line change
@@ -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()
5 changes: 5 additions & 0 deletions tests/ft/stdlib/stdlib.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
38 changes: 38 additions & 0 deletions tests/func_tests.jk
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 71e15ab

Please sign in to comment.