use crate::{ brush::Brush, lisp::{ error::{EvalError, LispError}, eval::eval, expr::{is_ident, Arity, LispExpr}, number::LispNumber, Environment, }, primitive, }; use std::{convert::TryInto, fs::File, io::Write}; use log::info; #[macro_export] macro_rules! primitive { ($env:expr, $arity:expr, $name:expr, $closure:expr) => { let val = crate::lisp::expr::LispExpr::PrimitiveFunc(crate::lisp::expr::PrimitiveFunc { arity: $arity, closure: $closure, }); let _ = $env.insert($name.to_string(), val); }; } #[macro_export] macro_rules! type_match { ($args:expr, $($range:expr => $kind:pat),+) => { { let mut temp_vec = vec![]; $( temp_vec.push(matches!(&$args[$range], $kind)); )+ temp_vec.iter().all(|&t| t) } } } pub fn new_env() -> Environment { let mut env = Environment::new(); primitive!(env, Arity::Atleast(2), "+", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; return Ok(LispExpr::Number( nums.iter().fold(LispNumber::Integer(0), |acc, &x| acc + *x), )); }); primitive!(env, Arity::Atleast(2), "-", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let mut acc = nums[0].clone(); for arg in nums.into_iter().skip(1) { acc = acc - *arg; } Ok(LispExpr::Number(acc)) }); primitive!(env, Arity::Atleast(2), "*", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; return Ok(LispExpr::Number( nums.iter().fold(LispNumber::Integer(1), |acc, &x| acc * *x), )); }); primitive!(env, Arity::Atleast(2), "/", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let mut acc = nums[0].clone(); for arg in nums.into_iter().skip(1) { acc = acc.div(*arg)?; } Ok(LispExpr::Number(acc)) }); primitive!(env, Arity::Exact(0), "toggle-grid", |_, app| { app.toggle_grid(); Ok(LispExpr::Unit) }); primitive!(env, Arity::Atleast(2), "and", |args, _| { if args .iter() .any(|arg| matches!(arg, LispExpr::BoolLit(false))) { Ok(LispExpr::BoolLit(false)) } else { Ok(LispExpr::BoolLit(true)) } }); primitive!(env, Arity::Atleast(2), "or", |args, _| { if args .iter() .any(|arg| matches!(arg, LispExpr::BoolLit(true))) { Ok(LispExpr::BoolLit(true)) } else { Ok(LispExpr::BoolLit(false)) } }); primitive!(env, Arity::Exact(1), "not", |args, _| { if matches!(&args[0], LispExpr::BoolLit(false)) { Ok(LispExpr::BoolLit(true)) } else { Ok(LispExpr::BoolLit(false)) } }); primitive!(env, Arity::Atleast(1), "begin", |args, _| { if args.is_empty() { Err(EvalError::ArgumentCount(Arity::Atleast(1)).into()) } else { Ok(args.into_iter().last().unwrap().clone()) } }); primitive!(env, Arity::Exact(0), "quit", |_, app| { app.quit(); Ok(LispExpr::Unit) }); primitive!(env, Arity::Exact(2), "eq?", |args, app| { let s = &args[0]; let o = &args[1]; info!("comparing {} {}", s, o); let result = s.compare(o, &app.lisp_env); result.map(LispExpr::BoolLit) }); primitive!(env, Arity::Atleast(2), ">", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let acc = nums[0].clone(); Ok(LispExpr::BoolLit( nums.into_iter().skip(1).all(|&arg| acc > arg), )) }); primitive!(env, Arity::Atleast(2), ">=", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let acc = nums[0].clone(); Ok(LispExpr::BoolLit( nums.into_iter().skip(1).all(|&arg| acc >= arg), )) }); primitive!(env, Arity::Atleast(2), "<", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let acc = nums[0].clone(); Ok(LispExpr::BoolLit( nums.into_iter().skip(1).all(|&arg| acc < arg), )) }); primitive!(env, Arity::Atleast(2), "<=", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let acc = nums[0].clone(); Ok(LispExpr::BoolLit( nums.into_iter().skip(1).all(|&arg| acc <= arg), )) }); primitive!(env, Arity::Exact(1), "string-len", |args, _| { if type_match!(args, 0 => LispExpr::StringLit(_)) { Ok(LispExpr::Number(LispNumber::Integer( args[0].as_ref().len() as i64, ))) } else { Err(EvalError::TypeMismatch.into()) } }); primitive!(env, Arity::Atmost(1), "save", |args, app| { let image = app.export().encode().unwrap(); if type_match!(args, 0 => LispExpr::StringLit(_)) { let mut buffer = File::create(&args[0].as_ref()).unwrap(); buffer.write_all(&image[..]).unwrap(); } else if let Some(p) = app.file_name { let file_name = p; let mut buffer = File::create(&file_name).unwrap(); buffer.write_all(&image[..]).unwrap(); } else { return Err(EvalError::NoFileName.into()); } return Ok(LispExpr::Unit); }); primitive!(env, Arity::Atmost(1), "brush", |args, app| { let old_size = if matches!(app.brush, Brush::Line { .. } | Brush::Circle { .. }) { app.brush.size().unwrap() } else { 0 }; if let [LispExpr::Quote(kind, _)] = args { if is_ident(kind) { match (&**kind).as_ref() { "fill" => app.brush = Brush::Fill, "circle" => app.brush = Brush::new(old_size), "line" => app.brush = Brush::line(old_size, false), "line-extend" => app.brush = Brush::line(old_size, true), _ => return Err(EvalError::CustomInternal("unknown brush type").into()), } } } else { return Err(EvalError::TypeMismatch.into()); } return Ok(LispExpr::Unit); }); primitive!(env, Arity::Exact(2), "map", |args, app| { let mut apply_map = |func: &LispExpr, ls: &Vec| -> Result, LispError> { ls.into_iter() .map(|arg| eval(&LispExpr::List(vec![func.clone(), arg.clone()]), app)) .collect() }; if matches!(&args[0], LispExpr::Function(_) | LispExpr::PrimitiveFunc(_)) { match &args[1] { LispExpr::List(ls) => return Ok(LispExpr::List(apply_map(&args[0], ls)?)), _ => return Err(EvalError::TypeMismatch.into()), } } else { return Err(EvalError::TypeMismatch.into()); } }); primitive!(env, Arity::Atleast(1), "list", |args, _| { return Ok(LispExpr::List(args.to_vec())); }); env }