diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 0000000..ad6d66c --- /dev/null +++ b/src/base.rs @@ -0,0 +1,117 @@ +use crate::value::*; +use crate::vm::*; +use imbl::*; +use std::cell::RefCell; +use std::fmt; +use std::rc::Rc; + +pub enum Base<'src> { + Unary( + &'src str, + Box) -> Result, LudusError>>, + ), + Binary( + &'src str, + Box, Value<'src>) -> Result, LudusError>>, + ), + Ternary( + &'src str, + Box< + dyn std::ops::Fn( + Value<'src>, + Value<'src>, + Value<'src>, + ) -> Result, LudusError>, + >, + ), +} + +impl<'src> Base<'src> { + pub fn name(&self) -> &'src str { + match self { + Base::Unary(name, _) => name, + Base::Binary(name, _) => name, + Base::Ternary(name, _) => name, + } + } +} + +impl<'src> fmt::Debug for Base<'src> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Base").field(&self.name()).finish() + } +} + +pub fn base<'src>() -> Vec<(&'src str, Value<'src>)> { + let mut base = vec![]; + + let eq = Base::Binary("eq", Box::new(|x, y| Ok(Value::Boolean(x == y)))); + base.push(("eq", Value::Base(&eq))); + + let add = Base::Binary( + "add", + Box::new(|x, y| match (x, y) { + (Value::Number(x), Value::Number(y)) => Ok(Value::Number(x + y)), + _ => Err(LudusError { + msg: "I can only add numbers".to_string(), + }), + }), + ); + base.push(("add", Value::Base(&add))); + + base +} + +// add (x, y) -> number +// and (x, y) -> value +// assoc (x, y) -> dict +// atan_2 (x) -> number +// bool (x) -> bool +// ceil (x) -> number +// chars (x) -> list +// concat (x, y) -> value +// conj (x, y) -> list +// cos (x) -> number +// dec (x) -> number +// disj (x) -> set +// dissoc (x, y) -> dict +// div (x, y) -> number +// doc (x) -> string +// downcase (x) -> string +// first (x) -> value +// floor (x) -> number +// get (x, y) -> value +// gt (x, y) -> bool +// gte! (x, y) -> bool +// inc (x) -> number +// last (x) -> value +// lt (x) -> bool +// lte (x) -> bool +// mod (x, y) -> number +// or (x, y) -> value +// pi +// print! (x) -> :ok +// prn (x) -> value +// push (x) -> list +// random () -> number +// range () -> list +// rest (x) -> coll +// round (x) -> number +// show (x) -> string +// sin (x) -> number +// slice (x, y, z) -> list +// split (x, y) -> list(string) +// sqrt (x) -> number +// store! (x, y) -> value +// str_slice (x, y, z) -> string +// stringify (x) -> string +// sub (x, y) -> number +// tan (x) -> number +// to_list (x) -> list +// to_number (x) -> number +// trim (x) -> string +// triml (x) -> string +// trimr (x) -> string +// type (x) -> keyword +// unbox (x) -> value +// upcase (x) -> string diff --git a/src/main.rs b/src/main.rs index 7f96eaa..0475319 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,16 +16,23 @@ // * [ ] wire up Ariadne parsing errors // * [ ] validation // * [x] break this out into multiple files -// * [ ] write a tree-walk VM -// - [ ] learn how to deal with lifetimes -// - [ ] with stack mechanics and refcounting -// - [ ] with tail-call optimization +// * [x] write a tree-walk VM +// - [x] learn how to deal with lifetimes +// - [x] with stack mechanics and refcounting +// - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM) +// - [ ] with all the necessary forms for current Ludus +// * [ ] guards in match clauses +// * [ ] `as` patterns +// * [ ] splat patterns in tuples, lists, dicts +// * [ ] splats in list and dict literals +// * [ ] `loop` and `recur` // * [ ] write `base` in Rust // * [ ] turn this into a library function // * [ ] compile this into WASM // * [ ] perf testing use chumsky::{input::Stream, prelude::*}; +use std::rc::Rc; mod spans; @@ -33,6 +40,7 @@ mod lexer; use crate::lexer::*; mod value; +use crate::value::*; mod parser; use crate::parser::*; @@ -40,10 +48,12 @@ use crate::parser::*; mod vm; use crate::vm::*; +mod base; +use crate::base::*; + pub fn main() { let src = " -#{:a 1, :b 2} - +eq "; let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if lex_errs.len() > 0 { @@ -60,7 +70,7 @@ pub fn main() { .unwrap(); // println!("{}", ast); - let mut ctx = vec![]; + let mut ctx = base(); let result = eval(&ast, &mut ctx).unwrap(); diff --git a/src/parser.rs b/src/parser.rs index 611dddb..5f66052 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -172,7 +172,17 @@ impl<'src> fmt::Display for Ast<'src> { } Ast::FnDeclaration(_name) => todo!(), Ast::Panic(_expr) => todo!(), - Ast::Do(_exprs) => todo!(), + Ast::Do(terms) => { + write!( + f, + "do: {}", + terms + .iter() + .map(|(term, _)| term.to_string()) + .collect::>() + .join(" > ") + ) + } Ast::Repeat(_times, _body) => todo!(), // Ast::Loop(init, body) => todo!(), // Ast::Recur(args) => todo!(), diff --git a/src/value.rs b/src/value.rs index cbb7282..24981c4 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,16 +1,17 @@ +use crate::base::*; use crate::parser::*; use imbl::*; use std::cell::RefCell; use std::fmt; use std::rc::Rc; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Fn<'src> { pub name: &'src str, pub body: &'src Vec>, } -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub enum Value<'src> { Nil, Placeholder, @@ -27,6 +28,7 @@ pub enum Value<'src> { Dict(HashMap<&'src str, Self>), Box(&'src str, Rc>), Fn(Rc>), + Base(&Base<'src>), // Set(HashSet), // Sets are hard // Sets require Eq @@ -54,6 +56,7 @@ impl<'src> Clone for Value<'src> { Value::Dict(d) => Value::Dict(d.clone()), Value::Box(name, b) => Value::Box(name, b.clone()), Value::Placeholder => Value::Placeholder, + Value::Base(b) => Value::Base(b.clone()), } } } @@ -91,8 +94,23 @@ impl<'src> fmt::Display for Value<'src> { .collect::>() .join(", ") ), - Value::Box(name, b) => todo!(), + Value::Box(name, value) => { + write!( + f, + "box {}: [{}]", + name, + &value.try_borrow().unwrap().to_string() + ) + } Value::Placeholder => write!(f, "_"), + Value::Base(base) => { + let name = match base { + Base::Unary(name, _) => name, + Base::Binary(name, _) => name, + Base::Ternary(name, _) => name, + }; + write!(f, "base fn {}", name) + } } } } @@ -104,4 +122,46 @@ impl<'src> Value<'src> { _ => true, } } + + pub fn ludus_type(&self) -> Value { + match self { + Value::Nil => Value::Keyword("nil"), + Value::Number(_) => Value::Keyword("number"), + Value::Boolean(_) => Value::Keyword("boolean"), + Value::Keyword(_) => Value::Keyword("keyword"), + Value::Tuple(_) => Value::Keyword("tuple"), + Value::String(_) => Value::Keyword("string"), + Value::List(_) => Value::Keyword("list"), + Value::Dict(_) => Value::Keyword("dict"), + Value::Fn(_) => Value::Keyword("fn"), + Value::Box(_, _) => Value::Keyword("box"), + Value::Placeholder => unreachable!(), + Value::Args(_) => unreachable!(), + Value::Base(_) => Value::Keyword("fn"), + } + } } + +impl<'src> PartialEq for Value<'src> { + fn eq(&self, other: &Value<'src>) -> bool { + match (self, other) { + // value equality types + (Value::Nil, Value::Nil) => true, + (Value::Boolean(x), Value::Boolean(y)) => x == y, + (Value::Number(x), Value::Number(y)) => x == y, + (Value::String(x), Value::String(y)) => x == y, + (Value::Keyword(x), Value::Keyword(y)) => x == y, + (Value::Tuple(x), Value::Tuple(y)) => x == y, + (Value::List(x), Value::List(y)) => x == y, + (Value::Dict(x), Value::Dict(y)) => x == y, + // reference equality types + (Value::Fn(x), Value::Fn(y)) => Rc::>::as_ptr(x) == Rc::>::as_ptr(y), + (Value::Box(_, x), Value::Box(_, y)) => { + Rc::>>::as_ptr(x) == Rc::>>::as_ptr(y) + } + _ => false, + } + } +} + +impl<'src> Eq for Value<'src> {} diff --git a/src/vm.rs b/src/vm.rs index 7f8c1b8..328730d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,3 +1,4 @@ +use crate::base::*; use crate::parser::*; use crate::value::*; use imbl::HashMap; @@ -7,7 +8,7 @@ use std::rc::Rc; #[derive(Clone, Debug)] pub struct LudusError { - msg: String, + pub msg: String, } // oy @@ -66,6 +67,10 @@ pub fn match_pattern<'src, 'a>( ctx.push((w, val.clone())); Some(ctx) } + (Pattern::As(word, type_kw), value) => { + let ludus_type = value.ludus_type(); + None + } // todo: add splats to these match clauses (Pattern::Tuple(x), Value::Tuple(y)) => { if x.len() != y.len() { @@ -97,7 +102,25 @@ pub fn match_pattern<'src, 'a>( } Some(ctx) } - (Pattern::Dict(_), _) => todo!("dictionary patterns still to do"), + (Pattern::Dict(x), Value::Dict(y)) => { + if x.len() != y.len() { + return None; + }; + let to = ctx.len(); + for (PairPattern { key, patt }, _) in x { + if let Some(val) = y.get(key) { + if let None = match_pattern(&patt.0, val, ctx) { + while ctx.len() > to { + ctx.pop(); + return None; + } + } + } else { + return None; + }; + } + Some(ctx) + } _ => None, } } @@ -135,7 +158,13 @@ pub fn apply<'src, 'a>( Ok(Value::Nil) } } - (Value::Dict(_dict), Value::Keyword(_kw)) => todo!(), + (Value::Dict(dict), Value::Keyword(kw)) => { + if let Some(val) = dict.get(kw) { + Ok(val.clone()) + } else { + Ok(Value::Nil) + } + } (Value::Fn(f), Value::Tuple(args)) => { let args = Value::Tuple(args); match_clauses(&args, f.body, ctx) @@ -276,7 +305,7 @@ pub fn eval<'src, 'a>( ctx.push((name, the_fn.clone())); Ok(the_fn) } - Ast::FnDeclaration(_) => todo!(), + Ast::FnDeclaration(_name) => todo!(), Ast::Panic(msg) => { let msg = eval(&msg.0, ctx)?; Err(LudusError { @@ -300,8 +329,15 @@ pub fn eval<'src, 'a>( } Ok(Value::Nil) } - Ast::Do(_) => todo!(), - // Ast::Loop(_, _) => todo!(), - // Ast::Recur(_) => todo!(), + Ast::Do(terms) => { + let mut result = eval(&terms[0].0, ctx)?; + for i in 1..terms.len() { + let next = eval(&terms[i].0, ctx)?; + let arg = Value::Tuple(Rc::new(vec![result])); + result = apply(next, arg, ctx)?; + } + Ok(result) + } // Ast::Loop(_, _) => todo!(), + // Ast::Recur(_) => todo!(), } }