use crate::value::*; use imbl::*; use ran::ran_f64; use std::rc::Rc; #[derive(Clone, Debug)] pub enum BaseFn { Nullary(fn() -> Value), Unary(fn(&Value) -> Value), Binary(fn(&Value, &Value) -> Value), Ternary(fn(&Value, &Value, &Value) -> Value), } pub fn eq(x: &Value, y: &Value) -> Value { if x == y { Value::True } else { Value::False } } pub fn add(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x + y), _ => unreachable!("internal Ludus error"), } } pub fn sub(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x - y), _ => unreachable!("internal Ludus error"), } } pub fn unbox(x: &Value) -> Value { match x { Value::Box(cell) => cell.as_ref().borrow().clone(), _ => unreachable!("internal Ludus error"), } } pub fn store(b: &Value, val: &Value) -> Value { 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(f: &Value) -> Value { match f { Value::Fn(f) => f.as_ref().doc(), _ => Value::Interned("no documentation found"), } } pub fn assoc(dict: &Value, key: &Value, value: &Value) -> Value { match (dict, key) { (Value::Dict(d), Value::Keyword(k)) => Value::Dict(Box::new(d.update(k, value.clone()))), _ => unreachable!("internal Ludus error"), } } pub fn r#bool(x: &Value) -> Value { match x { Value::Nil | Value::False => Value::False, _ => Value::True, } } pub fn chars(x: &Value) -> Value { match x { Value::Interned(s) => { let chars = s.chars(); let mut charlist = vector![]; for char in chars { if char.is_ascii() { charlist.push_back(Value::String(Rc::new(char.to_string()))) } else { return Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::String(Rc::new(format!("{char} is not an ascii character"))), ])); } } Value::Tuple(Rc::new(vec![ Value::Keyword("ok"), Value::List(Box::new(charlist)), ])) } Value::String(s) => { let chars = s.chars(); let mut charlist = vector![]; for char in chars { if char.is_ascii() { charlist.push_back(Value::String(Rc::new(char.to_string()))) } else { return Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::String(Rc::new(format!("{char} is not an ascii character"))), ])); } } Value::Tuple(Rc::new(vec![ Value::Keyword("ok"), Value::List(Box::new(charlist)), ])) } _ => unreachable!("internal Ludus error"), } } // TODO: figure out how to get to opportunistic mutation here pub fn concat(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Interned(x), Value::Interned(y)) => Value::String(Rc::new(format!("{x}{y}"))), (Value::String(x), Value::String(y)) => Value::String(Rc::new(format!("{x}{y}"))), (Value::String(x), Value::Interned(y)) => Value::String(Rc::new(format!("{x}{y}"))), (Value::Interned(x), Value::String(y)) => Value::String(Rc::new(format!("{x}{y}"))), (Value::List(x), Value::List(y)) => { let mut newlist = *x.clone(); newlist.append(*y.clone()); Value::List(Box::new(newlist)) } _ => unreachable!("internal Ludus error"), } } pub fn append(x: &Value, y: &Value) -> Value { 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(x: &Value) -> Value { match x { Value::Number(n) => Value::Number(n - 1.0), _ => unreachable!("internal Ludus error"), } } pub fn inc(x: &Value) -> Value { match x { Value::Number(n) => Value::Number(n + 1.0), _ => unreachable!("internal Ludus error"), } } pub fn div(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x / y), _ => unreachable!("internal Ludus error"), } } pub fn mult(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x * y), _ => unreachable!("internal Ludus error"), } } pub fn dissoc(dict: &Value, key: &Value) -> Value { 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(ordered: &Value) -> Value { 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(ordered: &Value, i: &Value) -> Value { 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(dict: &Value, key: &Value) -> Value { 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(ordered: &Value) -> Value { 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"), } } // TODO: fix this: x is a list of all the args passed to Ludus's print! pub fn print(x: &Value) -> Value { let Value::List(args) = x else { unreachable!("internal Ludus error") }; let out = args .iter() .map(|val| format!("{val}")) .collect::>() .join(""); println!("{out}"); Value::Keyword("ok") } pub fn show(x: &Value) -> Value { Value::String(Rc::new(format!("{x}"))) } pub fn rest(ordered: &Value) -> Value { match ordered { Value::List(list) => Value::List(Box::new(list.clone().split_at(1).1)), Value::Tuple(tuple) => { Value::List(Box::new(Vector::from_iter(tuple.iter().next().cloned()))) } _ => unreachable!("internal Ludus error"), } } pub fn count(coll: &Value) -> Value { 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::String(s) => Value::Number(s.len() as f64), Value::Interned(s) => Value::Number(s.len() as f64), _ => unreachable!("internal Ludus error"), } } pub fn range(start: &Value, end: &Value) -> Value { 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(Box::new(range)) } _ => unreachable!("internal Ludus error"), } } pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value { 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(Box::new(newlist.slice(start..end))) } // TODO: figure out something better to do than return an empty string on a bad slice (Value::String(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::String(Rc::new( string .clone() .as_str() .get(start..end) .unwrap_or("") .to_string(), )) } (Value::Interned(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::String(Rc::new(string.get(start..end).unwrap_or("").to_string())) } _ => unreachable!("internal Ludus error"), } } pub fn list(x: &Value) -> Value { match x { Value::List(_) => x.clone(), Value::Tuple(t) => Value::List(Box::new(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(Box::new(list)) } _ => Value::List(Box::new(vector![x.clone()])), } } pub fn number(x: &Value) -> Value { match x { Value::Interned(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::String(Rc::new(format!("could not parse `{string}` as a number"))), ])), }, Value::String(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::String(Rc::new(format!("could not parse `{string}` as a number"))), ])), }, _ => unreachable!("internal Ludus error"), } } pub fn r#type(x: &Value) -> Value { match x { Value::Nil => Value::Keyword("nil"), Value::Number(_) => Value::Keyword("number"), Value::True | Value::False => Value::Keyword("bool"), Value::Keyword(_) => Value::Keyword("keyword"), Value::Tuple(_) => Value::Keyword("tuple"), Value::Interned(_) => Value::Keyword("string"), 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::BaseFn(_) => Value::Keyword("fn"), Value::Partial(_) => Value::Keyword("fn"), Value::Nothing => unreachable!(), } } pub fn split(source: &Value, splitter: &Value) -> Value { match (source, splitter) { (Value::String(source), Value::String(splitter)) => { let parts = source.split_terminator(splitter.as_str()); let mut list = vector![]; for part in parts { list.push_back(Value::String(Rc::new(part.to_string()))); } Value::List(Box::new(list)) } (Value::String(source), Value::Interned(splitter)) => { let parts = source.split_terminator(splitter); let mut list = vector![]; for part in parts { list.push_back(Value::String(Rc::new(part.to_string()))); } Value::List(Box::new(list)) } (Value::Interned(source), Value::String(splitter)) => { let parts = source.split_terminator(splitter.as_str()); let mut list = vector![]; for part in parts { list.push_back(Value::String(Rc::new(part.to_string()))); } Value::List(Box::new(list)) } (Value::Interned(source), Value::Interned(splitter)) => { let parts = source.split_terminator(splitter); let mut list = vector![]; for part in parts { list.push_back(Value::String(Rc::new(part.to_string()))); } Value::List(Box::new(list)) } _ => unreachable!("internal Ludus error"), } } pub fn upcase(string: &Value) -> Value { match string { Value::String(string) => Value::String(Rc::new(string.to_uppercase())), Value::Interned(string) => Value::String(Rc::new(string.to_uppercase())), _ => unreachable!("internal Ludus error"), } } pub fn downcase(string: &Value) -> Value { match string { Value::String(string) => Value::String(Rc::new(string.to_lowercase())), Value::Interned(string) => Value::String(Rc::new(string.to_lowercase())), _ => unreachable!("internal Ludus error"), } } pub fn trim(string: &Value) -> Value { match string { Value::String(string) => Value::String(Rc::new(string.trim().to_string())), Value::Interned(string) => Value::String(Rc::new(string.trim().to_string())), _ => unreachable!("internal Ludus error"), } } pub fn triml(string: &Value) -> Value { match string { Value::String(string) => Value::String(Rc::new(string.trim_start().to_string())), Value::Interned(string) => Value::String(Rc::new(string.trim_start().to_string())), _ => unreachable!("internal Ludus error"), } } pub fn trimr(string: &Value) -> Value { match string { Value::String(string) => Value::String(Rc::new(string.trim_end().to_string())), Value::Interned(string) => Value::String(Rc::new(string.trim_end().to_string())), _ => unreachable!("internal Ludus error"), } } pub fn atan_2(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)), _ => unreachable!("internal Ludus error"), } } pub fn ceil(x: &Value) -> Value { match x { Value::Number(x) => Value::Number(x.ceil()), _ => unreachable!("internal Ludus error"), } } pub fn cos(x: &Value) -> Value { match x { Value::Number(x) => Value::Number(x.cos()), _ => unreachable!("internal Ludus error"), } } pub fn floor(x: &Value) -> Value { match x { Value::Number(x) => Value::Number(x.floor()), _ => unreachable!("internal Ludus error"), } } pub fn random() -> Value { Value::Number(ran_f64()) } pub fn round(x: &Value) -> Value { match x { Value::Number(x) => Value::Number(x.round()), _ => unreachable!("internal Ludus error"), } } pub fn sin(x: &Value) -> Value { match x { Value::Number(x) => Value::Number(x.sin()), _ => unreachable!("internal Ludus error"), } } pub fn sqrt(x: &Value) -> Value { match x { Value::Number(x) => Value::Number(x.sqrt()), _ => unreachable!("internal Ludus error"), } } pub fn tan(x: &Value) -> Value { match x { Value::Number(x) => Value::Number(x.tan()), _ => unreachable!("internal Ludus error"), } } pub fn gt(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => { if x > y { Value::True } else { Value::False } } _ => unreachable!("internal Ludus error"), } } pub fn gte(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => { if x >= y { Value::True } else { Value::False } } _ => unreachable!("internal Ludus error"), } } pub fn lt(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => { if x < y { Value::True } else { Value::False } } _ => unreachable!("internal Ludus error"), } } pub fn lte(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => { if x <= y { Value::True } else { Value::False } } _ => unreachable!("internal Ludus error"), } } pub fn r#mod(x: &Value, y: &Value) -> Value { match (x, y) { (Value::Number(x), Value::Number(y)) => Value::Number(x % y), _ => unreachable!("internal Ludus error"), } } pub fn base() -> Value { let members = vec![ ("add", Value::BaseFn(BaseFn::Binary(add))), ("append", Value::BaseFn(BaseFn::Binary(append))), ("assoc", Value::BaseFn(BaseFn::Ternary(assoc))), ("at", Value::BaseFn(BaseFn::Binary(at))), ("atan_2", Value::BaseFn(BaseFn::Binary(atan_2))), ("bool", Value::BaseFn(BaseFn::Unary(r#bool))), ("ceil", Value::BaseFn(BaseFn::Unary(ceil))), ("chars", Value::BaseFn(BaseFn::Unary(chars))), ("concat", Value::BaseFn(BaseFn::Binary(concat))), ("cos", Value::BaseFn(BaseFn::Unary(cos))), ("count", Value::BaseFn(BaseFn::Unary(count))), ("dec", Value::BaseFn(BaseFn::Unary(dec))), ("dissoc", Value::BaseFn(BaseFn::Binary(dissoc))), ("div", Value::BaseFn(BaseFn::Binary(div))), ("doc!", Value::BaseFn(BaseFn::Unary(doc))), ("downcase", Value::BaseFn(BaseFn::Unary(downcase))), ("eq?", Value::BaseFn(BaseFn::Binary(eq))), ("first", Value::BaseFn(BaseFn::Unary(first))), ("floor", Value::BaseFn(BaseFn::Unary(floor))), ("get", Value::BaseFn(BaseFn::Binary(get))), ("gt?", Value::BaseFn(BaseFn::Binary(gt))), ("gte?", Value::BaseFn(BaseFn::Binary(gte))), ("inc", Value::BaseFn(BaseFn::Unary(inc))), ("last", Value::BaseFn(BaseFn::Unary(last))), ("list", Value::BaseFn(BaseFn::Unary(list))), ("lt?", Value::BaseFn(BaseFn::Binary(lt))), ("lte?", Value::BaseFn(BaseFn::Binary(lte))), ("mod", Value::BaseFn(BaseFn::Binary(r#mod))), ("mult", Value::BaseFn(BaseFn::Binary(mult))), ("number", Value::BaseFn(BaseFn::Unary(number))), ("pi", Value::Number(std::f64::consts::PI)), ("print!", Value::BaseFn(BaseFn::Unary(print))), ("random", Value::BaseFn(BaseFn::Nullary(random))), ("range", Value::BaseFn(BaseFn::Binary(range))), ("rest", Value::BaseFn(BaseFn::Unary(rest))), ("round", Value::BaseFn(BaseFn::Unary(round))), ("show", Value::BaseFn(BaseFn::Unary(show))), ("sin", Value::BaseFn(BaseFn::Unary(sin))), ("slice", Value::BaseFn(BaseFn::Ternary(slice))), ("split", Value::BaseFn(BaseFn::Binary(split))), ("sqrt", Value::BaseFn(BaseFn::Unary(sqrt))), ("sqrt_2", Value::Number(std::f64::consts::SQRT_2)), ("store!", Value::BaseFn(BaseFn::Binary(store))), ("sub", Value::BaseFn(BaseFn::Binary(sub))), ("tan", Value::BaseFn(BaseFn::Unary(tan))), ("trim", Value::BaseFn(BaseFn::Unary(trim))), ("triml", Value::BaseFn(BaseFn::Unary(triml))), ("trimr", Value::BaseFn(BaseFn::Unary(trimr))), ("type", Value::BaseFn(BaseFn::Unary(r#type))), ("unbox", Value::BaseFn(BaseFn::Unary(unbox))), ("upcase", Value::BaseFn(BaseFn::Unary(upcase))), ]; Value::Dict(Box::new(HashMap::from(members))) }