#![allow(clippy::unnecessary_cast)]
#![allow(clippy::useless_conversion)]
#![allow(clippy::unit_arg)]
mod backend;
use backend::*;
use parking_lot::ReentrantMutex;
use std::cell::RefCell;
use std::sync::{LazyLock, Once};
use std::{fmt, mem};
#[cfg(test)]
mod tests;
mod bqntype;
mod conversions;
mod macros;
pub use backend::Error;
pub use bqntype::BQNType;
static LOCK: LazyLock<ReentrantMutex<()>> = LazyLock::new(|| ReentrantMutex::new(()));
static INIT: Once = Once::new();
pub struct BQNValue {
value: BQNV,
}
impl BQNValue {
fn new(value: BQNV) -> BQNValue {
BQNValue { value }
}
pub fn null() -> BQNValue {
let _l = LOCK.lock();
crate::INIT.call_once(|| bqn_init().unwrap());
BQNValue::new(bqn_makeChar(0).unwrap())
}
pub fn has_field(&self, field: &str) -> Result<bool> {
let _l = LOCK.lock();
if !BQNValue::is_valid_namespace_field(field) {
return Ok(false);
}
if self.bqn_type() != BQNType::Namespace {
return Err(Error::InvalidType("value isn't a namespace".into()));
}
bqn_hasField(self.value, BQNValue::from(field).value)
}
pub fn get_field(&self, field: &str) -> Result<Option<BQNValue>> {
let _l = LOCK.lock();
if !BQNValue::is_valid_namespace_field(field) {
return Ok(None);
}
if self.bqn_type() != BQNType::Namespace {
return Err(Error::InvalidType("value isn't a namespace".into()));
}
let f = BQNValue::from(field);
Ok(if bqn_hasField(self.value, f.value).unwrap() {
Some(BQNValue::new(bqn_getField(self.value, f.value)?))
} else {
None
})
}
pub fn call1(&self, x: &BQNValue) -> Result<BQNValue> {
let _l = LOCK.lock();
Ok(BQNValue::new(bqn_call1(self.value, x.value)?))
}
pub fn call2(&self, w: &BQNValue, x: &BQNValue) -> Result<BQNValue> {
let _l = LOCK.lock();
Ok(BQNValue::new(bqn_call2(self.value, w.value, x.value)?))
}
pub fn bqn_type(&self) -> BQNType {
BQNType::try_from(bqn_type(self.value).unwrap()).expect("expected to handle all types")
}
pub fn to_f64(&self) -> Result<f64> {
let _l = LOCK.lock();
if self.bqn_type() != BQNType::Number {
return Err(Error::InvalidType("value isn't a number".into()));
}
bqn_readF64(self.value)
}
pub fn to_char(&self) -> Result<Option<char>> {
let _l = LOCK.lock();
if self.bqn_type() != BQNType::Character {
return Err(Error::InvalidType("value isn't a character".into()));
}
Ok(char::from_u32(bqn_readChar(self.value)?))
}
pub fn to_u32(&self) -> Result<u32> {
let _l = LOCK.lock();
if self.bqn_type() != BQNType::Character {
return Err(Error::InvalidType("value isn't a character".into()));
}
bqn_readChar(self.value)
}
pub fn to_f64_vec(&self) -> Result<Vec<f64>> {
let l = LOCK.lock();
let b = self.get_numeric_array_bounds()?;
let mut ret = Vec::with_capacity(b);
#[allow(clippy::uninit_vec)]
unsafe {
ret.set_len(b)
};
bqn_readF64Arr(self.value, &mut ret).unwrap();
drop(l);
Ok(ret)
}
pub fn to_bqnvalue_vec(&self) -> Result<Vec<BQNValue>> {
let l = LOCK.lock();
if self.bqn_type() != BQNType::Array {
return Err(Error::InvalidType("value isn't an object array".into()));
}
let b = self.bound();
let mut objarr = Vec::with_capacity(b);
#[allow(clippy::uninit_vec)]
unsafe {
objarr.set_len(b)
};
bqn_readObjArr(self.value, &mut objarr).unwrap();
drop(l);
Ok(objarr.into_iter().map(BQNValue::new).collect())
}
pub fn rank(&self) -> usize {
bqn_rank(self.value).unwrap()
}
pub fn shape(&self) -> Vec<usize> {
let rank = self.rank();
let mut shape = Vec::with_capacity(rank);
#[allow(clippy::uninit_vec)]
unsafe {
shape.set_len(rank)
};
bqn_shape(self.value, &mut shape).unwrap();
shape
}
fn is_valid_namespace_field(field: &str) -> bool {
field.chars().all(|c| c.is_lowercase() && c != '_')
}
fn to_char_container<T: FromIterator<char>>(&self) -> Result<T> {
let l = LOCK.lock();
let b = self.get_character_array_bounds()?;
let mut u32s = Vec::with_capacity(b);
#[allow(clippy::uninit_vec)]
unsafe {
u32s.set_len(b)
};
bqn_readC32Arr(self.value, &mut u32s).unwrap();
drop(l);
Ok(u32s.into_iter().filter_map(char::from_u32).collect::<T>())
}
pub fn to_char_vec(&self) -> Result<Vec<char>> {
self.to_char_container::<Vec<char>>()
}
pub fn to_string(&self) -> Result<String> {
self.to_char_container::<String>()
}
pub fn fn1(func: fn(&BQNValue) -> BQNValue) -> BQNValue {
INIT.call_once(|| {
let _l = LOCK.lock();
bqn_init().unwrap();
});
let mut key = 0;
FNS.with(|fns| {
let mut boundfns = fns.borrow_mut();
let mut exists = false;
for f in boundfns.boundfn_1.iter() {
if *f as usize == func as usize {
exists = true;
break;
}
}
if !exists {
boundfns.boundfn_1.push(func);
key = boundfns.boundfn_1.len() as u64 - 1;
}
});
let obj = BQNValue::from(f64::from_bits(key));
let _l = LOCK.lock();
BQNValue {
value: bqn_makeBoundFn1(Some(boundfn_1_wrapper), obj.value).unwrap(),
}
}
pub fn fn2(func: fn(&BQNValue, &BQNValue) -> BQNValue) -> BQNValue {
INIT.call_once(|| {
let _l = LOCK.lock();
bqn_init().unwrap()
});
let mut key = 0;
FNS.with(|fns| {
let mut boundfns = fns.borrow_mut();
let mut exists = false;
for f in boundfns.boundfn_2.iter() {
if *f as usize == func as usize {
exists = true;
break;
}
}
if !exists {
boundfns.boundfn_2.push(func);
key = boundfns.boundfn_2.len() as u64 - 1;
}
});
let obj = BQNValue::from(f64::from_bits(key));
let _l = LOCK.lock();
BQNValue {
value: bqn_makeBoundFn2(Some(boundfn_2_wrapper), obj.value).unwrap(),
}
}
fn bound(&self) -> usize {
bqn_bound(self.value).unwrap() as usize
}
fn direct_arr_type(&self) -> u32 {
bqn_directArrType(self.value).unwrap()
}
fn get_character_array_bounds(&self) -> Result<usize> {
if self.bqn_type() != BQNType::Array {
return Err(Error::InvalidType("value isn't an array".into()));
}
let b = self.bound();
if !self.known_char_arr() {
for i in 0..b {
let t = BQNType::try_from({
let v = bqn_pick(self.value, i)?;
let t = bqn_type(v)?;
bqn_free(v)?;
t
})
.expect("expected known type");
if t != BQNType::Character {
return Err(Error::InvalidType("value isn't a character array".into()));
}
}
}
Ok(b)
}
fn get_numeric_array_bounds(&self) -> Result<usize> {
if self.bqn_type() != BQNType::Array {
return Err(Error::InvalidType("value isn't an array".into()));
}
let b = self.bound();
if !self.known_f64_arr() {
for i in 0..b {
let t = BQNType::try_from({
let v = bqn_pick(self.value, i)?;
let t = bqn_type(v)?;
bqn_free(v)?;
t
})
.expect("expected known type");
if t != BQNType::Number {
return Err(Error::InvalidType("value isn't a f64 array".into()));
}
}
}
Ok(b)
}
fn known_f64_arr(&self) -> bool {
#![allow(non_upper_case_globals)]
matches!(
self.direct_arr_type(),
BQNElType_elt_f64 | BQNElType_elt_i32 | BQNElType_elt_i16 | BQNElType_elt_i8
)
}
fn known_char_arr(&self) -> bool {
#![allow(non_upper_case_globals)]
matches!(
self.direct_arr_type(),
BQNElType_elt_c32 | BQNElType_elt_c16 | BQNElType_elt_c8
)
}
}
impl fmt::Debug for BQNValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fmt = crate::eval("•Fmt").expect("fmt");
let formatted = fmt.call1(self).expect("fmt.call1");
write!(
f,
"{}",
formatted.to_string().expect("formatted.to_string()")
)
}
}
impl Clone for BQNValue {
fn clone(&self) -> BQNValue {
let _l = LOCK.lock();
BQNValue {
value: bqn_copy(self.value).unwrap(),
}
}
}
impl Drop for BQNValue {
fn drop(&mut self) {
let _l = LOCK.lock();
bqn_free(self.value).unwrap();
}
}
#[derive(Default)]
pub(crate) struct BoundFns {
boundfn_1: Vec<fn(&BQNValue) -> BQNValue>,
boundfn_2: Vec<fn(&BQNValue, &BQNValue) -> BQNValue>,
}
thread_local! {
static FNS: RefCell<BoundFns> = RefCell::new(BoundFns::default());
}
unsafe extern "C" fn boundfn_1_wrapper(obj: BQNV, x: BQNV) -> BQNV {
let key = BQNValue::new(obj)
.to_f64()
.expect("boundfn obj to_f64")
.to_bits() as usize;
FNS.with(|fns| {
let boundfns = fns.borrow();
let tgt = &boundfns.boundfn_1[key];
let ret = tgt(&BQNValue::new(x));
let retval = ret.value;
mem::forget(ret);
retval
})
}
unsafe extern "C" fn boundfn_2_wrapper(obj: BQNV, w: BQNV, x: BQNV) -> BQNV {
let key = BQNValue::new(obj)
.to_f64()
.expect("boundfn obj to_f64")
.to_bits() as usize;
FNS.with(|fns| {
let boundfns = fns.borrow();
let tgt = &boundfns.boundfn_2[key];
let ret = tgt(&BQNValue::new(w), &BQNValue::new(x));
let retval = ret.value;
mem::forget(ret);
retval
})
}
pub fn eval(bqn: &str) -> Result<BQNValue> {
INIT.call_once(|| {
let _l = LOCK.lock();
bqn_init().unwrap();
});
let _l = LOCK.lock();
backend_eval(bqn)
}
pub fn init() {
let _l = LOCK.lock();
crate::INIT.call_once(|| bqn_init().unwrap());
}