use crate::{ bitmap::MapPoint, brush::Brush, lisp::{ error::{EvalError, LispError}, expr::{Arity, LispExpr}, number::LispNumber, Environment, }, primitive, undo::PaintRecord, utils::load_script, }; use std::convert::TryInto; #[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, name: $name, }); let _ = $env.insert($name.to_string(), val); }; } #[macro_export] macro_rules! type_match { ($args:expr, $($range:literal => $kind:pat),+) => { { let mut temp_vec = vec![]; $( temp_vec.push(matches!(&$args[$range], $kind)); )+ temp_vec.iter().all(|&t| t) } }; ($args:expr, $($range:expr => $kind:pat),+) => { { let mut temp_vec = vec![]; $( let arg_range: &[LispExpr] = &$args[$range]; for arg in arg_range { temp_vec.push(matches!(arg, $kind)); } )+ temp_vec.iter().all(|&t| t) } } } pub fn new_env() -> Result { 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 type_match!(args, 0 => LispExpr::BoolLit(false)) { Ok(LispExpr::BoolLit(true)) } else { Ok(LispExpr::BoolLit(false)) } }); primitive!(env, Arity::Atleast(1), "begin", |args, _| { 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, _| { let s = &args[0]; let o = &args[1]; match (s, o) { (LispExpr::Unit, LispExpr::Unit) => Ok(true), (LispExpr::Number(s), LispExpr::Number(o)) => Ok(s == o), (LispExpr::List(s), LispExpr::List(o)) => Ok(s.iter().zip(o).all(|(a, b)| a == b)), (LispExpr::List(s), LispExpr::Unit) => Ok(s.len() == 0), (LispExpr::Unit, LispExpr::List(s)) => Ok(s.len() == 0), (LispExpr::StringLit(s), LispExpr::StringLit(o)) => Ok(s == o), (LispExpr::Char(s), LispExpr::Char(o)) => Ok(s == o), (LispExpr::BoolLit(s), LispExpr::BoolLit(o)) => Ok(s == o), (LispExpr::Ident(s), LispExpr::Ident(o)) => Ok(s == o), (s @ LispExpr::Quote(_, _), o @ LispExpr::Quote(_, _)) => { // text of quotation should be equal Ok(format!("{}", s) == format!("{}", o)) } _ => Err(EvalError::TypeMismatch.into()), } .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].unwrap_stringlit().len() as i64, ))) } else { Err(EvalError::TypeMismatch.into()) } }); primitive!(env, Arity::Exact(0), "save", |_, app| { if let Some(file_name) = &app.file_name { return app .save_as(&file_name) .map_err(|e| LispError::Stringified(e.to_string())) .map(|_| LispExpr::Unit); } return Err(EvalError::NoFileName.into()); }); primitive!(env, Arity::Exact(1), "save-as", |args, app| { match &args[0] { LispExpr::StringLit(s) => { return app .save_as(&s) .map_err(|e| LispError::Stringified(e.to_string())) .map(|_| LispExpr::Unit); } _ => return Err(EvalError::TypeMismatch.into()), } }); primitive!(env, Arity::Exact(2), "cons", |args, _| { if type_match!(args, 1 => LispExpr::Unit) { return Ok(LispExpr::List(vec![args[0].clone()])); } else if !type_match!(args, 1 => LispExpr::List(_)) { return Ok(LispExpr::List(vec![args[0].clone(), args[1].clone()])); } else { let mut rest = args[1].unwrap_list(); rest.insert(0, args[0].clone()); return Ok(LispExpr::List(rest)); } }); primitive!(env, Arity::Exact(0), "brush-fill", |_, app| { app.brush = Brush::Fill; return Ok(LispExpr::Unit); }); primitive!(env, Arity::Exact(0), "brush-circle", |_, app| { app.brush = Brush::new(0); return Ok(LispExpr::Unit); }); primitive!(env, Arity::Exact(0), "brush-line", |_, app| { app.brush = Brush::line(0, false); return Ok(LispExpr::Unit); }); primitive!(env, Arity::Exact(0), "brush-line-extend", |_, app| { app.brush = Brush::line(0, true); return Ok(LispExpr::Unit); }); primitive!(env, Arity::Exact(1), "car", |args, _| { if type_match!(args, 0 => LispExpr::List(_)) { return Ok(args[0].unwrap_list().swap_remove(0)); } else if type_match!(args, 0 => LispExpr::Unit) { return Err(EvalError::AccessEmptyList.into()); } else { return Err(EvalError::TypeMismatch.into()); } }); primitive!(env, Arity::Exact(1), "cdr", |args, _| { if type_match!(args, 0 => LispExpr::List(_)) { let mut ls = args[0].unwrap_list(); if ls.len() == 0 { return Err(EvalError::AccessEmptyList.into()); } else if ls.len() == 1 { return Ok(LispExpr::Unit); } else { ls.remove(0); return Ok(LispExpr::List(ls)); } } else if type_match!(args, 0 => LispExpr::Unit) { return Err(EvalError::AccessEmptyList.into()); } else { return Err(EvalError::TypeMismatch.into()); } }); primitive!(env, Arity::Atleast(1), "list", |args, _| { return Ok(LispExpr::List(args.to_vec())); }); primitive!(env, Arity::Exact(1), "load-script", |args, app| { if type_match!(args, 0 => LispExpr::StringLit(_)) { let path = args[0].unwrap_stringlit(); load_script(&path, app).map(|_| LispExpr::Unit) } else { return Err(EvalError::TypeMismatch.into()); } }); primitive!(env, Arity::Atleast(1), "error", |args, _| { if type_match!(args, 0 => LispExpr::StringLit(_)) { let mut s = String::from(args[0].unwrap_stringlit()); for arg in args.into_iter().skip(1) { s.push_str(&format!(" {}", arg)); } return Err(EvalError::Custom(s).into()); } else { return Err(EvalError::TypeMismatch.into()); } }); primitive!(env, Arity::Exact(2), "assert-eq", |args, app| { if args[0].compare(&args[1], &app.lisp_env)? { return Ok(LispExpr::Unit); } else { return Err(EvalError::AssertionError { expected: args[0].clone(), got: args[1].clone(), } .into()); } }); primitive!(env, Arity::Exact(0), "canvas-width", |_, app| { return Ok(LispExpr::Number(LispNumber::Integer(app.width() as i64))); }); primitive!(env, Arity::Exact(0), "canvas-height", |_, app| { return Ok(LispExpr::Number(LispNumber::Integer(app.height() as i64))); }); primitive!(env, Arity::Exact(3), "set-pixel!", |args, app| { if type_match!( args, 0 => LispExpr::Number(LispNumber::Integer(_)), 1 => LispExpr::Number(LispNumber::Integer(_)), 2 => LispExpr::BoolLit(_) ) { let x = args[0].unwrap_number(); let y = args[1].unwrap_number(); let val = args[2].cast_bool(); let set_loc: MapPoint = (x.unwrap_integer(), y.unwrap_integer()) .try_into() .map_err(|_| -> LispError { EvalError::InvalidCoordinates((x, y)).into() })?; if !app.pixmap.contains(set_loc) { return Err(EvalError::InvalidCoordinates((x, y)).into()); } else { let old_val = app.pixmap.set(set_loc, val); app.current_operation .push(PaintRecord::new(set_loc, old_val, val)); return Ok(LispExpr::Unit); } } else { return Err(EvalError::TypeMismatch.into()); } }); primitive!(env, Arity::Exact(0), "commit", |_, app| { app.commit_operation(); return Ok(LispExpr::Unit); }); Ok(env) }