use core::{
error::Error as StdError,
fmt::{self, Display},
sync::atomic::{AtomicBool, Ordering},
};
#[cfg(feature = "std")]
use std::thread_local;
use crate::{
Janet, JanetTable,
env::{CFunOptions, DefOptions, JanetEnvironment, VarOptions},
};
#[cfg(feature = "std")]
thread_local! {
static INIT: AtomicBool = const { AtomicBool::new(false) };
}
#[cfg(not(feature = "std"))]
static INIT: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[non_exhaustive]
pub enum Error {
AlreadyInit,
CompileError,
EnvNotInit,
ParseError,
RunError,
}
impl Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AlreadyInit => f.pad("Janet client already initialized"),
Self::CompileError => f.pad("Failed to compile code"),
Self::EnvNotInit => f.pad("The environment table was not initialized"),
Self::ParseError => f.pad("Failed to parse code"),
Self::RunError => f.pad("Runtime VM error"),
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl StdError for Error {}
#[derive(Debug)]
pub struct JanetClient {
env_table: Option<JanetEnvironment>,
}
impl JanetClient {
#[inline]
pub fn init() -> Result<Self, Error> {
#[cfg(feature = "std")]
let init_state = INIT.with(|i| i.swap(true, Ordering::SeqCst));
#[cfg(not(feature = "std"))]
let init_state = INIT.swap(true, Ordering::SeqCst);
if init_state {
return Err(Error::AlreadyInit);
}
unsafe { evil_janet::janet_init() };
Ok(Self { env_table: None })
}
#[inline]
#[must_use = "function is a constructor associated function"]
pub unsafe fn init_unchecked() -> Self {
evil_janet::janet_init();
Self { env_table: None }
}
#[inline]
pub fn init_with_default_env() -> Result<Self, Error> {
let mut client = Self::init()?;
client.env_table = Some(JanetEnvironment::default());
Ok(client)
}
#[inline]
pub fn init_with_replacements(replacements: JanetTable<'static>) -> Result<Self, Error> {
let mut client = Self::init()?;
client.env_table = Some(JanetEnvironment::with_replacements(replacements));
Ok(client)
}
#[inline]
#[must_use]
pub fn load_env_default(mut self) -> Self {
self.env_table = Some(JanetEnvironment::default());
self
}
#[inline]
#[must_use]
pub fn load_env_replacements(mut self, replacements: JanetTable<'static>) -> Self {
self.env_table = Some(JanetEnvironment::with_replacements(replacements));
self
}
#[inline]
pub fn add_def(&mut self, def_opt: DefOptions) {
if self.env().is_none() {
self.env_table = Some(JanetEnvironment::default());
}
if let Some(ref mut env) = self.env_table {
env.add_def(def_opt)
}
}
#[inline]
pub fn add_var(&mut self, var_opt: VarOptions) {
if self.env().is_none() {
self.env_table = Some(JanetEnvironment::default());
}
if let Some(ref mut env) = self.env_table {
env.add_var(var_opt);
}
}
#[inline]
pub fn add_c_fn(&mut self, cfun_opt: CFunOptions<'static>) {
if self.env().is_none() {
self.env_table = Some(JanetEnvironment::default());
}
if let Some(ref mut env) = self.env_table {
env.add_c_fn(cfun_opt);
}
}
#[inline]
pub fn run_bytes(&self, code: impl AsRef<[u8]>) -> Result<Janet, Error> {
let code = code.as_ref();
let env = match self.env_table.as_ref() {
Some(e) => e.table(),
None => return Err(Error::EnvNotInit),
};
let mut out = Janet::nil();
let res = unsafe {
evil_janet::janet_dobytes(
env.raw,
code.as_ptr(),
code.len() as i32,
c"main".as_ptr(),
&mut out.inner,
)
};
match res {
0x01 => Err(Error::RunError),
0x02 => Err(Error::CompileError),
0x04 => Err(Error::ParseError),
_ => Ok(out),
}
}
#[inline]
pub fn run(&self, code: impl AsRef<str>) -> Result<Janet, Error> {
let code = code.as_ref();
self.run_bytes(code.as_bytes())
}
#[inline]
#[must_use]
pub const fn env(&self) -> Option<&JanetEnvironment> {
self.env_table.as_ref()
}
#[inline]
pub fn env_mut(&mut self) -> Option<&mut JanetEnvironment> {
self.env_table.as_mut()
}
}
impl Drop for JanetClient {
#[inline]
fn drop(&mut self) {
#[cfg(feature = "std")]
INIT.with(|i| i.swap(false, Ordering::SeqCst));
#[cfg(not(feature = "std"))]
INIT.swap(false, Ordering::SeqCst);
unsafe { evil_janet::janet_deinit() }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn double_init() {
let c1 = JanetClient::init();
let c2 = JanetClient::init();
let c3 = JanetClient::init();
assert!(c1.is_ok());
assert_eq!(Error::AlreadyInit, c2.unwrap_err());
assert_eq!(Error::AlreadyInit, c3.unwrap_err());
}
#[test]
fn env_not_init() -> Result<(), Error> {
let client = JanetClient::init()?;
let a = client.run("()");
assert_eq!(Err(Error::EnvNotInit), a);
Ok(())
}
}