use crate::value::*; use imbl::*; use ran::ran_f64; use std::rc::Rc; #[derive(Clone, Debug)] pub enum Base<'src> { Nullary(fn() -> Value<'src>), Unary(fn(&Value<'src>) -> Value<'src>), Binary(fn(&Value<'src>, &Value<'src>) -> Value<'src>), Ternary(fn(&Value<'src>, &Value<'src>, &Value<'src>) -> Value<'src>), } pub fn eq<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { Value::Boolean(x == y) } pub fn add<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x + y), _ => unreachable!("internal Ludus error"), } } pub fn sub<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x - y), _ => unreachable!("internal Ludus error"), } } pub fn unbox<'src>(x: &Value<'src>) -> Value<'src> { match x { Value::Box(_, cell) => cell.borrow().clone(), _ => unreachable!("internal Ludus error"), } } pub fn store<'src>(b: &Value<'src>, val: &Value<'src>) -> Value<'src> { if let Value::Box(_, cell) = b { cell.replace(val.clone()); val.clone() } else { unreachable!("internal Ludus error") } } // TODO: do better than returning just the docstr // name, patterns, AND docstring pub fn doc<'src>(f: &Value<'src>) -> Value<'src> { match f { Value::Fn(f) => { let name = &f.name; let doc = &f.doc; if let Some(docstr) = doc { Value::AllocatedString(Rc::new(format!("{name}: {docstr}"))) } else { Value::AllocatedString(Rc::new(format!("{name}: no documentation found"))) } } _ => Value::InternedString("no documentation found"), } } pub fn and<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { Value::Boolean(x.bool() && y.bool()) } pub fn assoc<'src>(dict: &Value<'src>, key: &Value<'src>, value: &Value<'src>) -> Value<'src> { match (dict, key) { (Value::Dict(d), Value::Keyword(k)) => Value::Dict(d.update(k, value.clone())), _ => unreachable!("internal Ludus error"), } } pub fn r#bool<'src>(x: &Value<'src>) -> Value<'src> { Value::Boolean(x.bool()) } pub fn chars<'src>(x: &Value<'src>) -> Value<'src> { match x { Value::InternedString(s) => { let chars = s.chars(); let mut charlist = vector![]; for char in chars { if char.is_ascii() { charlist.push_back(Value::AllocatedString(Rc::new(char.to_string()))) } else { return Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::AllocatedString(Rc::new(format!( "{char} is not an ascii character" ))), ])); } } Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::List(charlist)])) } Value::AllocatedString(s) => { let chars = s.chars(); let mut charlist = vector![]; for char in chars { if char.is_ascii() { charlist.push_back(Value::AllocatedString(Rc::new(char.to_string()))) } else { return Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::AllocatedString(Rc::new(format!( "{char} is not an ascii character" ))), ])); } } Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::List(charlist)])) } _ => unreachable!("internal Ludus error"), } } // TODO: figure out how to get to opportunistic mutation here pub fn concat<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { match (x, y) { (Value::InternedString(x), Value::InternedString(y)) => { Value::AllocatedString(Rc::new(format!("{x}{y}"))) } (Value::AllocatedString(x), Value::AllocatedString(y)) => { Value::AllocatedString(Rc::new(format!("{x}{y}"))) } (Value::AllocatedString(x), Value::InternedString(y)) => { Value::AllocatedString(Rc::new(format!("{x}{y}"))) } (Value::InternedString(x), Value::AllocatedString(y)) => { Value::AllocatedString(Rc::new(format!("{x}{y}"))) } (Value::List(x), Value::List(y)) => { let mut newlist = x.clone(); newlist.append(y.clone()); Value::List(newlist) } _ => unreachable!("internal Ludus error"), } } pub fn append<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { match x { Value::List(list) => { let mut newlist = list.clone(); newlist.push_back(y.clone()); Value::List(newlist) } _ => unreachable!("internal Ludus error"), } } pub fn dec<'src>(x: &Value<'src>) -> Value<'src> { match x { Value::Number(n) => Value::Number(n - 1.0), _ => unreachable!("internal Ludus error"), } } pub fn inc<'src>(x: &Value<'src>) -> Value<'src> { match x { Value::Number(n) => Value::Number(n + 1.0), _ => unreachable!("internal Ludus error"), } } pub fn div<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x / y), _ => unreachable!("internal Ludus error"), } } pub fn mult<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x * y), _ => unreachable!("internal Ludus error"), } } pub fn dissoc<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> { match (dict, key) { (Value::Dict(dict), Value::Keyword(key)) => { let mut new = dict.clone(); new.remove(key); Value::Dict(new) } _ => unreachable!("internal Ludus error"), } } pub fn first<'src>(ordered: &Value<'src>) -> Value<'src> { match ordered { Value::List(list) => match list.front() { Some(n) => n.clone(), None => Value::Nil, }, Value::Tuple(tuple) => match tuple.first() { Some(n) => n.clone(), None => Value::Nil, }, _ => unreachable!("internal Ludus error"), } } // TODO: figure out how to handle negative numbers // the cast from f64 to usize discards sign info pub fn at<'src>(ordered: &Value<'src>, i: &Value<'src>) -> Value<'src> { match (ordered, i) { (Value::List(list), Value::Number(n)) => { let i = *n as usize; match list.get(i) { Some(n) => n.clone(), None => Value::Nil, } } (Value::Tuple(tuple), Value::Number(n)) => { let i = *n as usize; match tuple.get(i) { Some(n) => n.clone(), None => Value::Nil, } } _ => unreachable!("internal Ludus error"), } } pub fn get<'src>(dict: &Value<'src>, key: &Value<'src>) -> Value<'src> { match (dict, key) { (Value::Dict(dict), Value::Keyword(key)) => match dict.get(key) { Some(x) => x.clone(), None => Value::Nil, }, _ => unreachable!("internal Ludus error"), } } pub fn last<'src>(ordered: &Value<'src>) -> Value<'src> { match ordered { Value::List(list) => match list.last() { Some(x) => x.clone(), None => Value::Nil, }, Value::Tuple(tuple) => match tuple.last() { Some(x) => x.clone(), None => Value::Nil, }, _ => unreachable!("internal Ludus error"), } } pub fn or<'src>(x: &Value<'src>, y: &Value<'src>) -> Value<'src> { Value::Boolean(x.bool() || y.bool()) } pub fn print<'src>(x: &Value<'src>) -> Value<'src> { println!("{}", x); Value::Keyword("ok") } pub fn show<'src>(x: &Value<'src>) -> Value<'src> { Value::AllocatedString(Rc::new(format!("{x}"))) } pub fn rest<'src>(ordered: &Value<'src>) -> Value<'src> { match ordered { Value::List(list) => Value::List(list.clone().split_at(1).1), Value::Tuple(tuple) => Value::List(Vector::from_iter(tuple.iter().next().cloned())), _ => unreachable!("internal Ludus error"), } } pub fn count<'src>(coll: &Value<'src>) -> Value<'src> { match coll { Value::Dict(d) => Value::Number(d.len() as f64), Value::List(l) => Value::Number(l.len() as f64), Value::Tuple(t) => Value::Number(t.len() as f64), Value::AllocatedString(s) => Value::Number(s.len() as f64), Value::InternedString(s) => Value::Number(s.len() as f64), _ => unreachable!("internal Ludus error"), } } pub fn range<'src>(start: &Value<'src>, end: &Value<'src>) -> Value<'src> { match (start, end) { (Value::Number(start), Value::Number(end)) => { let start = *start as isize; let end = *end as isize; let mut range = Vector::new(); for n in start..end { range.push_back(Value::Number(n as f64)) } Value::List(range) } _ => unreachable!("internal Ludus error"), } } pub fn slice<'src>(ordered: &Value<'src>, start: &Value<'src>, end: &Value<'src>) -> Value<'src> { match (ordered, start, end) { (Value::List(list), Value::Number(start), Value::Number(end)) => { let mut newlist = list.clone(); let start = std::cmp::max(*start as usize, 0); let end = std::cmp::min(*end as usize, list.len()); Value::List(newlist.slice(start..end)) } // TODO: figure out something better to do than return an empty string on a bad slice (Value::AllocatedString(string), Value::Number(start), Value::Number(end)) => { let start = std::cmp::max(*start as usize, 0); let end = std::cmp::min(*end as usize, string.len()); Value::AllocatedString(Rc::new( string .clone() .as_str() .get(start..end) .unwrap_or("") .to_string(), )) } (Value::InternedString(string), Value::Number(start), Value::Number(end)) => { let start = std::cmp::max(*start as usize, 0); let end = std::cmp::min(*end as usize, string.len()); Value::AllocatedString(Rc::new(string.get(start..end).unwrap_or("").to_string())) } _ => unreachable!("internal Ludus error"), } } pub fn list<'src>(x: &Value<'src>) -> Value<'src> { match x { Value::List(_) => x.clone(), Value::Tuple(t) => Value::List(Vector::from_iter(t.iter().cloned())), Value::Dict(d) => { let kvs = d.iter(); let mut list = vector![]; for (key, value) in kvs { let kv = Value::Tuple(Rc::new(vec![Value::Keyword(key), value.clone()])); list.push_back(kv); } Value::List(list) } _ => Value::List(vector![x.clone()]), } } pub fn number<'src>(x: &Value<'src>) -> Value<'src> { match x { Value::InternedString(string) => match string.parse::() { Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), Err(_) => Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::AllocatedString(Rc::new(format!("could not parse `{string}` as a number"))), ])), }, Value::AllocatedString(string) => match string.parse::() { Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), Err(_) => Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::AllocatedString(Rc::new(format!("could not parse `{string}` as a number"))), ])), }, _ => unreachable!("internal Ludus error"), } } pub fn r#type<'src>(x: &Value<'src>) -> Value<'src> { match x { 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::InternedString(_) => Value::Keyword("string"), Value::AllocatedString(_) => 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!("internal Ludus error"), Value::Args(_) => unreachable!("internal Ludus error"), Value::Base(_) => Value::Keyword("fn"), Value::Recur(..) => unreachable!("internal Ludus error"), } } pub fn split<'src>(source: &Value<'src>, splitter: &Value) -> Value<'src> { match (source, splitter) { (Value::AllocatedString(source), Value::AllocatedString(splitter)) => { let parts = source.split_terminator(splitter.as_str()); let mut list = vector![]; for part in parts { list.push_back(Value::AllocatedString(Rc::new(part.to_string()))); } Value::List(list) } (Value::AllocatedString(source), Value::InternedString(splitter)) => { let parts = source.split_terminator(splitter); let mut list = vector![]; for part in parts { list.push_back(Value::AllocatedString(Rc::new(part.to_string()))); } Value::List(list) } (Value::InternedString(source), Value::AllocatedString(splitter)) => { let parts = source.split_terminator(splitter.as_str()); let mut list = vector![]; for part in parts { list.push_back(Value::AllocatedString(Rc::new(part.to_string()))); } Value::List(list) } (Value::InternedString(source), Value::InternedString(splitter)) => { let parts = source.split_terminator(splitter); let mut list = vector![]; for part in parts { list.push_back(Value::AllocatedString(Rc::new(part.to_string()))); } Value::List(list) } _ => unreachable!("internal Ludus error"), } } pub fn upcase<'src>(string: &Value<'src>) -> Value<'src> { match string { Value::AllocatedString(string) => Value::AllocatedString(Rc::new(string.to_uppercase())), Value::InternedString(string) => Value::AllocatedString(Rc::new(string.to_uppercase())), _ => unreachable!("internal Ludus error"), } } pub fn downcase<'src>(string: &Value<'src>) -> Value<'src> { match string { Value::AllocatedString(string) => Value::AllocatedString(Rc::new(string.to_lowercase())), Value::InternedString(string) => Value::AllocatedString(Rc::new(string.to_lowercase())), _ => unreachable!("internal Ludus error"), } } pub fn trim<'src>(string: &Value<'src>) -> Value<'src> { match string { Value::AllocatedString(string) => { Value::AllocatedString(Rc::new(string.trim().to_string())) } Value::InternedString(string) => Value::AllocatedString(Rc::new(string.trim().to_string())), _ => unreachable!("internal Ludus error"), } } pub fn triml<'src>(string: &Value<'src>) -> Value<'src> { match string { Value::AllocatedString(string) => { Value::AllocatedString(Rc::new(string.trim_start().to_string())) } Value::InternedString(string) => { Value::AllocatedString(Rc::new(string.trim_start().to_string())) } _ => unreachable!("internal Ludus error"), } } pub fn trimr<'src>(string: &Value<'src>) -> Value<'src> { match string { Value::AllocatedString(string) => { Value::AllocatedString(Rc::new(string.trim_end().to_string())) } Value::InternedString(string) => { Value::AllocatedString(Rc::new(string.trim_end().to_string())) } _ => unreachable!("internal Ludus error"), } } pub fn atan_2<'src>(x: &Value, y: &Value) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)), _ => unreachable!("internal Ludus error"), } } pub fn ceil<'src>(x: &Value) -> Value<'src> { match x { Value::Number(x) => Value::Number(x.ceil()), _ => unreachable!("internal Ludus error"), } } pub fn cos<'src>(x: &Value) -> Value<'src> { match x { Value::Number(x) => Value::Number(x.cos()), _ => unreachable!("internal Ludus error"), } } pub fn floor<'src>(x: &Value) -> Value<'src> { match x { Value::Number(x) => Value::Number(x.floor()), _ => unreachable!("internal Ludus error"), } } pub fn random<'src>() -> Value<'src> { Value::Number(ran_f64()) } pub fn round<'src>(x: &Value) -> Value<'src> { match x { Value::Number(x) => Value::Number(x.round()), _ => unreachable!("internal Ludus error"), } } pub fn sin<'src>(x: &Value) -> Value<'src> { match x { Value::Number(x) => Value::Number(x.sin()), _ => unreachable!("internal Ludus error"), } } pub fn sqrt<'src>(x: &Value) -> Value<'src> { match x { Value::Number(x) => Value::Number(x.sqrt()), _ => unreachable!("internal Ludus error"), } } pub fn tan<'src>(x: &Value) -> Value<'src> { match x { Value::Number(x) => Value::Number(x.tan()), _ => unreachable!("internal Ludus error"), } } pub fn gt<'src>(x: &Value, y: &Value) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Boolean(x > y), _ => unreachable!("internal Ludus error"), } } pub fn gte<'src>(x: &Value, y: &Value) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Boolean(x >= y), _ => unreachable!("internal Ludus error"), } } pub fn lt<'src>(x: &Value, y: &Value) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Boolean(x < y), _ => unreachable!("internal Ludus error"), } } pub fn lte<'src>(x: &Value, y: &Value) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Boolean(x <= y), _ => unreachable!("internal Ludus error"), } } pub fn r#mod<'src>(x: &Value, y: &Value) -> Value<'src> { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x % y), _ => unreachable!("internal Ludus error"), } } pub fn base<'src>() -> Vec<(String, Value<'src>)> { vec![ ("add".to_string(), Value::Base(Base::Binary(add))), ("and".to_string(), Value::Base(Base::Binary(and))), ("append".to_string(), Value::Base(Base::Binary(append))), ("assoc".to_string(), Value::Base(Base::Ternary(assoc))), ("at".to_string(), Value::Base(Base::Binary(at))), ("atan_2".to_string(), Value::Base(Base::Binary(atan_2))), ("bool".to_string(), Value::Base(Base::Unary(r#bool))), ("ceil".to_string(), Value::Base(Base::Unary(ceil))), ("chars".to_string(), Value::Base(Base::Unary(chars))), ("concat".to_string(), Value::Base(Base::Binary(concat))), ("cos".to_string(), Value::Base(Base::Unary(cos))), ("count".to_string(), Value::Base(Base::Unary(count))), ("dec".to_string(), Value::Base(Base::Unary(dec))), ("dissoc".to_string(), Value::Base(Base::Binary(dissoc))), ("div".to_string(), Value::Base(Base::Binary(div))), ("doc!".to_string(), Value::Base(Base::Unary(doc))), ("downcase".to_string(), Value::Base(Base::Unary(downcase))), ("eq?".to_string(), Value::Base(Base::Binary(eq))), ("first".to_string(), Value::Base(Base::Unary(first))), ("floor".to_string(), Value::Base(Base::Unary(floor))), ("get".to_string(), Value::Base(Base::Binary(get))), ("gt?".to_string(), Value::Base(Base::Binary(gt))), ("gte?".to_string(), Value::Base(Base::Binary(gte))), ("inc".to_string(), Value::Base(Base::Unary(inc))), ("last".to_string(), Value::Base(Base::Unary(last))), ("list".to_string(), Value::Base(Base::Unary(list))), ("lt?".to_string(), Value::Base(Base::Binary(lt))), ("lte?".to_string(), Value::Base(Base::Binary(lte))), ("mod".to_string(), Value::Base(Base::Binary(r#mod))), ("mult".to_string(), Value::Base(Base::Binary(mult))), ("number".to_string(), Value::Base(Base::Unary(number))), ("or".to_string(), Value::Base(Base::Binary(or))), ("pi".to_string(), Value::Number(std::f64::consts::PI)), ("print!".to_string(), Value::Base(Base::Unary(print))), ("random".to_string(), Value::Base(Base::Nullary(random))), ("range".to_string(), Value::Base(Base::Binary(range))), ("rest".to_string(), Value::Base(Base::Unary(rest))), ("round".to_string(), Value::Base(Base::Unary(round))), ("show".to_string(), Value::Base(Base::Unary(show))), ("sin".to_string(), Value::Base(Base::Unary(sin))), ("slice".to_string(), Value::Base(Base::Ternary(slice))), ("split".to_string(), Value::Base(Base::Binary(split))), ("sqrt".to_string(), Value::Base(Base::Unary(sqrt))), ( "sqrt_2".to_string(), Value::Number(std::f64::consts::SQRT_2), ), ("store!".to_string(), Value::Base(Base::Binary(store))), ("sub".to_string(), Value::Base(Base::Binary(sub))), ("tan".to_string(), Value::Base(Base::Unary(tan))), ("trim".to_string(), Value::Base(Base::Unary(trim))), ("triml".to_string(), Value::Base(Base::Unary(triml))), ("trimr".to_string(), Value::Base(Base::Unary(trimr))), ("type".to_string(), Value::Base(Base::Unary(r#type))), ("unbox".to_string(), Value::Base(Base::Unary(unbox))), ("upcase".to_string(), Value::Base(Base::Unary(upcase))), ] }