use std::{
collections::BTreeMap,
sync::{Arc, Mutex, MutexGuard},
};
use sim_kernel::{
Args, Callable, ClassRef, Cx, Error, Object, ObjectCompat, Result, Symbol, Value,
};
type BindingSlot = Arc<Mutex<Option<Value>>>;
pub type BindingInitializer =
Box<dyn Fn(&mut Cx, &LexicalEnv) -> Result<Value> + Send + Sync + 'static>;
type LexicalBody =
Arc<dyn Fn(&mut Cx, &LexicalEnv, Vec<Value>) -> Result<Value> + Send + Sync + 'static>;
#[derive(Clone, Debug)]
pub struct LexicalEnv {
frame: Arc<LexicalFrame>,
}
#[derive(Debug)]
struct LexicalFrame {
parent: Option<LexicalEnv>,
slots: Mutex<BTreeMap<Symbol, BindingSlot>>,
}
impl Default for LexicalEnv {
fn default() -> Self {
Self::new()
}
}
impl LexicalEnv {
pub fn new() -> Self {
Self {
frame: Arc::new(LexicalFrame {
parent: None,
slots: Mutex::new(BTreeMap::new()),
}),
}
}
pub fn child(&self) -> Self {
Self {
frame: Arc::new(LexicalFrame {
parent: Some(self.clone()),
slots: Mutex::new(BTreeMap::new()),
}),
}
}
pub fn define(&self, name: Symbol, value: Value) -> Result<()> {
self.define_slot(name, Some(value))
}
pub fn lookup(&self, name: &Symbol) -> Result<Value> {
let Some(slot) = self.lookup_slot(name)? else {
return Err(Error::Eval(format!(
"lexical binding {name} is not defined"
)));
};
slot.lock()
.map_err(|_| Error::Eval(format!("lexical binding {name} lock is poisoned")))?
.clone()
.ok_or_else(|| Error::Eval(format!("lexical binding {name} is not initialized")))
}
fn predefine(&self, name: Symbol) -> Result<()> {
self.define_slot(name, None)
}
fn set(&self, name: &Symbol, value: Value) -> Result<()> {
let Some(slot) = self.lookup_slot(name)? else {
return Err(Error::Eval(format!(
"lexical binding {name} is not defined"
)));
};
*slot
.lock()
.map_err(|_| Error::Eval(format!("lexical binding {name} lock is poisoned")))? =
Some(value);
Ok(())
}
fn define_slot(&self, name: Symbol, value: Option<Value>) -> Result<()> {
let mut slots = self.slots()?;
if slots.contains_key(&name) {
return Err(Error::Eval(format!(
"lexical binding {name} is already defined in this frame"
)));
}
slots.insert(name, Arc::new(Mutex::new(value)));
Ok(())
}
fn lookup_slot(&self, name: &Symbol) -> Result<Option<BindingSlot>> {
if let Some(slot) = self.slots()?.get(name).cloned() {
return Ok(Some(slot));
}
match &self.frame.parent {
Some(parent) => parent.lookup_slot(name),
None => Ok(None),
}
}
fn slots(&self) -> Result<MutexGuard<'_, BTreeMap<Symbol, BindingSlot>>> {
self.frame
.slots
.lock()
.map_err(|_| Error::Eval("lexical binding frame lock is poisoned".to_owned()))
}
}
pub fn eval_let(
cx: &mut Cx,
outer: &LexicalEnv,
bindings: Vec<(Symbol, Value)>,
body: impl FnOnce(&mut Cx, &LexicalEnv) -> Result<Value>,
) -> Result<Value> {
let env = outer.child();
for (name, value) in bindings {
env.define(name, value)?;
}
body(cx, &env)
}
pub fn eval_let_star(
cx: &mut Cx,
outer: &LexicalEnv,
bindings: Vec<(Symbol, BindingInitializer)>,
body: impl FnOnce(&mut Cx, &LexicalEnv) -> Result<Value>,
) -> Result<Value> {
let env = outer.child();
for (name, initializer) in bindings {
let value = initializer(cx, &env)?;
env.define(name, value)?;
}
body(cx, &env)
}
pub fn eval_letrec(
cx: &mut Cx,
outer: &LexicalEnv,
bindings: Vec<(Symbol, BindingInitializer)>,
body: impl FnOnce(&mut Cx, &LexicalEnv) -> Result<Value>,
) -> Result<Value> {
let env = outer.child();
let names = bindings
.iter()
.map(|(name, _)| name.clone())
.collect::<Vec<_>>();
for name in &names {
env.predefine(name.clone())?;
}
for ((_, initializer), name) in bindings.into_iter().zip(names.iter()) {
let value = initializer(cx, &env)?;
env.set(name, value)?;
}
body(cx, &env)
}
#[derive(Clone)]
pub struct LexicalFunction {
name: Symbol,
env: LexicalEnv,
body: LexicalBody,
}
impl LexicalFunction {
pub fn new(name: Symbol, env: LexicalEnv, body: LexicalBody) -> Self {
Self { name, env, body }
}
pub fn name(&self) -> &Symbol {
&self.name
}
}
impl Object for LexicalFunction {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("#<binding-function {}>", self.name))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ObjectCompat for LexicalFunction {
fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
cx.resolve_class(&Symbol::qualified("core", "Function"))
}
fn as_callable(&self) -> Option<&dyn Callable> {
Some(self)
}
}
impl Callable for LexicalFunction {
fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
(self.body)(cx, &self.env, args.into_vec())
}
}
pub fn lexical_function_value(
cx: &mut Cx,
name: Symbol,
env: LexicalEnv,
body: LexicalBody,
) -> Result<Value> {
cx.factory()
.opaque(Arc::new(LexicalFunction::new(name, env, body)))
}