mod add_function;
mod rest;
pub use rest::Rest;
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
};
use crate::{
TulispObject, TulispValue, builtin,
context::add_function::TulispCallable,
error::Error,
eval::{DummyEval, eval, eval_and_then, eval_basic, funcall},
list,
object::wrappers::generic::{Shared, TulispFn},
parse::parse,
};
#[derive(Debug, Default, Clone)]
pub(crate) struct Scope {
pub scope: Vec<TulispObject>,
}
impl Scope {
pub fn set(&mut self, symbol: TulispObject, value: TulispObject) -> Result<(), Error> {
symbol.set_scope(value)?;
self.scope.push(symbol);
Ok(())
}
pub fn remove_all(&self) -> Result<(), Error> {
for item in &self.scope {
item.unset()?;
}
Ok(())
}
}
pub struct TulispContext {
obarray: HashMap<String, TulispObject>,
pub(crate) filenames: Vec<String>,
pub(crate) load_path: Option<PathBuf>,
}
impl Default for TulispContext {
fn default() -> Self {
Self::new()
}
}
impl TulispContext {
pub fn new() -> Self {
let mut ctx = Self {
obarray: HashMap::new(),
filenames: vec!["<eval_string>".to_string()],
load_path: None,
};
builtin::functions::add(&mut ctx);
builtin::macros::add(&mut ctx);
ctx
}
pub fn intern(&mut self, name: &str) -> TulispObject {
if let Some(sym) = self.obarray.get(name) {
sym.clone()
} else {
let name = name.to_string();
let constant = name.starts_with(':');
let sym = TulispObject::symbol(name.clone(), constant);
self.obarray.insert(name, sym.clone());
sym
}
}
pub(crate) fn intern_soft(&mut self, name: &str) -> Option<TulispObject> {
self.obarray.get(name).cloned()
}
#[inline(always)]
pub fn add_special_form(&mut self, name: &str, func: impl TulispFn + std::any::Any) {
self.intern(name)
.set_global(TulispValue::Func(Shared::new_tulisp_fn(func)).into_ref(None))
.unwrap();
}
#[inline(always)]
pub fn add_function<
Args: 'static,
Output: 'static,
const NEEDS_CONTEXT: bool,
const NUM_ARGS: usize,
const NUM_OPTIONAL: usize,
const HAS_REST: bool,
const HAS_RETURN: bool,
const FALLIBLE: bool,
>(
&mut self,
name: &str,
func: impl TulispCallable<
Args,
Output,
NEEDS_CONTEXT,
NUM_ARGS,
NUM_OPTIONAL,
HAS_REST,
HAS_RETURN,
FALLIBLE,
> + 'static,
) -> &mut Self {
func.add_to_context(self, name);
self
}
#[inline(always)]
pub fn add_macro(&mut self, name: &str, func: impl TulispFn) {
self.intern(name)
.set_global(TulispValue::Macro(Shared::new_tulisp_fn(func)).into_ref(None))
.unwrap();
}
pub fn set_load_path<P: AsRef<Path>>(&mut self, path: Option<P>) -> Result<(), Error> {
self.load_path = match path {
Some(path) => Some(
std::fs::canonicalize(path)
.map_err(|e| Error::os_error(format!("Unable to set load path: {e}")))?,
),
None => None,
};
Ok(())
}
#[inline(always)]
pub fn eval(&mut self, value: &TulispObject) -> Result<TulispObject, Error> {
eval(self, value)
}
#[inline(always)]
pub fn eval_and_then<T>(
&mut self,
expr: &TulispObject,
f: impl FnOnce(&mut TulispContext, &TulispObject) -> Result<T, Error>,
) -> Result<T, Error> {
eval_and_then(self, expr, f)
}
pub fn funcall(
&mut self,
func: &TulispObject,
args: &TulispObject,
) -> Result<TulispObject, Error> {
let func = self.eval(func)?;
funcall::<DummyEval>(self, &func, args)
}
pub fn map(&mut self, func: &TulispObject, seq: &TulispObject) -> Result<TulispObject, Error> {
let func = self.eval(func)?;
let ret = TulispObject::nil();
for item in seq.base_iter() {
ret.push(funcall::<DummyEval>(self, &func, &list!(item)?)?)?;
}
Ok(ret)
}
pub fn filter(
&mut self,
func: &TulispObject,
seq: &TulispObject,
) -> Result<TulispObject, Error> {
let func = self.eval(func)?;
let ret = TulispObject::nil();
for item in seq.base_iter() {
if funcall::<DummyEval>(self, &func, &list!(item.clone())?)?.is_truthy() {
ret.push(item)?;
}
}
Ok(ret)
}
pub fn reduce(
&mut self,
func: &TulispObject,
seq: &TulispObject,
initial_value: &TulispObject,
) -> Result<TulispObject, Error> {
let func = self.eval(func)?;
let mut ret = initial_value.clone();
for item in seq.base_iter() {
ret = funcall::<DummyEval>(self, &func, &list!(ret, item)?)?;
}
Ok(ret)
}
pub fn eval_string(&mut self, string: &str) -> Result<TulispObject, Error> {
let vv = parse(self, 0, string)?;
self.eval_progn(&vv)
}
#[inline(always)]
pub fn eval_progn(&mut self, seq: &TulispObject) -> Result<TulispObject, Error> {
let mut ret = None;
for val in seq.base_iter() {
match eval_basic(self, &val)? {
std::borrow::Cow::Borrowed(_) => {
ret = Some(val);
}
std::borrow::Cow::Owned(o) => {
ret = Some(o);
}
};
}
Ok(ret.unwrap_or_else(TulispObject::nil))
}
#[inline(always)]
pub fn eval_each(&mut self, seq: &TulispObject) -> Result<TulispObject, Error> {
let ret = TulispObject::nil();
for val in seq.base_iter() {
ret.push(eval(self, &val)?)?;
}
Ok(ret)
}
pub fn eval_file(&mut self, filename: &str) -> Result<TulispObject, Error> {
let contents = fs::read_to_string(filename).map_err(|e| {
Error::undefined(format!("Unable to read file: {filename}. Error: {e}"))
})?;
self.filenames.push(filename.to_owned());
let string: &str = &contents;
let vv = parse(self, self.filenames.len() - 1, string)?;
self.eval_progn(&vv)
}
pub(crate) fn get_filename(&self, file_id: usize) -> String {
self.filenames[file_id].clone()
}
}