use crate::{ brush::Brush, lisp::{ error::{EvalError, LispError}, eval::Evaluator, expr::{Arity, LispExpr}, number::LispNumber, Environment, }, primitive, utils::load_script, }; 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, 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![]; $( for arg in &$args[$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, app| { let s = &args[0]; let o = &args[1]; info!("comparing s: {} and o: {}", 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].unwrap_stringlit().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 matches!(kind.as_ref(), LispExpr::Ident(_)) { 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), "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(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()); } }); Ok(env) }