~jojo/microcrisp

a084a37d0b2a6eee60ef78df23812285294d838c — JoJo 1 year, 2 months ago 596471a
basic evaluator
3 files changed, 293 insertions(+), 1 deletions(-)

A src/eval.rs
M src/lib.rs
M src/matching.rs
A src/eval.rs => src/eval.rs +242 -0
@@ 0,0 1,242 @@
use crate::{Lisp, Symbol};
use std::{collections::HashMap, default::Default};

pub struct Runtime<F> {
    pub builtins: HashMap<Symbol, F, TheSymbolHasher>,
}

impl<F> Runtime<F>
where
    F: FnMut(&[Lisp]) -> Lisp,
{
    pub fn eval(&mut self, locals: &mut Locals, expr: &Lisp) -> Lisp {
        match expr {
            Lisp::Int(_) | Lisp::Float(_) | Lisp::String(_) => expr.clone(),
            Lisp::Symbol(x) => match locals.get(x) {
                Some(y) => y.clone(),
                None => expr.clone(),
            },
            Lisp::List(xs) => {
                let xs = xs.iter().map(|x| self.eval(locals, x)).collect::<Vec<_>>();
                if let Some((f, args)) = xs.split_first() {
                    crate::match_expr!(f, {
                        ('fun, l(params), body) => {
                            locals.push_all(params.iter().zip(args).filter_map(|(p, a)| p.as_symbol().map(|p| (p, a.clone()))));
                            let y = self.eval(locals, body);
                            locals.pop_all(params.iter().filter_map(|p| p.as_symbol()));
                            y
                        },
                        sy(f) => {
                            if let Some(f) = self.builtins.get_mut(f) {
                                f(args)
                            } else {
                                Lisp::List(xs)
                            }
                        },
                        _ => Lisp::List(xs),
                    })
                } else {
                    Lisp::List(xs)
                }
            }
        }
    }
}

impl Default for Runtime<fn(&[Lisp]) -> Lisp> {
    fn default() -> Self {
        Runtime {
            builtins: [
                ("+", builtins::add as _),
                ("*", builtins::mul as _),
                ("-", builtins::sub as _),
                ("idiv", builtins::idiv as _),
                ("fdiv", builtins::fdiv as _),
                ("sin", builtins::sin as _),
            ]
            .into_iter()
            .map(|(s, f)| (s.try_into().unwrap(), f))
            .collect(),
        }
    }
}

pub mod builtins {
    use crate::*;

    pub fn add(xs: &[Lisp]) -> Lisp {
        xs.iter().fold(Lisp::Int(0), |acc, x| match (acc, x) {
            (Lisp::Int(acc), Lisp::Int(x)) => Lisp::Int(acc + x),
            (Lisp::Float(acc), Lisp::Int(x)) => Lisp::Float(acc + *x as f64),
            (Lisp::Int(acc), Lisp::Float(x)) => Lisp::Float(acc as f64 + x),
            (Lisp::Float(acc), Lisp::Float(x)) => Lisp::Float(acc + x),
            (acc, _) => acc,
        })
    }

    pub fn mul(xs: &[Lisp]) -> Lisp {
        xs.iter().fold(Lisp::Int(1), |acc, x| match (acc, x) {
            (Lisp::Int(acc), Lisp::Int(x)) => Lisp::Int(acc * x),
            (Lisp::Float(acc), Lisp::Int(x)) => Lisp::Float(acc * *x as f64),
            (Lisp::Int(acc), Lisp::Float(x)) => Lisp::Float(acc as f64 * x),
            (Lisp::Float(acc), Lisp::Float(x)) => Lisp::Float(acc * x),
            (acc, _) => acc,
        })
    }

    pub fn sub(xs: &[Lisp]) -> Lisp {
        match xs.split_first() {
            None => Lisp::Int(0),
            Some((Lisp::Int(x), [])) => Lisp::Int(-x),
            Some((Lisp::Float(x), [])) => Lisp::Float(-x),
            Some((init, xs)) => xs.iter().fold(init.clone(), |acc, x| match (acc, x) {
                (Lisp::Int(acc), Lisp::Int(x)) => Lisp::Int(acc - x),
                (Lisp::Float(acc), Lisp::Int(x)) => Lisp::Float(acc - *x as f64),
                (Lisp::Int(acc), Lisp::Float(x)) => Lisp::Float(acc as f64 - x),
                (Lisp::Float(acc), Lisp::Float(x)) => Lisp::Float(acc - x),
                (acc, _) => acc,
            }),
        }
    }

    pub fn idiv(xs: &[Lisp]) -> Lisp {
        match xs.split_first() {
            None => Lisp::Int(1),
            Some((_, [])) => Lisp::Int(0),
            Some((init, xs)) => xs.iter().fold(init.clone(), |acc, x| match (acc, x) {
                (Lisp::Int(acc), Lisp::Int(x)) => Lisp::Int(acc / x),
                (Lisp::Float(acc), Lisp::Int(x)) => Lisp::Int(acc as i64 / x),
                (Lisp::Int(acc), Lisp::Float(x)) => Lisp::Int(acc - *x as i64),
                (Lisp::Float(acc), Lisp::Float(x)) => Lisp::Int(acc as i64 - *x as i64),
                (acc, _) => acc,
            }),
        }
    }

    pub fn fdiv(xs: &[Lisp]) -> Lisp {
        match xs.split_first() {
            None => Lisp::Int(1),
            Some((Lisp::Int(x), [])) => Lisp::Float(1.0 / *x as f64),
            Some((Lisp::Float(x), [])) => Lisp::Float(1.0 / x),
            Some((init, xs)) => xs.iter().fold(init.clone(), |acc, x| match (acc, x) {
                (Lisp::Int(acc), Lisp::Int(x)) =>
                    if acc % x == 0 {
                        Lisp::Int(acc / x)
                    } else {
                        Lisp::Float(acc as f64 / *x as f64)
                    },
                (Lisp::Float(acc), Lisp::Int(x)) => Lisp::Float(acc / *x as f64),
                (Lisp::Int(acc), Lisp::Float(x)) => Lisp::Float(acc as f64 / x),
                (Lisp::Float(acc), Lisp::Float(x)) => Lisp::Float(acc / x),
                (acc, _) => acc,
            }),
        }
    }

    pub fn sin(xs: &[Lisp]) -> Lisp {
        match xs.first() {
            Some(Lisp::Int(x)) => Lisp::Float((*x as f64).sin()),
            Some(Lisp::Float(x)) => Lisp::Float(x.sin()),
            _ => Lisp::Int(0),
        }
    }
}

pub struct Locals(HashMap<Symbol, Vec<Lisp>, TheSymbolHasher>);

impl Locals {
    pub fn new() -> Self {
        Locals(HashMap::default())
    }

    pub fn get(&self, x: &Symbol) -> Option<&Lisp> {
        self.0.get(x).and_then(|ys| ys.last())
    }

    pub fn push_all<I, S>(&mut self, defs: I)
    where
        S: TryInto<Symbol>,
        <S as TryInto<Symbol>>::Error: std::fmt::Debug,
        I: IntoIterator<Item = (S, Lisp)>,
    {
        for (lhs, rhs) in defs {
            self.0.entry(lhs.try_into().unwrap()).or_insert(vec![]).push(rhs)
        }
    }

    pub fn pop_all<I>(&mut self, vars: I)
    where
        I: IntoIterator<Item = Symbol>,
    {
        for var in vars {
            self.0.get_mut(&var).unwrap().pop();
        }
    }
}

pub struct SymbolHasher(u128);
pub struct TheSymbolHasher;

impl std::hash::Hasher for SymbolHasher {
    fn finish(&self) -> u64 {
        (self.0 % (1 << 64)) as u64
    }

    fn write(&mut self, _bytes: &[u8]) {
        panic!("Expected `write_u128` to be called, but `write` was called instead. `SymbolHasher` should only be used for `Symbol` keys.")
    }

    fn write_u128(&mut self, i: u128) {
        self.0 = i;
    }
}

impl std::hash::BuildHasher for TheSymbolHasher {
    type Hasher = SymbolHasher;

    fn build_hasher(&self) -> Self::Hasher {
        SymbolHasher(0)
    }
}

impl Default for TheSymbolHasher {
    fn default() -> Self {
        TheSymbolHasher
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn eval_expr() {
        let mut rt = Runtime::default();

        assert_eq!(rt.eval(&mut Locals::new(), &Lisp::Int(123)), Lisp::Int(123));

        assert!(crate::match_expr!(rt.eval(&mut Locals::new(), &crate::parse(b"foo").unwrap()), {
            'foo => true,
            _ => false
        }));

        assert!({
            let mut env = Locals::new();
            env.push_all([("foo", Lisp::Int(55)), ("bar", Lisp::Int(66))]);
            crate::match_expr!(rt.eval(&mut env, &crate::parse(b"(foo bar)").unwrap()), {
                ('foo, 'bar) => false,
                (55, 66) => true,
                _ => false
            })
        });

        assert!({
            let mut env = Locals::new();
            env.push_all([("x", Lisp::Int(11)), ("y", Lisp::Int(22)), ("z", Lisp::Int(33))]);
            crate::match_expr!(rt.eval(&mut env, &crate::parse(b"(+ x y z)").unwrap()), {
                66 => true,
                _ => false
            })
        });
    }
}

M src/lib.rs => src/lib.rs +26 -1
@@ 1,5 1,9 @@
#![allow(clippy::new_without_default)]

mod eval;
mod matching;

pub use eval::*;
use std::{
    borrow::Cow,
    cmp::{max, min},


@@ 19,7 23,7 @@ pub enum Lisp {
    String(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Symbol(u128);

pub fn parse(src: &[u8]) -> Result<Lisp, String> {


@@ 213,6 217,13 @@ impl Symbol {
    }
}

impl TryFrom<&str> for Symbol {
    type Error = String;
    fn try_from(s: &str) -> Result<Symbol, String> {
        Symbol::pack_str(s).ok_or_else(|| format!("could not pack {s:?} into a symbol"))
    }
}

impl Lisp {
    pub fn as_list(&self) -> Option<&[Lisp]> {
        match self {


@@ 228,12 239,26 @@ impl Lisp {
        }
    }

    pub fn as_float(&self) -> Option<f64> {
        match *self {
            Lisp::Float(x) => Some(x),
            _ => None,
        }
    }

    pub fn as_string(&self) -> Option<&str> {
        match self {
            Lisp::String(s) => Some(s),
            _ => None,
        }
    }

    pub fn as_symbol(&self) -> Option<Symbol> {
        match *self {
            Lisp::Symbol(x) => Some(x),
            _ => None,
        }
    }
}

impl From<i64> for Lisp {

M src/matching.rs => src/matching.rs +25 -0
@@ 17,6 17,25 @@ macro_rules! match_case {
            _ => $else
        }
    };
    ($e:expr, @go {f($lhs:pat)}, $body:expr, $else:expr) => {
        match $e {
            $crate::Lisp::Float($lhs) => $body,
            _ => $else
        }
    };
    ($e:expr, @go {num($lhs:ident)}, $body:expr, $else:expr) => {
        match $e {
            $crate::Lisp::Int(ref x) => {
                let $lhs = *x as f64;
                $body
            },
            $crate::Lisp::Float(ref x) => {
                let $lhs = *x;
                $body
            },
            _ => $else
        }
    };
    ($e:expr, @go {$symbol:lifetime}, $body:expr, $else:expr) => {
        match $e {
            $crate::Lisp::Symbol(ref s) if s == &$crate::Symbol::pack(stringify!($symbol)[1..].bytes().map(|c| if c == b'_' { b'-' } else { c })).unwrap() =>


@@ 46,6 65,12 @@ macro_rules! match_case {
            _ => $else
        }
    }};
    ($e:expr, @go {l($lhs:pat)}, $body:expr, $else:expr) => {
        match $e {
            $crate::Lisp::List($lhs) => $body,
            _ => $else
        }
    };

    // ==== MUNCH! ====
    ($es:expr, @munch {} {}, $body:expr, $else:expr) => {