170 lines
5.0 KiB
Rust
170 lines
5.0 KiB
Rust
use crate::interop::SharedStats;
|
|
use libloading::Library;
|
|
use std::env;
|
|
use std::ffi::{CStr, CString};
|
|
use std::fs;
|
|
use std::os::raw::{c_char, c_int};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
unsafe extern "C" {
|
|
fn hs_init(argc: *mut c_int, argv: *mut *mut *mut c_char);
|
|
fn hs_exit();
|
|
}
|
|
|
|
type HsComputeStats = unsafe extern "C" fn(c_int, c_int, *mut SharedStats) -> c_int;
|
|
type HsMakeMessage = unsafe extern "C" fn(*const c_char, c_int, c_int) -> *mut c_char;
|
|
type HsFreeString = unsafe extern "C" fn(*mut c_char);
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct DemoArgs {
|
|
pub name: String,
|
|
pub left: i32,
|
|
pub right: i32,
|
|
pub library_path: Option<String>,
|
|
}
|
|
|
|
pub fn run_haskell_demo(args: &DemoArgs) -> Result<String, String> {
|
|
let library_path = resolve_library_path(args.library_path.as_deref())?;
|
|
let runtime = HaskellRuntime::start()?;
|
|
|
|
let output = load_and_run(&library_path, args);
|
|
|
|
drop(runtime);
|
|
output
|
|
}
|
|
|
|
struct HaskellRuntime;
|
|
|
|
impl HaskellRuntime {
|
|
fn start() -> Result<Self, String> {
|
|
let mut argc: c_int = 1;
|
|
let program_name = CString::new("integrations-hs-runtime")
|
|
.map_err(|_| "failed to create runtime program name".to_string())?;
|
|
let mut argv = vec![program_name.as_ptr() as *mut c_char, std::ptr::null_mut()];
|
|
let mut argv_ptr = argv.as_mut_ptr();
|
|
|
|
unsafe {
|
|
hs_init(&mut argc, &mut argv_ptr);
|
|
}
|
|
|
|
Ok(Self)
|
|
}
|
|
}
|
|
|
|
impl Drop for HaskellRuntime {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
hs_exit();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn load_and_run(library_path: &Path, args: &DemoArgs) -> Result<String, String> {
|
|
let library = unsafe { Library::new(library_path) }
|
|
.map_err(|error| format!("failed to load {}: {error}", library_path.display()))?;
|
|
|
|
let compute_stats: HsComputeStats = unsafe {
|
|
*library
|
|
.get(b"hs_compute_stats\0")
|
|
.map_err(|error| format!("failed to load hs_compute_stats: {error}"))?
|
|
};
|
|
let make_message: HsMakeMessage = unsafe {
|
|
*library
|
|
.get(b"hs_make_message\0")
|
|
.map_err(|error| format!("failed to load hs_make_message: {error}"))?
|
|
};
|
|
let free_string: HsFreeString = unsafe {
|
|
*library
|
|
.get(b"hs_free_string\0")
|
|
.map_err(|error| format!("failed to load hs_free_string: {error}"))?
|
|
};
|
|
|
|
let mut stats = SharedStats::default();
|
|
let status = unsafe { compute_stats(args.left, args.right, &mut stats) };
|
|
if status != 0 {
|
|
return Err(format!("hs_compute_stats returned status {status}"));
|
|
}
|
|
|
|
let name = CString::new(args.name.replace('\0', "?"))
|
|
.map_err(|_| "failed to prepare demo name".to_string())?;
|
|
let message_ptr = unsafe { make_message(name.as_ptr(), args.left, args.right) };
|
|
if message_ptr.is_null() {
|
|
return Err("hs_make_message returned a null pointer".to_string());
|
|
}
|
|
|
|
let message = unsafe { CStr::from_ptr(message_ptr) }
|
|
.to_string_lossy()
|
|
.into_owned();
|
|
unsafe {
|
|
free_string(message_ptr);
|
|
}
|
|
|
|
Ok(format!(
|
|
"Rust -> Haskell demo\nLibrary: {}\nInputs: name={}, left={}, right={}\nStats from Haskell: total={}, product={}, gap={}\nMessage from Haskell: {}",
|
|
library_path.display(),
|
|
args.name,
|
|
args.left,
|
|
args.right,
|
|
stats.total,
|
|
stats.product,
|
|
stats.gap,
|
|
message,
|
|
))
|
|
}
|
|
|
|
fn resolve_library_path(explicit_path: Option<&str>) -> Result<PathBuf, String> {
|
|
if let Some(path) = explicit_path {
|
|
return Ok(PathBuf::from(path));
|
|
}
|
|
|
|
if let Ok(path) = env::var("HASKELL_FOREIGN_LIB") {
|
|
return Ok(PathBuf::from(path));
|
|
}
|
|
|
|
let dist_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("haskell")
|
|
.join("dist-newstyle");
|
|
let mut matches = Vec::new();
|
|
collect_matching_libraries(&dist_dir, &mut matches)?;
|
|
matches.sort();
|
|
|
|
matches.into_iter().next().ok_or_else(|| {
|
|
"could not find the Haskell foreign library under haskell/dist-newstyle; run `make haskell-build` first or pass an explicit path".to_string()
|
|
})
|
|
}
|
|
|
|
fn collect_matching_libraries(root: &Path, matches: &mut Vec<PathBuf>) -> Result<(), String> {
|
|
if !root.exists() {
|
|
return Ok(());
|
|
}
|
|
|
|
let entries = fs::read_dir(root)
|
|
.map_err(|error| format!("failed to read {}: {error}", root.display()))?;
|
|
for entry in entries {
|
|
let entry = entry.map_err(|error| format!("failed to inspect {}: {error}", root.display()))?;
|
|
let path = entry.path();
|
|
if path.is_dir() {
|
|
collect_matching_libraries(&path, matches)?;
|
|
continue;
|
|
}
|
|
|
|
if is_haskell_foreign_library(&path) {
|
|
matches.push(path);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn is_haskell_foreign_library(path: &Path) -> bool {
|
|
let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else {
|
|
return false;
|
|
};
|
|
|
|
let is_library = file_name.ends_with(".so")
|
|
|| file_name.ends_with(".dylib")
|
|
|| file_name.ends_with(".dll");
|
|
|
|
is_library && file_name.contains("interop_hs")
|
|
}
|