use crate::cell::Cell;
use crate::vm::lambda::Lambda;
use crate::vm::vcell::VCell;
use crate::vm::Error;
use crate::vm::Error::InvalidDefineSyntax;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum BindingSource {
Global,
Argument(usize),
IofArgument(usize),
IofEnvironment(usize),
InternalDefinition,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum BindingLocation {
Argument(usize),
Global,
Environment(usize),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct EnvironmentMap {
map: Vec<(VCell, BindingSource)>,
}
impl EnvironmentMap {
pub fn new() -> EnvironmentMap {
EnvironmentMap { map: vec![] }
}
pub fn new_from_iof(
args: &[VCell],
internally_defined: &[VCell],
iof: &Lambda,
free_symbols: &[VCell],
) -> EnvironmentMap {
let mut map = args
.iter()
.enumerate()
.map(|it| (it.1.clone(), BindingSource::Argument(it.0)))
.collect::<Vec<(VCell, BindingSource)>>();
internally_defined
.iter()
.map(|it| (it.clone(), BindingSource::InternalDefinition))
.for_each(|binding| map.push(binding));
free_symbols
.iter()
.filter_map(|sym| {
if let Some(slot) = iof.envmap.get_slot(sym) {
Some((sym.clone(), BindingSource::IofEnvironment(slot)))
} else if let Some((n, _)) = iof.args.iter().enumerate().find(|(_, it)| *it == sym)
{
Some((sym.clone(), BindingSource::IofArgument(n)))
} else {
None
}
})
.for_each(|binding| map.push(binding));
EnvironmentMap { map }
}
pub fn get_slot(&self, sym: &VCell) -> Option<usize> {
self.map
.iter()
.enumerate()
.find(|it| it.1 .0 == *sym)
.map(|it| it.0)
}
pub fn slots_len(&self) -> usize {
self.map.len()
}
pub fn get_map(&self) -> &[(VCell, BindingSource)] {
&self.map
}
}
impl Default for EnvironmentMap {
fn default() -> Self {
EnvironmentMap::new()
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct LexicalEnvironment {
slots: RefCell<Vec<VCell>>,
}
impl LexicalEnvironment {
pub fn new(size: usize) -> LexicalEnvironment {
LexicalEnvironment {
slots: RefCell::new(vec![VCell::undefined(); size]),
}
}
pub fn slot_len(&self) -> usize {
self.slots.borrow().len()
}
pub fn get(&self, index: usize) -> VCell {
self.slots
.borrow()
.get(index)
.expect("slot index out of bounds")
.clone()
}
pub fn put(&self, index: usize, vcell: VCell) {
*self
.slots
.borrow_mut()
.get_mut(index)
.expect("slot index out of bounds") = vcell;
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct GlobalEnvironment {
bindings: HashMap<usize, usize>,
slots: Vec<VCell>,
}
impl GlobalEnvironment {
pub fn new() -> GlobalEnvironment {
GlobalEnvironment {
bindings: HashMap::new(),
slots: vec![],
}
}
pub fn iter_bindings(&self) -> std::collections::hash_map::Keys<usize, usize> {
self.bindings.keys()
}
pub fn iter_slots(&self) -> std::slice::Iter<VCell> {
self.slots.iter()
}
pub fn get_binding<T: Into<usize>>(&mut self, sym: T) -> usize {
let sym: usize = sym.into();
match self.bindings.get(&sym) {
Some(slot) => *slot,
None => {
self.slots.push(VCell::undefined());
let slot = self.slots.len() - 1;
self.bindings.insert(sym, slot);
slot
}
}
}
pub fn get<T: Into<usize>>(&mut self, sym: T) -> Option<VCell> {
let sym: usize = sym.into();
self.bindings.get(&sym).map(|slot| self.get_slot(*slot))
}
pub fn get_symbol<T: Into<usize>>(&self, slot: T) -> Option<usize> {
let slot = slot.into();
self.bindings
.iter()
.find(|it| *(it.1) == slot)
.map(|it| *it.0)
}
pub fn get_slot(&self, slot: usize) -> VCell {
self.slots
.get(slot)
.expect("invalid environment slot")
.clone()
}
pub fn put_slot(&mut self, slot: usize, vcell: VCell) {
assert!(
matches!(vcell, VCell::Ptr(_) | VCell::Undefined),
"unexpected put_slot() of {:?}",
vcell
);
*self.slots.get_mut(slot).expect("invalid environment slot") = vcell;
}
}
impl Default for GlobalEnvironment {
fn default() -> Self {
Self::new()
}
}
pub fn free_symbols(cell: &Cell) -> Result<HashSet<&Cell>, Error> {
let mut env = HashSet::new();
let mut free_set = HashSet::new();
find_free_symbols(cell, &mut env, &mut free_set)?;
Ok(free_set)
}
fn find_free_symbols<'a>(
cell: &'a Cell,
env: &mut HashSet<&'a Cell>,
free: &mut HashSet<&'a Cell>,
) -> Result<(), Error> {
match cell {
Cell::Symbol(_) => match env.contains(&cell) {
true => Ok(()),
false => {
free.insert(cell);
Ok(())
}
},
Cell::Pair(car, cdr) => {
let car = car.as_ref();
let cdr = cdr.as_ref();
find_free_symbols_in_proc((car, cdr), env, free)?;
Ok(())
}
_ => Ok(()),
}
}
fn find_free_symbols_in_proc<'a>(
(car, cdr): (&'a Cell, &'a Cell),
env: &mut HashSet<&'a Cell>,
free: &mut HashSet<&'a Cell>,
) -> Result<(), Error> {
if car.is_quote() {
return Ok(());
}
if car.is_symbol() && !car.is_primitive_symbol() && !env.contains(car) {
free.insert(car);
}
if car.is_pair() {
find_free_symbols(car, env, free)?;
}
let mut rest = match car {
Cell::Symbol(sym) => match sym.as_str() {
"define" => cdr
.cdr()
.ok_or_else(|| Error::InvalidNumArgs("define".into()))?,
"lambda" => {
let mut args = cdr
.car()
.ok_or_else(|| Error::InvalidNumArgs("lambda".into()))?;
while args.is_pair() {
let sym = args.car().unwrap();
if !sym.is_symbol() {
return Err(Error::InvalidArgs(
"lambda".into(),
"argument".into(),
sym.to_string(),
));
}
env.insert(sym);
args = args.cdr().unwrap();
}
cdr.cdr()
.ok_or_else(|| Error::InvalidNumArgs("lambda".into()))?
}
_ => cdr,
},
_ => cdr,
};
while rest.is_pair() {
find_free_symbols(rest.car().unwrap(), env, free)?;
rest = rest.cdr().unwrap();
}
if !rest.is_nil() {
find_free_symbols(rest, env, free)?;
}
Ok(())
}
pub fn internally_defined_symbols(body: &Cell) -> Result<HashSet<&Cell>, Error> {
let mut symbols = HashSet::new();
let mut beginning_of_body = true;
for expr in body {
if expr.is_pair() && expr.car().unwrap().is_define() {
if !beginning_of_body {
return Err(InvalidDefineSyntax(format!("out of context: {}", expr)));
}
let expr = expr.cdr().unwrap();
if expr.is_pair() {
let expr = expr.car().unwrap();
if expr.is_symbol() {
symbols.insert(expr);
} else if expr.is_pair() && expr.car().unwrap().is_symbol() {
symbols.insert(expr.car().unwrap());
}
}
continue;
} else {
beginning_of_body = false;
}
}
Ok(symbols)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{cell, lex, parse};
#[test]
fn same_binding_same_slot() {
let mut env = GlobalEnvironment::new();
assert_eq!(env.get_binding(50_usize), 0);
assert_eq!(env.get_binding(100_usize), 1);
assert_eq!(env.get_binding(50_usize), 0);
assert_eq!(env.get_symbol(0_usize), Some(50_usize));
assert_eq!(env.get_symbol(1_usize), Some(100_usize));
assert_eq!(env.get_slot(0), VCell::undefined());
env.put_slot(0, VCell::ptr(42));
assert_eq!(env.get_slot(0), VCell::ptr(42));
env.put_slot(0, VCell::undefined());
}
#[test]
#[should_panic]
fn put_slot_panics_if_non_ptr() {
let mut env = GlobalEnvironment::new();
assert_eq!(env.get_binding(50_usize), 0);
env.put_slot(0, VCell::nil());
}
#[test]
fn primitive_symbol_check() {
assert!(cell!["lambda"].is_primitive_symbol());
assert!(!cell!["foo"].is_primitive_symbol());
assert!(!cell![100].is_primitive_symbol());
}
#[test]
fn free_syms() {
assert_eq!(free_symbols(&parse!["a"]), Ok(HashSet::from([&cell!["a"]])));
assert_eq!(free_symbols(&parse!["42"]), Ok(HashSet::new()));
assert_eq!(free_symbols(&parse!["#t"]), Ok(HashSet::new()));
assert_eq!(free_symbols(&parse!["(quote (a b c))"]), Ok(HashSet::new()));
assert_eq!(
free_symbols(&parse!["(a b c)"]),
Ok(HashSet::from([&cell!["a"], &cell!["b"], &cell!["c"]]))
);
assert_eq!(
free_symbols(&parse!["(+ (* a b) (* c d) e)"]),
Ok(HashSet::from([
&cell!["+"],
&cell!["*"],
&cell!["a"],
&cell!["b"],
&cell!["c"],
&cell!["d"],
&cell!["e"]
]))
);
assert_eq!(
free_symbols(&parse!["(a b . c)"]),
Ok(HashSet::from([&cell!["a"], &cell!["b"], &cell!["c"]]))
);
assert_eq!(
free_symbols(&parse!["(define a b)"]),
Ok(HashSet::from([&cell!["b"]]))
);
assert_eq!(
free_symbols(&parse!["(lambda (x) (+ x y) z)"]),
Ok(HashSet::from([&cell!["y"], &cell!["z"], &cell!["+"]]))
);
assert_eq!(
free_symbols(&parse!("(lambda (n) (+ ((adder num) n)))")),
Ok(HashSet::from([&cell!["adder"], &cell!["num"], &cell!["+"]]))
);
assert!(free_symbols(&parse!["(lambda)"]).is_err());
assert!(free_symbols(&parse!["(lambda (10) (+ x y))"]).is_err());
assert!(free_symbols(&parse!["(lambda (a 10) (+ x y))"]).is_err());
}
#[test]
fn free_syms_returns_errors() {
assert!(free_symbols(&parse!["(define)"]).is_err());
}
#[test]
fn internally_defined_symbols_returns_vec() {
assert_eq!(
internally_defined_symbols(&parse!["((define foo 10)(define (bar baz) 10))"]),
Ok(HashSet::from([&cell!["foo"], &cell!["bar"]]))
);
assert!(internally_defined_symbols(&parse![
"((define foo 10)(set! foo 5)(define (bar baz) 10))"
])
.is_err());
}
}