pub(crate) mod conversions;
pub mod wrappers;
use crate::{
Number, TulispValue,
cons::{self, Cons},
error::Error,
object::wrappers::generic::{Shared, SharedMut, SharedRef},
value::TulispAny,
};
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct Span {
pub file_id: usize,
pub start: (usize, usize),
pub end: (usize, usize),
}
impl Span {
pub fn new(file_id: usize, start: (usize, usize), end: (usize, usize)) -> Self {
Span {
file_id,
start,
end,
}
}
}
#[derive(Debug, Clone)]
pub struct TulispObject {
rc: SharedMut<(TulispValue, Option<Span>)>,
}
impl Default for TulispObject {
#[inline(always)]
fn default() -> Self {
TulispObject::nil()
}
}
impl std::fmt::Display for TulispObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", self.rc.borrow().0))
}
}
macro_rules! predicate_fn {
($visibility: vis, $name: ident $(, $doc: literal)?) => {
$(#[doc=$doc])?
#[inline(always)]
$visibility fn $name(&self) -> bool {
self.rc.borrow().0.$name()
}
};
}
macro_rules! extractor_fn_with_err {
($retty: ty, $name: ident $(, $doc: literal)?) => {
$(#[doc=$doc])?
#[inline(always)]
pub fn $name(&self) -> Result<$retty, Error> {
self.rc
.borrow()
.0
.$name()
.map_err(|e| e.with_trace(self.clone()))
}
};
}
impl TulispObject {
#[inline(always)]
pub fn nil() -> TulispObject {
TulispValue::Nil.into_ref(None)
}
#[inline(always)]
pub fn t() -> TulispObject {
TulispValue::T.into_ref(None)
}
#[inline(always)]
pub fn cons(car: TulispObject, cdr: TulispObject) -> TulispObject {
TulispValue::List {
cons: Cons::new(car, cdr),
ctxobj: None,
}
.into_ref(None)
}
pub fn equal(&self, other: &TulispObject) -> bool {
if self.symbolp() {
self.eq(other)
} else {
self.eq_val(other)
}
}
#[allow(clippy::should_implement_trait)]
pub fn eq(&self, other: &TulispObject) -> bool {
self.eq_ptr(other)
|| other.inner_ref().0.lex_symbol_eq(self)
|| self.inner_ref().0.lex_symbol_eq(other)
}
pub fn eql(&self, other: &TulispObject) -> bool {
if self.numberp() {
self.eq_val(other)
} else {
self.eq_ptr(other)
}
}
pub fn base_iter(&self) -> cons::BaseIter {
cons::BaseIter { next: self.clone() }
}
pub fn iter<T: std::convert::TryFrom<TulispObject>>(&self) -> Result<cons::Iter<T>, Error> {
if !self.listp() {
return Err(Error::type_mismatch(format!(
"Expected a list, got {}",
self.fmt_string()
)));
}
Ok(cons::Iter::new(self.base_iter()))
}
pub fn push(&self, val: TulispObject) -> Result<&TulispObject, Error> {
self.rc
.borrow_mut()
.0
.push(val)
.map(|_| self)
.map_err(|e| e.with_trace(self.clone()))
}
pub fn append(&self, other_list: TulispObject) -> Result<&TulispObject, Error> {
self.rc
.borrow_mut()
.0
.append(other_list)
.map(|_| self)
.map_err(|e| e.with_trace(self.clone()))
}
pub fn fmt_string(&self) -> String {
self.rc.borrow().0.fmt_string()
}
pub fn set(&self, to_set: TulispObject) -> Result<(), Error> {
self.rc
.borrow_mut()
.0
.set(to_set)
.map_err(|e| e.with_trace(self.clone()))
}
pub fn set_scope(&self, to_set: TulispObject) -> Result<(), Error> {
self.rc
.borrow_mut()
.0
.set_scope(to_set)
.map_err(|e| e.with_trace(self.clone()))
}
pub fn unset(&self) -> Result<(), Error> {
self.rc
.borrow_mut()
.0
.unset()
.map_err(|e| e.with_trace(self.clone()))
}
pub fn get(&self) -> Result<TulispObject, Error> {
if self.keywordp() {
Ok(self.clone())
} else {
self.rc
.borrow()
.0
.get()
.map_err(|e| e.with_trace(self.clone()))
}
}
extractor_fn_with_err!(
f64,
try_float,
"Returns a float if `self` holds a float or an int, and an Error otherwise."
);
extractor_fn_with_err!(
i64,
try_int,
"Returns an int if `self` holds a float or an int, and an Error otherwise."
);
extractor_fn_with_err!(
f64,
as_float,
"Returns a float is `self` holds a float, and an Error otherwise."
);
extractor_fn_with_err!(
i64,
as_int,
"Returns an int is `self` holds an int, and an Error otherwise."
);
extractor_fn_with_err!(
Number,
as_number,
"Returns a Number (int or float) if `self` holds a number, and an Error otherwise."
);
extractor_fn_with_err!(
String,
as_symbol,
"Returns a string containing symbol name, if `self` is a symbol, and an Error otherwise."
);
extractor_fn_with_err!(
String,
as_string,
"Returns a string if `self` contains a string, and an Error otherwise."
);
extractor_fn_with_err!(
Shared<dyn TulispAny>,
as_any,
r#"Returns a boxed value if `self` contains a boxed value, and an Error otherwise.
Functions exported to _Tulisp_ can return arbitrary boxed values, which can be extracted
with `as_any`, and downcast to desired types.
## Example
```rust
# use tulisp::{TulispContext, destruct_bind, Error, TulispAny, Shared};
#
# fn main() -> Result<(), Error> {
let mut ctx = TulispContext::new();
struct TestStruct {
value: i64,
}
impl std::fmt::Display for TestStruct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"TestStruct {}\"", self.value)
}
}
ctx.defspecial("make_any", |_ctx, args| {
destruct_bind!((inp) = args);
let inp: i64 = inp.try_into()?;
let any_obj = Shared::new(TestStruct { value: inp });
Ok(any_obj.into())
});
let out = ctx.eval_string("(make_any 25)")?;
let ts = out.as_any()?.downcast::<TestStruct>().unwrap();
assert_eq!(ts.value, 25);
#
# Ok(())
# }
```
"#
);
predicate_fn!(pub, consp, "Returns True if `self` is a cons cell.");
predicate_fn!(
pub,
listp,
"Returns True if `self` is a list. i.e., a cons cell or nil."
);
predicate_fn!(pub, integerp, "Returns True if `self` is an integer.");
predicate_fn!(pub, floatp, "Returns True if `self` is a float.");
predicate_fn!(
pub,
numberp,
"Returns True if `self` is a number. i.e., an integer or a float."
);
predicate_fn!(pub, stringp, "Returns True if `self` is a string.");
predicate_fn!(pub, symbolp, "Returns True if `self` is a Symbol.");
predicate_fn!(
pub,
boundp,
"Returns True if `self` is bound in the current scope."
);
predicate_fn!(pub, keywordp, "Returns True if `self` is a Keyword.");
predicate_fn!(pub, null, "Returns True if `self` is `nil`.");
predicate_fn!(pub, is_truthy, "Returns True if `self` is not `nil`.");
predicate_fn!(pub(crate), is_bounce, "Returns True if `self` is a tail-call trampoline bounce object.");
predicate_fn!(pub(crate), is_bounced, "Returns True if `self` is a tail-call trampoline bounced function call.");
}
impl TulispObject {
pub(crate) fn symbol(name: String, constant: bool) -> TulispObject {
TulispValue::symbol(name, constant).into_ref(None)
}
pub(crate) fn lexical_binding(symbol: TulispObject) -> TulispObject {
let span = symbol.span();
TulispValue::lexical_binding(symbol).into_ref(span)
}
pub(crate) fn new(vv: TulispValue, span: Option<Span>) -> TulispObject {
Self {
rc: SharedMut::new((vv, span)),
}
}
pub(crate) fn set_unchecked(&self, to_set: TulispObject) {
self.rc.borrow_mut().0.set_unchecked(to_set)
}
pub(crate) fn set_global(&self, to_set: TulispObject) -> Result<(), Error> {
self.rc.borrow_mut().0.set_global(to_set)
}
pub(crate) fn is_lexically_bound(&self) -> bool {
self.rc.borrow().0.is_lexically_bound()
}
#[inline(always)]
pub(crate) fn eq_ptr(&self, other: &TulispObject) -> bool {
self.rc.ptr_eq(&other.rc)
}
#[inline(always)]
pub(crate) fn eq_val(&self, other: &TulispObject) -> bool {
self.inner_ref().0.eq(&other.inner_ref().0)
}
pub(crate) fn addr_as_usize(&self) -> usize {
self.rc.addr_as_usize()
}
#[inline(always)]
pub(crate) fn strong_count(&self) -> usize {
self.rc.strong_count()
}
#[inline(always)]
pub(crate) fn assign(&self, vv: TulispValue) {
self.rc.borrow_mut().0 = vv
}
pub(crate) fn clone_inner(&self) -> TulispValue {
self.rc.borrow().0.clone()
}
#[inline(always)]
pub(crate) fn inner_ref(&self) -> SharedRef<'_, (TulispValue, Option<Span>)> {
self.rc.borrow()
}
pub(crate) fn as_list_cons(&self) -> Option<Cons> {
self.rc.borrow().0.as_list_cons()
}
pub(crate) fn ctxobj(&self) -> Option<TulispObject> {
self.rc.borrow().0.ctxobj()
}
pub(crate) fn with_ctxobj(&self, in_ctxobj: Option<TulispObject>) -> Self {
self.rc.borrow_mut().0.with_ctxobj(in_ctxobj);
self.clone()
}
pub(crate) fn with_span(&self, in_span: Option<Span>) -> Self {
self.rc.borrow_mut().1 = in_span;
self.clone()
}
pub(crate) fn take(&self) -> TulispValue {
self.rc.borrow_mut().0.take()
}
#[doc(hidden)]
#[inline(always)]
pub fn span(&self) -> Option<Span> {
self.rc.borrow().1
}
#[doc(hidden)]
pub fn deep_copy(&self) -> Result<TulispObject, Error> {
if self.symbolp() {
return Ok(self.clone());
}
let mut ret = if !self.consp() {
self.clone_inner()
} else {
let mut ret = TulispValue::Nil;
let mut val = self.clone(); loop {
let (first, rest) = (val.car()?, val.cdr()?);
let first = if !first.consp() {
first
} else {
first.clone_inner().into_ref(first.span())
};
ret.push_with_meta(first, val.span(), val.ctxobj())?;
if !rest.consp() {
ret.append(rest)?;
break;
}
val = rest;
}
ret
};
ret.with_ctxobj(self.ctxobj());
let ret = ret.into_ref(self.span());
Ok(ret)
}
}
impl TryFrom<TulispObject> for f64 {
type Error = Error;
fn try_from(value: TulispObject) -> Result<Self, Self::Error> {
let res = value.rc.borrow().0.try_float();
res.map_err(|e| e.with_trace(value))
}
}
impl TryFrom<TulispObject> for i64 {
type Error = Error;
fn try_from(value: TulispObject) -> Result<Self, Self::Error> {
let res = value.rc.borrow().0.as_int();
res.map_err(|e| e.with_trace(value))
}
}
impl TryFrom<&TulispObject> for f64 {
type Error = Error;
fn try_from(value: &TulispObject) -> Result<Self, Self::Error> {
value
.rc
.borrow()
.0
.try_float()
.map_err(|e| e.with_trace(value.clone()))
}
}
impl TryFrom<&TulispObject> for i64 {
type Error = Error;
fn try_from(value: &TulispObject) -> Result<Self, Self::Error> {
value
.rc
.borrow()
.0
.as_int()
.map_err(|e| e.with_trace(value.clone()))
}
}
impl TryFrom<TulispObject> for String {
type Error = Error;
fn try_from(value: TulispObject) -> Result<Self, Self::Error> {
value.as_string().map_err(|e| e.with_trace(value))
}
}
impl TryFrom<&TulispObject> for String {
type Error = Error;
fn try_from(value: &TulispObject) -> Result<Self, Self::Error> {
value.as_string().map_err(|e| e.with_trace(value.clone()))
}
}
impl TryFrom<TulispObject> for bool {
type Error = Error;
fn try_from(value: TulispObject) -> Result<Self, Self::Error> {
Ok(value.is_truthy())
}
}
impl TryFrom<&TulispObject> for bool {
type Error = Error;
fn try_from(value: &TulispObject) -> Result<Self, Self::Error> {
Ok(value.is_truthy())
}
}
impl TryFrom<TulispObject> for Shared<dyn TulispAny> {
type Error = Error;
fn try_from(value: TulispObject) -> Result<Self, Self::Error> {
value.as_any().map_err(|e| e.with_trace(value))
}
}
impl TryFrom<&TulispObject> for Shared<dyn TulispAny> {
type Error = Error;
fn try_from(value: &TulispObject) -> Result<Self, Self::Error> {
value.as_any().map_err(|e| e.with_trace(value.clone()))
}
}
impl<T> TryFrom<TulispObject> for Vec<T>
where
T: TryFrom<TulispObject, Error = Error>,
{
type Error = Error;
fn try_from(value: TulispObject) -> Result<Self, Self::Error> {
value
.base_iter()
.map(|item| item.try_into())
.collect::<Result<Vec<T>, Error>>()
.map_err(|e| e.with_trace(value))
}
}
impl<T> TryFrom<&TulispObject> for Vec<T>
where
T: TryFrom<TulispObject, Error = Error>,
{
type Error = Error;
fn try_from(value: &TulispObject) -> Result<Self, Self::Error> {
value
.base_iter()
.map(|item| item.try_into())
.collect::<Result<Vec<T>, Error>>()
.map_err(|e| e.with_trace(value.clone()))
}
}
macro_rules! tulisp_object_from {
($ty: ty) => {
impl From<$ty> for TulispObject {
fn from(vv: $ty) -> Self {
TulispValue::from(vv).into_ref(None)
}
}
};
}
impl From<i64> for TulispObject {
fn from(vv: i64) -> Self {
const CACHE_MIN: i64 = -128;
const CACHE_MAX: i64 = 128;
if (CACHE_MIN..=CACHE_MAX).contains(&vv) {
thread_local! {
static INT_CACHE: Vec<TulispObject> = (CACHE_MIN..=CACHE_MAX)
.map(|i| TulispValue::from(i).into_ref(None))
.collect();
}
INT_CACHE.with(|cache| cache[(vv - CACHE_MIN) as usize].clone())
} else {
TulispValue::from(vv).into_ref(None)
}
}
}
tulisp_object_from!(f64);
tulisp_object_from!(&str);
tulisp_object_from!(String);
tulisp_object_from!(bool);
tulisp_object_from!(Shared<dyn TulispAny>);
impl FromIterator<TulispObject> for TulispObject {
fn from_iter<T: IntoIterator<Item = TulispObject>>(iter: T) -> Self {
TulispValue::from_iter(iter).into_ref(None)
}
}
impl<T> From<Vec<T>> for TulispObject
where
T: Into<TulispObject>,
{
fn from(vec: Vec<T>) -> Self {
vec.into_iter().map(Into::into).collect()
}
}
impl<T> From<Option<T>> for TulispObject
where
T: Into<TulispObject>,
{
fn from(opt: Option<T>) -> Self {
match opt {
Some(v) => v.into(),
None => TulispObject::nil(),
}
}
}
macro_rules! extractor_cxr_fn {
($name: ident, $doc: literal) => {
#[doc=concat!("Returns the ", $doc, " of `self` if it is a list, and an Error otherwise.")]
#[inline(always)]
pub fn $name(&self) -> Result<TulispObject, Error> {
self.rc
.borrow()
.0
.$name()
.map_err(|e| e.with_trace(self.clone()))
}
};
($name: ident) => {
#[doc(hidden)]
#[inline(always)]
pub fn $name(&self) -> Result<TulispObject, Error> {
self.rc
.borrow()
.0
.$name()
.map_err(|e| e.with_trace(self.clone()))
}
};
}
macro_rules! extractor_cxr_and_then_fn {
($name: ident, $doc: literal) => {
#[doc=concat!(
"Executes the given function on the ", $doc, " of `self` and returns the result."
)]
#[inline(always)]
pub fn $name<Out: Default>(
&self,
f: impl FnOnce(&TulispObject) -> Result<Out, Error>,
) -> Result<Out, Error> {
self.rc
.borrow()
.0
.$name(f)
.map_err(|e| e.with_trace(self.clone()))
}
};
($name: ident) => {
#[doc(hidden)]
#[inline(always)]
pub fn $name<Out: Default>(
&self,
f: impl FnOnce(&TulispObject) -> Result<Out, Error>,
) -> Result<Out, Error> {
self.rc
.borrow()
.0
.$name::<Out>(f)
.map_err(|e| e.with_trace(self.clone()))
}
};
}
impl TulispObject {
extractor_cxr_fn!(car, "`car`");
extractor_cxr_fn!(cdr, "`cdr`");
extractor_cxr_fn!(caar, "`car` of `car`");
extractor_cxr_fn!(cadr, "`car` of `cdr`");
extractor_cxr_fn!(cdar, "`cdr` of `car`");
extractor_cxr_fn!(cddr, "`cdr` of `cdr`");
extractor_cxr_fn!(caaar);
extractor_cxr_fn!(caadr);
extractor_cxr_fn!(cadar);
extractor_cxr_fn!(caddr);
extractor_cxr_fn!(cdaar);
extractor_cxr_fn!(cdadr);
extractor_cxr_fn!(cddar);
extractor_cxr_fn!(cdddr);
extractor_cxr_fn!(caaaar);
extractor_cxr_fn!(caaadr);
extractor_cxr_fn!(caadar);
extractor_cxr_fn!(caaddr);
extractor_cxr_fn!(cadaar);
extractor_cxr_fn!(cadadr);
extractor_cxr_fn!(caddar);
extractor_cxr_fn!(cadddr);
extractor_cxr_fn!(cdaaar);
extractor_cxr_fn!(cdaadr);
extractor_cxr_fn!(cdadar);
extractor_cxr_fn!(cdaddr);
extractor_cxr_fn!(cddaar);
extractor_cxr_fn!(cddadr);
extractor_cxr_fn!(cdddar);
extractor_cxr_fn!(cddddr);
extractor_cxr_and_then_fn!(car_and_then, "`car`");
extractor_cxr_and_then_fn!(cdr_and_then, "`cdr`");
extractor_cxr_and_then_fn!(caar_and_then, "`car` of `car`");
extractor_cxr_and_then_fn!(cadr_and_then, "`car` of `cdr`");
extractor_cxr_and_then_fn!(cdar_and_then, "`cdr` of `car`");
extractor_cxr_and_then_fn!(cddr_and_then, "`cdr` of `cdr`");
extractor_cxr_and_then_fn!(caaar_and_then);
extractor_cxr_and_then_fn!(caadr_and_then);
extractor_cxr_and_then_fn!(cadar_and_then);
extractor_cxr_and_then_fn!(caddr_and_then);
extractor_cxr_and_then_fn!(cdaar_and_then);
extractor_cxr_and_then_fn!(cdadr_and_then);
extractor_cxr_and_then_fn!(cddar_and_then);
extractor_cxr_and_then_fn!(cdddr_and_then);
extractor_cxr_and_then_fn!(caaaar_and_then);
extractor_cxr_and_then_fn!(caaadr_and_then);
extractor_cxr_and_then_fn!(caadar_and_then);
extractor_cxr_and_then_fn!(caaddr_and_then);
extractor_cxr_and_then_fn!(cadaar_and_then);
extractor_cxr_and_then_fn!(cadadr_and_then);
extractor_cxr_and_then_fn!(caddar_and_then);
extractor_cxr_and_then_fn!(cadddr_and_then);
extractor_cxr_and_then_fn!(cdaaar_and_then);
extractor_cxr_and_then_fn!(cdaadr_and_then);
extractor_cxr_and_then_fn!(cdadar_and_then);
extractor_cxr_and_then_fn!(cdaddr_and_then);
extractor_cxr_and_then_fn!(cddaar_and_then);
extractor_cxr_and_then_fn!(cddadr_and_then);
extractor_cxr_and_then_fn!(cdddar_and_then);
extractor_cxr_and_then_fn!(cddddr_and_then);
}