use crate::cell::Cell;
use crate::vm::vcell::VCell;
use crate::vm::Error;
use lazy_static::lazy_static;
use std::collections::{HashMap, HashSet};
pub fn free_symbols<'a>(
cell: &'a Cell,
env: &mut HashSet<&'a Cell>,
) -> Result<HashSet<&'a Cell>, Error> {
match cell {
Cell::Symbol(_) => match env.contains(&cell) {
true => Ok(HashSet::new()),
false => Ok(HashSet::from([cell])),
},
Cell::Pair(car, cdr) => {
let car = car.as_ref();
let cdr = cdr.as_ref();
if car.is_quote() {
return Ok(HashSet::new());
}
let mut union = HashSet::new();
if !car.is_primitive_symbol() && !env.contains(car) {
union.insert(car);
}
let mut lat = 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 lat.is_pair() {
union.extend(free_symbols(lat.car().unwrap(), env)?.iter());
lat = lat.cdr().unwrap();
}
if !lat.is_nil() {
union.extend(free_symbols(lat, env)?.iter());
}
Ok(union)
}
_ => Ok(HashSet::new()),
}
}
impl Cell {
pub fn is_quote(&self) -> bool {
self.is_symbol_str("quote")
}
pub fn is_lambda(&self) -> bool {
self.is_symbol_str("lambda")
}
pub fn is_symbol_str(&self, s: &'static str) -> bool {
match self.as_symbol() {
Some(sym) => sym == s,
_ => false,
}
}
pub fn is_primitive_symbol(&self) -> bool {
lazy_static! {
static ref PRIMITIVE_SYMBOLS: HashSet<&'static str> = HashSet::from([
"car", "cdr", "cons", "define", "eq?", "eqv?", "lambda", "quote", "+", "-", "*"
]);
}
match self {
Cell::Symbol(sym) => PRIMITIVE_SYMBOLS.contains(sym.as_str()),
_ => false,
}
}
}
#[derive(Debug)]
pub struct Environment {
bindings: HashMap<usize, usize>,
slots: Vec<VCell>,
}
impl Environment {
pub fn new() -> Environment {
Environment {
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_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 Environment {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{cell, lex, parse};
#[test]
fn same_binding_same_slot() {
let mut env = Environment::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 = Environment::new();
assert_eq!(env.get_binding(50_usize), 0);
env.put_slot(0, VCell::nil());
}
#[test]
fn primitive_symbol_check() {
assert!(cell!["+"].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"], &mut HashSet::new()),
Ok(HashSet::from([&cell!["a"]]))
);
assert_eq!(
free_symbols(&parse!["a"], &mut HashSet::from([&cell!["a"]])),
Ok(HashSet::new())
);
assert_eq!(
free_symbols(&parse!["42"], &mut HashSet::new()),
Ok(HashSet::new())
);
assert_eq!(
free_symbols(&parse!["#t"], &mut HashSet::new()),
Ok(HashSet::new())
);
assert_eq!(
free_symbols(&parse!["(quote (a b c))"], &mut HashSet::new()),
Ok(HashSet::new())
);
assert_eq!(
free_symbols(&parse!["(a b c)"], &mut HashSet::new()),
Ok(HashSet::from([&cell!["a"], &cell!["b"], &cell!["c"]]))
);
assert_eq!(
free_symbols(&parse!["(+ (* a b) (* c d) e)"], &mut HashSet::new()),
Ok(HashSet::from([
&cell!["a"],
&cell!["b"],
&cell!["c"],
&cell!["d"],
&cell!["e"]
]))
);
assert_eq!(
free_symbols(&parse!["(a b . c)"], &mut HashSet::new()),
Ok(HashSet::from([&cell!["a"], &cell!["b"], &cell!["c"]]))
);
assert_eq!(
free_symbols(&parse!["(a b . c)"], &mut HashSet::from([&cell!["c"]])),
Ok(HashSet::from([&cell!["a"], &cell!["b"]]))
);
assert_eq!(
free_symbols(&parse!["(define a b)"], &mut HashSet::new()),
Ok(HashSet::from([&cell!["b"]]))
);
assert_eq!(
free_symbols(&parse!["(lambda (x) (+ x y) z)"], &mut HashSet::new()),
Ok(HashSet::from([&cell!["y"], &cell!["z"]]))
);
assert!(free_symbols(&parse!["(lambda)"], &mut HashSet::new()).is_err());
assert!(free_symbols(&parse!["(lambda (10) (+ x y))"], &mut HashSet::new()).is_err());
assert!(free_symbols(&parse!["(lambda (a 10) (+ x y))"], &mut HashSet::new()).is_err());
}
#[test]
fn free_syms_returns_errors() {
assert!(free_symbols(&parse!["(define)"], &mut HashSet::new()).is_err());
}
}