diff --git a/Cargo.lock b/Cargo.lock index 601e6d2b3..6af67a047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3514,19 +3514,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -3551,9 +3552,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3561,9 +3562,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -3574,9 +3575,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" diff --git a/Cargo.toml b/Cargo.toml index 2dd5a7197..896626c55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,7 @@ ego-tree = "0.6.2" serde_json = "1.0.122" serde = "1.0.204" +wasm-bindgen = "0.2.93" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] crossterm = { version = "0.28.1", optional = true } diff --git a/docs/shared_library/libscryer_prolog.h b/docs/shared_library/libscryer_prolog.h index e7face2e3..169bfd8c3 100644 --- a/docs/shared_library/libscryer_prolog.h +++ b/docs/shared_library/libscryer_prolog.h @@ -18,6 +18,7 @@ extern "C" { /** * Consults a Scryer Prolog module from a string, optionally with a given module name. + * Will panic if provided invalid Scryer Prolog code. * * # Safety * @@ -40,10 +41,6 @@ extern "C" { * Scryer Prolog module name. * * `input` - A pointer to a null-terminated UTF-8 string representing Scryer Prolog code. * - * # Returns - * - * - [`true`] if the module was successfully loaded. - * - [`false`] if an error occurred while loading the module. */ void scryer_consult_module_string(struct Machine *machine, const char *module_name, @@ -100,22 +97,18 @@ void scryer_query_state_free(struct QueryState *query_state); /** * Greedily evaluate a prolog query, returning all results in a JSON-formatted string. + * Errors are printed to stderr and a null pointer is returned. * * # Safety * * * `machine` must be a valid [`Machine`] pointer created with [`machine_new`] * that has not yet been freed. - * * `query_state` must be a valid [`QueryState`] created with - * [`run_query_iter`] that has not yet been freed. * * There must be no existing [`QueryState`] for this [`Machine`] started by * [`run_query_iter`] that has not yet been freed with * [`query_state_free`] or the [`Machine`] state will enter an undefined * configuration with unpredictable results. * * it is the responsibility of the caller to deallocate the pointer returned by * this function with [`scyer_free_c_string`] in order to avoid memory leaks. - * * once the first boolean result has been returned, the query has been exhausted, - * and the caller should call [`query_state_free`] on the - * [`QueryState`] before calling any other [`Machine`] functions from this shared library. * * * # Returns @@ -128,34 +121,29 @@ void scryer_query_state_free(struct QueryState *query_state); * // current limitation is that only concrete (equality) bindings are returned, * // residual goals not yet supported. * { - * "status": "ok", // Can also be "error" or "panic" + * "status": "ok", * "result": [{ ... }], * } * * // if result is a boolean goal * * { - * "status": "ok", // Can also be "error" or "panic" + * "status": "ok", * "result": boolean * } - * - * // if panic - * { - * "status": "error" | "panic", - * "error": error message | "panic" - * } */ char *scryer_run_query(struct Machine *machine, const char *input); /** - * Returns a new query generator for the given virtual machine. + * Returns a new query generator for the given virtual machine. Null pointer returned + * if string is not valid UTF-8. * * # Safety * * Caller must satisfy the following preconditions for this function: * * * Valid [`Machine`] pointer created with [`machine_new`] that has not yet been freed. - * * the input fulfill the safety requirements of [`CStr::From_ptr`]. + * * the input fulfill the safety requirements of [`CStr::from_ptr`]. * * There must be no other [`QueryState`] for this [`Machine`] started by * [`run_query_iter`] that has not yet been freed with * [`query_state_free`] or the [`Machine`] state will enter an undefined @@ -163,7 +151,7 @@ char *scryer_run_query(struct Machine *machine, const char *input); * * Other concerns: * * after invoking this function, calling any other function besides [`run_query_next`] - * before invoking [`query_state_free`] on the [`Machine`] pointer and [`QueryState`] pointer + * before invoking [`query_state_free`] on the [`QueryState`] pointer * will leave the [`Machine`] in an undefined state * */ @@ -171,7 +159,7 @@ struct QueryState *scryer_run_query_iter(struct Machine *machine, const char *in /** * Returns a NULL POINTER if no addition iterations, else returns a - * JSON encoded UTF-8 string with one iteration of results from a Scryer Prolog query. + * UTF-8 encoded JSON string with one iteration of results from a Scryer Prolog query. * * See documentation for known limitations (e.g., concrete goals only). * diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs new file mode 100644 index 000000000..71c6ab246 --- /dev/null +++ b/src/ffi/mod.rs @@ -0,0 +1,4 @@ +#[cfg(not(target_arch = "wasm32"))] +pub mod shared_library; +#[cfg(target_arch = "wasm32")] +pub mod wasm; diff --git a/src/ffi/shared_library.rs b/src/ffi/shared_library.rs new file mode 100644 index 000000000..5cc11c2bc --- /dev/null +++ b/src/ffi/shared_library.rs @@ -0,0 +1,251 @@ +#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/docs/shared_library/README.md"))] + +use std::ffi::{c_char, CStr, CString}; + +use crate::machine::lib_machine::QueryState; +use crate::machine::parsed_results::QueryResolution; +use crate::machine::Machine; +use serde_json::{json, Value}; + +/// Create a new instance of the Scryer Machine. +/// +/// +/// It is the caller's responsibility to maintain a reference to the machine pointer +/// created by this function, passing it to [`machine_free`] to deallocate +/// resources. Failure to do so may lead to memory leaks. +/// +#[export_name = "scryer_machine_new"] +pub extern "C" fn machine_new() -> *mut Machine { + let machine = Box::into_raw(Box::new(Machine::new_lib())); + machine +} + +/// Frees the memory occupied by a [`Machine`] object. +/// +/// # Safety +/// +/// * It is the caller's responsibility to ensure that the input is a valid [`Machine`] pointer +/// created by [`machine_new`] that has not yet been freed by this function. +/// * Any [`QueryState`] instances associated with this machine should be deallocated prior +/// to calling this function on the input, otherwise those [`QueryState`] instances may +/// not be deallocated until the program terminates. +/// +#[export_name = "scryer_machine_free"] +pub unsafe extern "C" fn machine_free(ptr: *mut Machine) { + unsafe { + drop(Box::from_raw(ptr)); + } +} + +/// Returns a new query generator for the given virtual machine. Null pointer returned +/// if string is not valid UTF-8. +/// +/// # Safety +/// +/// Caller must satisfy the following preconditions for this function: +/// +/// * Valid [`Machine`] pointer created with [`machine_new`] that has not yet been freed. +/// * the input fulfill the safety requirements of [`CStr::from_ptr`]. +/// * There must be no other [`QueryState`] for this [`Machine`] started by +/// [`run_query_iter`] that has not yet been freed with +/// [`query_state_free`] or the [`Machine`] state will enter an undefined +/// configuration with unpredictable results. +/// +/// Other concerns: +/// * after invoking this function, calling any other function besides [`run_query_next`] +/// before invoking [`query_state_free`] on the [`QueryState`] pointer +/// will leave the [`Machine`] in an undefined state +/// +#[export_name = "scryer_run_query_iter"] +pub unsafe extern "C" fn run_query_iter( + machine: &mut Machine, + input: *const c_char, +) -> *mut QueryState { + match unsafe { CStr::from_ptr(input) }.to_str() { + Ok(input) => { + let string = input.to_string(); + let query_state = machine.run_query_iter(string); + Box::into_raw(Box::new(query_state)) + } + Err(err) => std::ptr::null_mut(), + } +} + +/// Cleans up the [`QueryState`] in the associated Scryer [`Machine`]. +/// +/// # Safety +/// +/// * `query_state` must be a valid mutable pointer to a [`QueryState`] created by [`run_query_iter`] +/// that has not yet been freed by [`query_state_free`]. +/// * There can be only one [`QueryState`] per [`Machine`] started by +/// [`run_query_iter`] that has not yet been freed with +/// [`query_state_free`] or the [`Machine`] state will enter an undefined +/// configuration with unpredictable results. +#[export_name = "scryer_query_state_free"] +pub unsafe extern "C" fn query_state_free(query_state: *mut QueryState) { + unsafe { + drop(Box::from_raw(query_state)); + } +} + +/// Returns a NULL POINTER if no addition iterations, else returns a +/// UTF-8 encoded JSON string with one iteration of results from a Scryer Prolog query. +/// +/// See documentation for known limitations (e.g., concrete goals only). +/// +/// # Safety +/// +/// * `query_state` must be a valid mutable pointer to a [`QueryState`] created by [`run_query_iter`] +/// that has not yet been freed by [`query_state_free`]. +/// * There can be only one [`QueryState`] per [`Machine`] started by +/// [`run_query_iter`] that has not yet been freed with +/// [`query_state_free`] or the [`Machine`] state will enter an undefined +/// configuration with unpredictable results. +#[export_name = "scryer_run_query_next"] +pub extern "C" fn run_query_next(query_state: &mut QueryState) -> *mut c_char { + match query_state.next() { + None => std::ptr::null_mut(), + Some(Ok(query_resolution_line)) => { + let v = QueryResolution::from(vec![query_resolution_line]).to_string(); + + let obj = serde_json::from_str::(&v).expect("Bad JSON"); + + let output_string = json!({ + "status": "ok", + "result": obj + }) + .to_string(); + + CString::new(output_string.to_string()).unwrap().into_raw() + } + Some(Err(err)) => { + let output_string = json!({ + "status": "error", + "error": err.to_string(), + }) + .to_string(); + + CString::new(output_string.to_string()).unwrap().into_raw() + } + } +} + +/// Consults a Scryer Prolog module from a string, optionally with a given module name. +/// Will panic if provided invalid Scryer Prolog code. +/// +/// # Safety +/// +/// * `machine` must be a valid mutable pointer to a [`Machine`] created by +/// [`machine_new`] that has not yet been freed. +/// * `input` must be a valid pointer to a null terminated UTF-8 string of valid +/// Scryer Prolog code that satisfies the safety requirements of [`CStr::from_ptr`]. +/// * `module_name`, must be a valid pointer to a null terminated UTF-8 string +/// of a valid Prolog module name that satisfies the safety requirements of +/// [`CStr::from_ptr`]. +/// * there must be no [`QueryState`] for this [`Machine`] that has been created by +/// [`run_query_iter`] that has not yet been freed by +/// [`query_state_free`], or the [`Machine`] will enter an undefined state +/// with unpredictable behavior. +/// +/// # Arguments +/// +/// * `machine` - A mutable reference to the [`Machine`] to load the module into. +/// * `module_name` - A pointer to a null-terminated UTF-8 string representing +/// Scryer Prolog module name. +/// * `input` - A pointer to a null-terminated UTF-8 string representing Scryer Prolog code. +/// +#[export_name = "scryer_consult_module_string"] +pub unsafe extern "C" fn consult_module_string( + machine: &mut Machine, + module_name: *const c_char, + input: *const c_char, +) { + let c_str: &CStr; + unsafe { + c_str = CStr::from_ptr(input); + } + let r_str = c_str.to_str().expect("Not a valid UTF-8 string"); + + let m_str: &CStr; + unsafe { + m_str = CStr::from_ptr(module_name); + } + let module_name = m_str.to_str().expect("Not a valid UTF-8 string"); + + machine.consult_module_string(&module_name, r_str.to_owned()) +} + +/// Greedily evaluate a prolog query, returning all results in a JSON-formatted string. +/// Errors are printed to stderr and a null pointer is returned. +/// +/// # Safety +/// +/// * `machine` must be a valid [`Machine`] pointer created with [`machine_new`] +/// that has not yet been freed. +/// * There must be no existing [`QueryState`] for this [`Machine`] started by +/// [`run_query_iter`] that has not yet been freed with +/// [`query_state_free`] or the [`Machine`] state will enter an undefined +/// configuration with unpredictable results. +/// * it is the responsibility of the caller to deallocate the pointer returned by +/// this function with [`scyer_free_c_string`] in order to avoid memory leaks. +/// +/// +/// # Returns +/// - Returns a pointer to a JSON formatted, null terminated UTF-8 string +/// +/// # Response Format +/// ```json +/// // if result is a binding +/// +/// // current limitation is that only concrete (equality) bindings are returned, +/// // residual goals not yet supported. +/// { +/// "status": "ok", +/// "result": [{ ... }], +/// } +/// +/// // if result is a boolean goal +/// +/// { +/// "status": "ok", +/// "result": boolean +/// } +#[export_name = "scryer_run_query"] +pub unsafe extern "C" fn run_query(machine: &mut Machine, input: *const c_char) -> *mut c_char { + let c_string; + let r_str; + unsafe { + c_string = CStr::from_ptr(input); + }; + r_str = c_string.to_str().expect("Not a valid UTF-8 string"); + let query_resolution = machine.run_query(r_str.to_owned()); + + let output_string: String = match query_resolution { + Ok(query_resolution_line) => { + let value: Value = serde_json::from_str(&format!("{}", query_resolution_line)).unwrap(); + json!( { + "status": "ok", + "result": value + }) + .to_string() + } + Err(err) => { + eprintln!("Error {err}"); + return std::ptr::null_mut(); + } + }; + CString::new(output_string).unwrap().into_raw() +} + +/// Deallocate a Scryer Prolog string. +/// +/// # Safety +/// +/// * it is the caller's responsibility to ensure the `ptr` is not deallocated more than +/// once. +#[export_name = "scryer_free_c_string"] +pub unsafe extern "C" fn free_c_string(ptr: *mut c_char) { + unsafe { + let _ = CString::from_raw(ptr); + } +} diff --git a/src/ffi/wasm.rs b/src/ffi/wasm.rs new file mode 100644 index 000000000..370064c5b --- /dev/null +++ b/src/ffi/wasm.rs @@ -0,0 +1,12 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn eval_code(s: &str) -> String { + use machine::mock_wam::*; + + console_error_panic_hook::set_once(); + + let mut wam = Machine::with_test_streams(); + let bytes = wam.test_load_string(s); + String::from_utf8_lossy(&bytes).to_string() +} diff --git a/src/lib.rs b/src/lib.rs index b17ad68dd..748758be2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,7 @@ mod allocator; mod arithmetic; pub mod codegen; mod debray_allocator; -#[cfg(feature = "ffi")] -mod ffi; + mod forms; mod heap_iter; pub mod heap_print; @@ -42,20 +41,4 @@ mod repl_helper; mod targets; pub mod types; -pub mod lib { - #[cfg(target_arch = "wasm32")] - pub mod wasm { - use wasm_bindgen::prelude::*; - - #[wasm_bindgen] - pub fn eval_code(s: &str) -> String { - use machine::mock_wam::*; - - console_error_panic_hook::set_once(); - - let mut wam = Machine::with_test_streams(); - let bytes = wam.test_load_string(s); - String::from_utf8_lossy(&bytes).to_string() - } - } -} +pub mod ffi; diff --git a/src/ffi.rs b/src/machine/ffi.rs similarity index 100% rename from src/ffi.rs rename to src/machine/ffi.rs diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index 9f22d0e3b..4ae91ec94 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -2,9 +2,9 @@ use crate::arena::*; use crate::atom_table::*; use crate::parser::ast::*; -#[cfg(feature = "ffi")] -use crate::ffi::FFIError; use crate::forms::*; +#[cfg(feature = "ffi")] +use crate::machine::ffi::FFIError; use crate::machine::heap::*; use crate::machine::loader::CompilationTarget; use crate::machine::machine_state::*; diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 5b3a60e5b..8f9e0ea8f 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -29,16 +29,19 @@ pub mod system_calls; pub mod term_stream; pub mod unify; +#[cfg(feature = "ffi")] +mod ffi; + use crate::arena::*; use crate::arithmetic::*; use crate::atom_table::*; -#[cfg(feature = "ffi")] -use crate::ffi::ForeignFunctionTable; use crate::forms::*; use crate::instructions::*; use crate::machine::args::*; use crate::machine::compile::*; use crate::machine::copier::*; +#[cfg(feature = "ffi")] +use crate::machine::ffi::ForeignFunctionTable; use crate::machine::heap::*; use crate::machine::loader::*; use crate::machine::machine_errors::*; diff --git a/src/machine/shared_library.rs b/src/machine/shared_library.rs index 80568804a..8b1378917 100644 --- a/src/machine/shared_library.rs +++ b/src/machine/shared_library.rs @@ -1,261 +1 @@ -pub mod shared_library { - #![doc = include_str!("../../docs/shared_library/README.md")] - #[cfg(not(target_arch = "wasm32"))] - pub mod dll { - use std::ffi::{c_char, CStr, CString}; - use crate::machine::lib_machine::QueryState; - use crate::machine::parsed_results::QueryResolution; - use crate::machine::Machine; - use serde_json::{json, Value}; - - /// Create a new instance of the Scryer Machine. - /// - /// - /// It is the caller's responsibility to maintain a reference to the machine pointer - /// created by this function, passing it to [`machine_free`] to deallocate - /// resources. Failure to do so may lead to memory leaks. - /// - #[export_name = "scryer_machine_new"] - pub extern "C" fn machine_new() -> *mut Machine { - let machine = Box::into_raw(Box::new(Machine::new_lib())); - machine - } - - /// Frees the memory occupied by a [`Machine`] object. - /// - /// # Safety - /// - /// * It is the caller's responsibility to ensure that the input is a valid [`Machine`] pointer - /// created by [`machine_new`] that has not yet been freed by this function. - /// * Any [`QueryState`] instances associated with this machine should be deallocated prior - /// to calling this function on the input, otherwise those [`QueryState`] instances may - /// not be deallocated until the program terminates. - /// - #[export_name = "scryer_machine_free"] - pub unsafe extern "C" fn machine_free(ptr: *mut Machine) { - unsafe { - drop(Box::from_raw(ptr)); - } - } - - /// Returns a new query generator for the given virtual machine. Null pointer returned - /// if string is not valid UTF-8. - /// - /// # Safety - /// - /// Caller must satisfy the following preconditions for this function: - /// - /// * Valid [`Machine`] pointer created with [`machine_new`] that has not yet been freed. - /// * the input fulfill the safety requirements of [`CStr::from_ptr`]. - /// * There must be no other [`QueryState`] for this [`Machine`] started by - /// [`run_query_iter`] that has not yet been freed with - /// [`query_state_free`] or the [`Machine`] state will enter an undefined - /// configuration with unpredictable results. - /// - /// Other concerns: - /// * after invoking this function, calling any other function besides [`run_query_next`] - /// before invoking [`query_state_free`] on the [`QueryState`] pointer - /// will leave the [`Machine`] in an undefined state - /// - #[export_name = "scryer_run_query_iter"] - pub unsafe extern "C" fn run_query_iter( - machine: &mut Machine, - input: *const c_char, - ) -> *mut QueryState { - match unsafe { CStr::from_ptr(input) }.to_str() { - Ok(input) => { - let string = input.to_string(); - let query_state = machine.run_query_iter(string); - Box::into_raw(Box::new(query_state)) - } - Err(err) => { - std::ptr::null_mut() - } - } - } - - /// Cleans up the [`QueryState`] in the associated Scryer [`Machine`]. - /// - /// # Safety - /// - /// * `query_state` must be a valid mutable pointer to a [`QueryState`] created by [`run_query_iter`] - /// that has not yet been freed by [`query_state_free`]. - /// * There can be only one [`QueryState`] per [`Machine`] started by - /// [`run_query_iter`] that has not yet been freed with - /// [`query_state_free`] or the [`Machine`] state will enter an undefined - /// configuration with unpredictable results. - #[export_name = "scryer_query_state_free"] - pub unsafe extern "C" fn query_state_free(query_state: *mut QueryState) { - unsafe { - drop(Box::from_raw(query_state)); - } - } - - /// Returns a NULL POINTER if no addition iterations, else returns a - /// UTF-8 encoded JSON string with one iteration of results from a Scryer Prolog query. - /// - /// See documentation for known limitations (e.g., concrete goals only). - /// - /// # Safety - /// - /// * `query_state` must be a valid mutable pointer to a [`QueryState`] created by [`run_query_iter`] - /// that has not yet been freed by [`query_state_free`]. - /// * There can be only one [`QueryState`] per [`Machine`] started by - /// [`run_query_iter`] that has not yet been freed with - /// [`query_state_free`] or the [`Machine`] state will enter an undefined - /// configuration with unpredictable results. - #[export_name = "scryer_run_query_next"] - pub extern "C" fn run_query_next(query_state: &mut QueryState) -> *mut c_char { - match query_state.next() { - None => std::ptr::null_mut(), - Some(Ok(query_resolution_line)) => { - let v = QueryResolution::from(vec![query_resolution_line]).to_string(); - - let obj = serde_json::from_str::(&v).expect("Bad JSON"); - - let output_string = json!({ - "status": "ok", - "result": obj - }) - .to_string(); - - CString::new(output_string.to_string()).unwrap().into_raw() - } - Some(Err(err)) => { - let output_string = json!({ - "status": "error", - "error": err.to_string(), - }) - .to_string(); - - CString::new(output_string.to_string()).unwrap().into_raw() - } - } - } - - /// Consults a Scryer Prolog module from a string, optionally with a given module name. - /// Will panic if provided invalid Scryer Prolog code. - /// - /// # Safety - /// - /// * `machine` must be a valid mutable pointer to a [`Machine`] created by - /// [`machine_new`] that has not yet been freed. - /// * `input` must be a valid pointer to a null terminated UTF-8 string of valid - /// Scryer Prolog code that satisfies the safety requirements of [`CStr::from_ptr`]. - /// * `module_name`, must be a valid pointer to a null terminated UTF-8 string - /// of a valid Prolog module name that satisfies the safety requirements of - /// [`CStr::from_ptr`]. - /// * there must be no [`QueryState`] for this [`Machine`] that has been created by - /// [`run_query_iter`] that has not yet been freed by - /// [`query_state_free`], or the [`Machine`] will enter an undefined state - /// with unpredictable behavior. - /// - /// # Arguments - /// - /// * `machine` - A mutable reference to the [`Machine`] to load the module into. - /// * `module_name` - A pointer to a null-terminated UTF-8 string representing - /// Scryer Prolog module name. - /// * `input` - A pointer to a null-terminated UTF-8 string representing Scryer Prolog code. - /// - #[export_name = "scryer_consult_module_string"] - pub unsafe extern "C" fn consult_module_string( - machine: &mut Machine, - module_name: *const c_char, - input: *const c_char, - ) { - let c_str: &CStr; - unsafe { - c_str = CStr::from_ptr(input); - } - let r_str = c_str.to_str().expect("Not a valid UTF-8 string"); - - let m_str: &CStr; - unsafe { - m_str = CStr::from_ptr(module_name); - } - let module_name = m_str.to_str().expect("Not a valid UTF-8 string"); - - machine.consult_module_string(&module_name, r_str.to_owned()) - } - - /// Greedily evaluate a prolog query, returning all results in a JSON-formatted string. - /// Errors are printed to stderr and a null pointer is returned. - /// - /// # Safety - /// - /// * `machine` must be a valid [`Machine`] pointer created with [`machine_new`] - /// that has not yet been freed. - /// * There must be no existing [`QueryState`] for this [`Machine`] started by - /// [`run_query_iter`] that has not yet been freed with - /// [`query_state_free`] or the [`Machine`] state will enter an undefined - /// configuration with unpredictable results. - /// * it is the responsibility of the caller to deallocate the pointer returned by - /// this function with [`scyer_free_c_string`] in order to avoid memory leaks. - /// - /// - /// # Returns - /// - Returns a pointer to a JSON formatted, null terminated UTF-8 string - /// - /// # Response Format - /// ```json - /// // if result is a binding - /// - /// // current limitation is that only concrete (equality) bindings are returned, - /// // residual goals not yet supported. - /// { - /// "status": "ok", - /// "result": [{ ... }], - /// } - /// - /// // if result is a boolean goal - /// - /// { - /// "status": "ok", - /// "result": boolean - /// } - #[export_name = "scryer_run_query"] - pub unsafe extern "C" fn run_query( - machine: &mut Machine, - input: *const c_char, - ) -> *mut c_char { - let c_string; - let r_str; - unsafe { - c_string = CStr::from_ptr(input); - }; - r_str = c_string.to_str().expect("Not a valid UTF-8 string"); - let query_resolution = machine.run_query(r_str.to_owned()); - - let output_string: String = match query_resolution { - Ok(query_resolution_line) => { - let value: Value = - serde_json::from_str(&format!("{}", query_resolution_line)).unwrap(); - json!( { - "status": "ok", - "result": value - }) - .to_string() - } - Err(err) => { - eprintln!("Error {err}"); - return std::ptr::null_mut(); - } - }; - CString::new(output_string).unwrap().into_raw() - } - - /// Deallocate a Scryer Prolog string. - /// - /// # Safety - /// - /// * it is the caller's responsibility to ensure the `ptr` is not deallocated more than - /// once. - #[export_name = "scryer_free_c_string"] - pub unsafe extern "C" fn free_c_string(ptr: *mut c_char) { - unsafe { - let _ = CString::from_raw(ptr); - } - } - } -} diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index ce5803756..6d6bf4cc2 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -9,7 +9,7 @@ use num_order::NumOrd; use crate::arena::*; use crate::atom_table::*; #[cfg(feature = "ffi")] -use crate::ffi::*; +use crate::machine::ffi::*; use crate::forms::*; use crate::heap_iter::*; use crate::heap_print::*; @@ -4745,7 +4745,7 @@ impl Machine { let return_value = self.deref_register(3); if let Some(function_name) = self.machine_st.value_to_str_like(function_name) { let stub_gen = || functor_stub(atom!("foreign_call"), 3); - fn map_arg(machine_st: &mut MachineState, source: HeapCellValue) -> crate::ffi::Value { + fn map_arg(machine_st: &mut MachineState, source: HeapCellValue) -> crate::machine::ffi::Value { match Number::try_from(source) { Ok(Number::Fixnum(n)) => Value::Int(n.get_num()), Ok(Number::Float(n)) => Value::Float(n.into_inner()), diff --git a/tests/scryer/shared_library_tests.rs b/tests/scryer/shared_library_tests.rs index 268b32c5d..34fc57b4b 100644 --- a/tests/scryer/shared_library_tests.rs +++ b/tests/scryer/shared_library_tests.rs @@ -1,12 +1,15 @@ #[cfg(test)] -mod dll_tests { +mod shared_library_tests { use std::ffi::{CStr, CString}; + use scryer_prolog::ffi::shared_library::{ + consult_module_string, free_c_string, machine_free, machine_new, query_state_free, + run_query, run_query_iter, run_query_next, + }; use serde_json::{json, Value}; - // uncomment if we can figure out why this isn't working // use crate::lib::dll::{machine_free}; - use scryer_prolog::machine::shared_library::shared_library::dll::{consult_module_string, free_c_string, machine_free, machine_new, query_state_free, run_query, run_query_iter, run_query_next}; + use scryer_prolog::machine::Machine; #[test] @@ -46,7 +49,9 @@ mod dll_tests { } // breaks if uncommented - unsafe { machine_free(machine_ptr); } + unsafe { + machine_free(machine_ptr); + } } #[test] @@ -86,7 +91,9 @@ mod dll_tests { } unsafe { query_state_free(query_state) } - unsafe { machine_free(machine_ptr); } + unsafe { + machine_free(machine_ptr); + } } #[test] @@ -94,7 +101,9 @@ mod dll_tests { let program = CString::new(":- use_module(library(lists)). :- use_module(library(dif)).").unwrap(); let module_name = CString::new("facts").unwrap(); - let query = CString::new(r#"member(X, [a,"a",f(a),"f(a)", true, "true", false, "false"])."#).unwrap(); + let query = + CString::new(r#"member(X, [a,"a",f(a),"f(a)", true, "true", false, "false"])."#) + .unwrap(); let machine_ptr: *mut Machine = machine_new(); unsafe { consult_module_string(&mut *machine_ptr, module_name.as_ptr(), program.as_ptr());