1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
//! Utilities for interacting with a Wolfram Kernel process via WSTP.
//!
//! # Example
//!
//! Launch a new Wolfram Kernel process from the file path to a
//! [`WolframKernel`][WolframKernel] executable:
//!
//! ```no_run
//! use std::path::PathBuf;
//! use wstp::kernel::WolframKernelProcess;
//!
//! let exe = PathBuf::from(
//! "/Applications/Mathematica.app/Contents/MacOS/WolframKernel"
//! );
//!
//! let kernel = WolframKernelProcess::launch(&exe).unwrap();
//! ```
//!
//! ### Automatic Wolfram Kernel discovery
//!
//! Use the [wolfram-app-discovery] crate to automatically discover a suitable
//! `WolframKernel`:
//!
//! ```no_run
//! use std::path::PathBuf;
//! use wolfram_app_discovery::WolframApp;
//! use wstp::kernel::WolframKernelProcess;
//!
//! let app = WolframApp::try_default()
//! .expect("unable to find any Wolfram Language installations");
//!
//! let exe: PathBuf = app.kernel_executable_path().unwrap();
//!
//! let kernel = WolframKernelProcess::launch(&exe).unwrap();
//! ```
//!
//! Using automatic discovery makes it easy to write programs that are portable to
//! different computers, without relying on end-user configuration to specify the location
//! of the local Wolfram Language installation.
//!
//!
//! [WolframKernel]: https://reference.wolfram.com/language/ref/program/WolframKernel.html
//! [wolfram-app-discovery]: https://crates.io/crates/wolfram-app-discovery
//!
//!
//! # Related Links
//!
//! #### Wolfram Language documentation
//!
//! These resources describe the packet expression interface used by the Wolfram Kernel.
//!
//! * [WSTP Packets](https://reference.wolfram.com/language/guide/WSTPPackets.html)
//! * [Running the Wolfram System from within an External Program](https://reference.wolfram.com/language/tutorial/RunningTheWolframSystemFromWithinAnExternalProgram.html)
//!
//! #### Link packet methods
//!
//! * [`Link::put_eval_packet()`]
use std::{path::PathBuf, process};
use wolfram_expr::Expr;
use crate::{Error as WstpError, Link, Protocol};
/// Handle to a Wolfram Kernel process connected via WSTP.
///
/// Use [`WolframKernelProcess::launch()`] to launch a new Wolfram Kernel process.
///
/// Use [`WolframKernelProcess::link()`] to access the WSTP [`Link`] used to communicate with
/// this kernel.
#[derive(Debug)]
pub struct WolframKernelProcess {
#[allow(dead_code)]
process: process::Child,
link: Link,
}
/// Wolfram Kernel process error.
#[derive(Debug)]
pub struct Error(String);
impl From<WstpError> for Error {
fn from(err: WstpError) -> Error {
Error(format!("WSTP error: {err}"))
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error(format!("IO error: {err}"))
}
}
impl WolframKernelProcess {
/// Launch a new Wolfram Kernel child process and establish a WSTP connection with it.
///
/// See also the [wolfram-app-discovery](https://crates.io/crates/wolfram-app-discovery)
/// crate, whose
/// [`WolframApp::kernel_executable_path()`](https://docs.rs/wolfram-app-discovery/0.2.0/wolfram_app_discovery/struct.WolframApp.html#method.kernel_executable_path)
/// method can be used to get the location of a [`WolframKernel`][WolframKernel]
/// executable suitable for use with this function.
///
/// [WolframKernel]: https://reference.wolfram.com/language/ref/program/WolframKernel.html
//
// TODO: Would it be correct to describe this as essentially `LinkLaunch`? Also note
// that this doesn't actually use `-linkmode launch`.
pub fn launch(path: &PathBuf) -> Result<WolframKernelProcess, Error> {
// FIXME: Make this a random string.
const NAME: &str = "SHM_WK_LINK";
let listener = std::thread::spawn(|| {
// This will block until a connection is made.
Link::listen(Protocol::SharedMemory, NAME)
});
let kernel_process = process::Command::new(path)
.arg("-wstp")
.arg("-linkprotocol")
.arg("SharedMemory")
.arg("-linkconnect")
.arg("-linkname")
.arg(NAME)
.spawn()?;
let link: Link = match listener.join() {
Ok(result) => result?,
Err(panic) => {
return Err(Error(format!(
"unable to launch Wolfram Kernel: listening thread panicked: {:?}",
panic
)))
},
};
Ok(WolframKernelProcess {
process: kernel_process,
link,
})
}
/// Get the WSTP [`Link`] connection used to communicate with this Wolfram Kernel
/// process.
pub fn link(&mut self) -> &mut Link {
let WolframKernelProcess { process: _, link } = self;
link
}
}
impl Link {
/// Put an [`EvaluatePacket[expr]`][EvaluatePacket] onto the link.
///
/// [EvaluatePacket]: https://reference.wolfram.com/language/ref/EvaluatePacket.html
pub fn put_eval_packet(&mut self, expr: &Expr) -> Result<(), Error> {
self.put_function("System`EvaluatePacket", 1)?;
self.put_expr(expr)?;
self.end_packet()?;
Ok(())
}
}