use crate::env::Env;
use crate::value::{
bool_val, bool_val_at, list_val, list_val_at, map_val_at, nil, num_at, str_val_at,
vector_val_at, BlinkValue, Value,
};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
pub fn native_add(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
let sum: f64 = args
.into_iter()
.map(|arg| {
let node = arg.read();
match &node.value {
Value::Number(n) => Ok(*n),
_ => Err("+ expects numbers".to_string()),
}
})
.collect::<Result<Vec<f64>, _>>()?
.into_iter()
.sum();
Ok(num_at(sum, pos.map(|pos| pos.start)))
}
pub fn native_sub(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
let mut nums: Vec<f64> = args
.into_iter()
.map(|v| {
let node = v.read();
match &node.value {
Value::Number(n) => Ok(*n),
_ => Err("- expects numbers".to_string()),
}
})
.collect::<Result<_, _>>()?;
let first = nums.remove(0);
let result = if nums.is_empty() {
-first
} else {
nums.into_iter().fold(first, |a, b| a - b)
};
Ok(num_at(result, pos.map(|pos| pos.start)))
}
pub fn native_mul(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
let mut product = 1.0;
for arg in args {
let node = arg.read();
match &node.value {
Value::Number(n) => product *= n,
_ => return Err("* expects numbers".into()),
}
}
Ok(num_at(product, pos.map(|pos| pos.start)))
}
pub fn native_div(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
let mut nums: Vec<f64> = args
.into_iter()
.map(|v| {
let node = v.read();
match &node.value {
Value::Number(n) => Ok(*n),
_ => Err("/ expects numbers".to_string()),
}
})
.collect::<Result<_, _>>()?;
let first = nums.remove(0);
let result = if nums.is_empty() {
1.0 / first
} else {
nums.into_iter().fold(first, |a, b| a / b)
};
Ok(num_at(result, pos.map(|pos| pos.start)))
}
pub fn native_eq(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
if args.len() < 2 {
return Ok(bool_val(true));
}
let first_ref = args[0].read();
let first_val = &first_ref.value;
for other in &args[1..] {
let other_ref = other.read();
let other_val = &other_ref.value;
if first_val.type_tag() != other_val.type_tag() {
return Ok(bool_val(false));
}
if format!("{:?}", first_val) != format!("{:?}", other_val) {
return Ok(bool_val(false));
}
}
Ok(bool_val_at(true, pos.map(|pos| pos.start)))
}
pub fn native_not(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
if args.len() != 1 {
return Err("not expects one argument".into());
}
let result = match &args[0].read().value {
Value::Bool(b) => !*b,
Value::Nil => true,
_ => false,
};
Ok(bool_val_at(result, pos.map(|pos| pos.start)))
}
pub fn native_map(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
if args.len() != 2 {
return Err("map expects 2 arguments".into());
}
let func = args[0].clone();
let list = match &args[1].read().value {
Value::List(xs) => xs.clone(),
_ => return Err("map expects a list as second argument".into()),
};
let mut results = Vec::new();
for val in list {
if let Value::NativeFunc(f) = &func.read().value {
results.push(f(vec![val])?);
} else {
return Err("map only works on native functions for now".into());
}
}
Ok(list_val_at(results, pos.map(|pos| pos.start)))
}
pub fn native_reduce(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
if args.len() != 3 {
return Err("reduce expects 3 arguments".into());
}
let func = args[0].clone();
let initial = args[1].clone();
let list = match &args[2].read().value {
Value::List(xs) => xs.clone(),
_ => return Err("reduce expects a list as third argument".into()),
};
let mut acc = initial;
for val in list {
if let Value::NativeFunc(f) = &func.read().value {
acc = f(vec![acc, val])?;
} else {
return Err("reduce only works on native functions for now".into());
}
}
Ok(acc)
}
pub fn native_list(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
Ok(list_val(args))
}
pub fn native_vector(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
let pos = args.get(0).and_then(|v| v.read().pos.clone());
Ok(vector_val_at(args, pos.map(|pos| pos.start)))
}
pub fn native_map_construct(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
if args.len() % 2 != 0 {
return Err("map expects an even number of arguments".into());
}
let pos = args.get(0).and_then(|v| v.read().pos.clone());
let mut map = HashMap::new();
let mut it = args.into_iter();
while let (Some(k), Some(v)) = (it.next(), it.next()) {
let key_str = match &k.read().value {
Value::Str(s) => s.clone(),
Value::Symbol(s) => s.clone(),
Value::Keyword(k) => format!(":{}", k),
_ => return Err("map keys must be strings, symbols, or keywords".into()),
};
map.insert(key_str, v);
}
Ok(map_val_at(map, pos.map(|pos| pos.start)))
}
pub fn native_print(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
for val in args {
print!("{} ", format!("{:?}", val.read()));
}
println!();
Ok(nil())
}
pub fn native_type_of(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
if args.len() != 1 {
return Err("type-of expects exactly one argument".into());
}
let arg = &args[0];
let type_name = arg.read().value.type_tag();
let pos = arg.read().pos.clone();
Ok(str_val_at(type_name, pos.map(|pos| pos.start)))
}
pub fn native_cons(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
if args.len() != 2 {
return Err("cons expects 2 arguments".into());
}
let mut new_list = vec![args[0].clone()];
match &args[1].read().value {
Value::List(rest) => new_list.extend(rest.clone()),
_ => return Err("second argument to cons must be a list".into()),
}
Ok(list_val(new_list))
}
pub fn native_car(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
if args.len() != 1 {
return Err("car expects 1 argument".into());
}
let arg_ref = args[0].read();
match &arg_ref.value {
Value::List(xs) => xs.get(0).cloned().ok_or_else(|| "car on empty list".into()),
_ => Err("car expects a list".into()),
}
}
pub fn native_cdr(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
if args.len() != 1 {
return Err("cdr expects 1 argument".into());
}
let arg_ref = args[0].read();
match &arg_ref.value {
Value::List(xs) => Ok(list_val(xs.iter().skip(1).cloned().collect())),
_ => Err("cdr expects a list".into()),
}
}
pub fn native_get(args: Vec<BlinkValue>) -> Result<BlinkValue, String> {
if args.len() < 2 || args.len() > 3 {
return Err("get expects 2 or 3 arguments".into());
}
let target_val = &args[0];
let key_val = &args[1];
let fallback_val = args.get(2).cloned();
let key_pos = key_val.read().pos.clone(); let target_ref = target_val.read();
let target = &target_ref.value;
let key_ref = key_val.read();
let key = &key_ref.value;
match target {
Value::Vector(vec) => {
if let Value::Number(n) = key {
let idx = n.clone() as usize;
if let Some(val) = vec.get(idx).cloned() {
Ok(val)
} else if let Some(default) = fallback_val {
Ok(default)
} else {
Err(format!(
"Index {} out of bounds{}",
idx,
key_pos.map(|p| format!(" at {}", p)).unwrap_or_default()
))
}
} else {
Err("get on vector expects numeric index".into())
}
}
Value::Map(map) => {
let kstr = match key {
Value::Str(s) => s.clone(),
Value::Symbol(s) => s.clone(),
Value::Keyword(k) => format!(":{}", k),
_ => return Err("get on map expects string, symbol, or keyword key".into()),
};
if let Some(val) = map.get(&kstr) {
Ok(val.clone())
} else if let Some(default) = fallback_val {
Ok(default)
} else {
Err(format!(
"Key '{}' not found in map{}",
kstr,
key_pos.map(|p| format!(" at {}", p)).unwrap_or_default()
))
}
}
_ => Err("get only works on vector or map".into()),
}
}
use crate::value::LispNode;
pub fn register_builtins(env: &Arc<RwLock<Env>>) {
let mut e = env.write();
macro_rules! reg {
($name:expr, $func:expr) => {
e.set(
$name,
BlinkValue(Arc::new(RwLock::new(LispNode {
value: Value::NativeFunc($func),
pos: None,
}))),
);
};
}
reg!("+", native_add);
reg!("-", native_sub);
reg!("*", native_mul);
reg!("/", native_div);
reg!("=", native_eq);
reg!("not", native_not);
reg!("map", native_map);
reg!("reduce", native_reduce);
reg!("list", native_list);
reg!("vector", native_vector);
reg!("hash-map", native_map_construct);
reg!("print", native_print);
reg!("type-of", native_type_of);
reg!("cons", native_cons);
reg!("car", native_car);
reg!("cdr", native_cdr);
reg!("first", native_car);
reg!("rest", native_cdr);
reg!("get", native_get);
}