From 4afc8ffdfe7ca2976bbab9d59da4c2ae11902c2f Mon Sep 17 00:00:00 2001 From: Akshay Date: Tue, 30 Mar 2021 19:33:46 +0530 Subject: add assert primitive; pass state in Evaluator --- src/lisp/error.rs | 6 +- src/lisp/eval.rs | 400 ++++++++++++++++++++++++---------------------------- src/lisp/expr.rs | 7 +- src/lisp/prelude.rs | 57 +++----- src/lisp/std.lisp | 22 +++ src/utils.rs | 8 +- 6 files changed, 240 insertions(+), 260 deletions(-) diff --git a/src/lisp/error.rs b/src/lisp/error.rs index 53681d8..f905e6d 100644 --- a/src/lisp/error.rs +++ b/src/lisp/error.rs @@ -1,5 +1,5 @@ use crate::lisp::{ - expr::Arity, + expr::{Arity, LispExpr}, lex::{Span, SpanDisplay}, }; @@ -98,6 +98,7 @@ pub enum EvalError { TypeMismatch, NoFileName, AccessEmptyList, + AssertionError { expected: LispExpr, got: LispExpr }, ScriptLoadError(io::Error), CustomInternal(&'static str), Custom(String), @@ -124,6 +125,9 @@ impl fmt::Display for EvalError { Self::DivByZero => write!(f, "attempt to divide by zero"), Self::NoFileName => write!(f, "no file name specified"), Self::AccessEmptyList => write!(f, "attempted to access empty list"), + Self::AssertionError { expected, got } => { + write!(f, "assertion error: expected `{}` got `{}`", expected, got) + } Self::ScriptLoadError(s) => write!(f, "error while loading script: {}", s), Self::CustomInternal(s) => write!(f, "{}", s), Self::Custom(s) => write!(f, "error: {}", s), diff --git a/src/lisp/eval.rs b/src/lisp/eval.rs index 44540c0..2e46251 100644 --- a/src/lisp/eval.rs +++ b/src/lisp/eval.rs @@ -3,7 +3,6 @@ use crate::{ lisp::{ error::{EvalError, LispError}, expr::{Arity, Ident, LispExpr, LispFunction}, - number::LispNumber, Environment, }, type_match, @@ -13,155 +12,208 @@ use std::convert::TryInto; use log::{error, info}; -pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result { - match expr { - LispExpr::Unit => Ok(expr.clone()), - LispExpr::StringLit(_) => Ok(expr.clone()), - LispExpr::Char(_) => Ok(expr.clone()), - LispExpr::Number(_) => Ok(expr.clone()), - LispExpr::BoolLit(_) => Ok(expr.clone()), - LispExpr::Ident(ref id) => lookup_extended(&app.lisp_env, id), - LispExpr::Quote(item, _) => match item.as_ref() { - i @ LispExpr::Unit - | i @ LispExpr::StringLit(_) - | i @ LispExpr::Char(_) - | i @ LispExpr::Number(_) - | i @ LispExpr::BoolLit(_) => Ok(i.clone()), - _ => Ok(*item.clone()), - }, - LispExpr::List(li) => { - let func_expr = &li[0]; - match func_expr { - LispExpr::Ident(s) => match s.as_ref() { - "define" => define_var(&li[1..], app), - "set!" => set_var(&li[1..], app), - "lambda" => create_lambda(&li[1..]), - "if" => eval_if(&li[1..], app), - "quote" => eval_quote(&li[1..]), - _ => { - let mut new_ls = vec![eval(&func_expr, app)?]; - new_ls.extend(li[1..].to_vec()); - eval(&(LispExpr::List(new_ls)), app) - } - }, - LispExpr::PrimitiveFunc(f) => { - let mut args = Vec::new(); - for item in li[1..].iter() { - args.push(eval(item, app)?); - } - f.call(&args, app) - } - LispExpr::Function(f) => { - let mut args = Vec::new(); - for item in li[1..].iter() { - let i = eval(item, app)?; - args.push(i); +pub type Context = Vec; + +pub struct Evaluator<'ctx, 'file, 'global> { + pub app: &'global mut AppState<'ctx, 'file>, + pub context: Context, +} + +impl<'ctx, 'file, 'global> Evaluator<'ctx, 'file, 'global> +where + 'ctx: 'global, + 'file: 'global, +{ + pub fn eval(&mut self, expr: &LispExpr) -> Result { + match expr { + LispExpr::Unit => Ok(expr.clone()), + LispExpr::StringLit(_) => Ok(expr.clone()), + LispExpr::Char(_) => Ok(expr.clone()), + LispExpr::Number(_) => Ok(expr.clone()), + LispExpr::BoolLit(_) => Ok(expr.clone()), + LispExpr::Ident(ref id) => lookup(&self.app.lisp_env, id), + LispExpr::Quote(item, _) => match item.as_ref() { + i @ LispExpr::Unit + | i @ LispExpr::StringLit(_) + | i @ LispExpr::Char(_) + | i @ LispExpr::Number(_) + | i @ LispExpr::BoolLit(_) => Ok(i.clone()), + _ => Ok(*item.clone()), + }, + LispExpr::List(li) => { + let func_expr = &li[0]; + match func_expr { + LispExpr::Ident(s) => match s.as_ref() { + "define" => self.define_var(&li[1..]), + "set!" => self.set_var(&li[1..]), + "lambda" => create_lambda(&li[1..]), + "if" => self.eval_if(&li[1..]), + "quote" => eval_quote(&li[1..]), + _ => { + let mut new_ls = vec![self.eval(&func_expr)?]; + new_ls.extend(li[1..].to_vec()); + self.eval(&(LispExpr::List(new_ls))) + } + }, + LispExpr::PrimitiveFunc(f) => { + let mut args = Vec::new(); + // context.push(f.name.to_string()); + for item in li[1..].iter() { + args.push(self.eval(item)?); + } + f.call(&args, &mut self.app) } - if f.params.len() != args.len() { - info!("too many or too little number of args"); - Err(EvalError::ArgumentCount(Arity::Exact(f.params.len())).into()) - } else { - let nested_env: Environment = - f.params.clone().into_iter().zip(args).collect(); - app.lisp_env.push(nested_env); - let result = if f.body.is_empty() { - Ok(LispExpr::Unit) + LispExpr::Function(f) => { + let mut args = Vec::new(); + for item in li[1..].iter() { + let i = self.eval(item)?; + args.push(i); + } + if f.params.len() != args.len() { + info!("too many or too little number of args"); + Err(EvalError::ArgumentCount(Arity::Exact(f.params.len())).into()) } else { - eval(&LispExpr::List(f.body.clone()), app) - }; - app.lisp_env.pop(); - return result; + let nested_env: Environment = + f.params.clone().into_iter().zip(args).collect(); + self.app.lisp_env.push(nested_env); + let result = if f.body.is_empty() { + Ok(LispExpr::Unit) + } else { + self.eval(&LispExpr::List(f.body.clone())) + }; + self.app.lisp_env.pop(); + return result; + } } + LispExpr::List(_) => { + info!("list as funciton"); + let func_expr = self.eval(&func_expr)?; + let mut new_ls = vec![func_expr]; + new_ls.extend(li[1..].to_vec()); + self.eval(&(LispExpr::List(new_ls))) + } + _ => Err(EvalError::BadForm.into()), } - LispExpr::List(_) => { - info!("list as funciton"); - let func_expr = eval(&func_expr, app)?; - let mut new_ls = vec![func_expr]; - new_ls.extend(li[1..].to_vec()); - eval(&(LispExpr::List(new_ls)), app) - } - _ => Err(EvalError::BadForm.into()), } + _ => Err(EvalError::BadForm.into()), } - _ => Err(EvalError::BadForm.into()), } -} -pub fn define_var(args: &[LispExpr], app: &mut AppState) -> Result { - info!("defining"); - let arity = Arity::Exact(2); - if !arity.is_valid(args) { - return Err(arity.to_error()); - } - match args { - [LispExpr::Ident(id), expr] => { - let value = eval(&expr, app)?; - let local_env = app.lisp_env.last_mut(); - if let Some(env) = local_env { - env.insert(id.into(), value); - } else { - error!("Unable to create global definition"); - return Err(EvalError::BadForm.into()); - } - return Ok(LispExpr::Unit); + pub fn define_var(&mut self, args: &[LispExpr]) -> Result { + let arity = Arity::Exact(2); + if !arity.is_valid(args) { + return Err(arity.to_error()); } - [LispExpr::List(shorthand), LispExpr::List(body)] => { - // (define (func arg) ) shorthand + match args { + [LispExpr::Ident(id), expr] => { + let value = self.eval(&expr)?; + let local_env = &mut self.app.lisp_env.last_mut(); + if let Some(env) = local_env { + env.insert(id.into(), value); + } else { + error!("Unable to create global definition"); + return Err(EvalError::BadForm.into()); + } + return Ok(LispExpr::Unit); + } + [LispExpr::List(shorthand), LispExpr::List(body)] => { + // (define (func arg) ) shorthand - let id = shorthand[0].unwrap_ident(); - let params = if shorthand.len() > 1 { - &shorthand[1..] - } else { - &[] + let id = shorthand[0].unwrap_ident(); + let params = if shorthand.len() > 1 { + &shorthand[1..] + } else { + &[] + } + .to_vec() + .into_iter() + .map(|arg| arg.try_into()) + .collect::, LispError>>()?; + let value = LispExpr::Function(LispFunction { + params, + body: body.to_vec(), + }); + + let local_env = &mut self.app.lisp_env.last_mut(); + if let Some(env) = local_env { + env.insert(id.into(), value); + } else { + error!("Unable to create global definition"); + return Err(EvalError::BadForm.into()); + } + return Ok(LispExpr::Unit); + } + _ => { + error!("Invalid usage of `define`"); + Err(EvalError::BadForm.into()) } - .to_vec() - .into_iter() - .map(|arg| arg.try_into()) - .collect::, LispError>>()?; - let value = LispExpr::Function(LispFunction { - params, - body: body.to_vec(), - }); + } + } - let local_env = app.lisp_env.last_mut(); - if let Some(env) = local_env { - info!("creating function {}", id); - env.insert(id.into(), value); - } else { - error!("Unable to create global definition"); + pub fn set_var(&mut self, args: &[LispExpr]) -> Result { + let arity = Arity::Exact(2); + if !arity.is_valid(args) { + return Err(arity.to_error()); + } + match args { + [LispExpr::Ident(id), expr] => { + let value = self.eval(&expr)?; + let local_env = self.app.lisp_env.last_mut(); + if let Some(env) = local_env { + return env + .insert(id.into(), value) + .ok_or(EvalError::UnboundVariable(id.into()).into()); + } else { + error!("Unable to set in global env!"); + return Err(EvalError::BadForm.into()); + } + } + _ => { + error!("Invalid usage of `set!`"); return Err(EvalError::BadForm.into()); } - return Ok(LispExpr::Unit); } - _ => { - error!("Invalid usage of `define`"); - Err(EvalError::BadForm.into()) + } + + pub fn eval_if(&mut self, args: &[LispExpr]) -> Result { + let arity = Arity::Exact(3); + if !arity.is_valid(args) { + return Err(arity.to_error()); + } else { + match args { + [predicate, then, else_] => { + let predicate = self.eval(&predicate)?; + if matches!(predicate, LispExpr::BoolLit(false)) { + return self.eval(&else_); + } else { + return self.eval(&then); + } + } + _ => { + panic!("panicked at `if` expression") + } + } } } } -pub fn set_var(args: &[LispExpr], app: &mut AppState) -> Result { - let arity = Arity::Exact(2); +pub fn eval_quote(args: &[LispExpr]) -> Result { + let arity = Arity::Exact(1); if !arity.is_valid(args) { return Err(arity.to_error()); - } - match args { - [LispExpr::Ident(id), expr] => { - let value = eval(&expr, app)?; - let local_env = app.lisp_env.last_mut(); - if let Some(env) = local_env { - return env - .insert(id.into(), value) - .ok_or(EvalError::UnboundVariable(id.into()).into()); - } else { - error!("Unable to set in global env!"); - return Err(EvalError::BadForm.into()); + } else { + match &args[0] { + LispExpr::Quote(item, depth) => Ok(LispExpr::Quote(item.clone(), depth + 1)), + i @ LispExpr::Unit + | i @ LispExpr::StringLit(_) + | i @ LispExpr::Char(_) + | i @ LispExpr::Number(_) + | i @ LispExpr::BoolLit(_) => Ok(i.clone()), + _ => { + let quoted_expr = Box::new(args[0].clone()); + Ok(LispExpr::Quote(quoted_expr, 1)) } } - _ => { - error!("Invalid usage of `set!`"); - return Err(EvalError::BadForm.into()); - } } } @@ -170,7 +222,6 @@ pub fn create_lambda(cdr: &[LispExpr]) -> Result { if !arity.is_valid(cdr) { return Err(arity.to_error()); } - info!("creating lambda"); match cdr { [LispExpr::List(params), LispExpr::List(body)] if type_match!(params, (..) => LispExpr::Ident(_)) => { @@ -189,48 +240,7 @@ pub fn create_lambda(cdr: &[LispExpr]) -> Result { } } -pub fn eval_if(args: &[LispExpr], app: &mut AppState) -> Result { - let arity = Arity::Exact(3); - if !arity.is_valid(args) { - return Err(arity.to_error()); - } else { - match args { - [predicate, then, else_] => { - let predicate = eval(&predicate, app)?; - if matches!(predicate, LispExpr::BoolLit(false)) { - return eval(&else_, app); - } else { - return eval(&then, app); - } - } - _ => { - panic!("panicked at `if` expression") - } - } - } -} - -pub fn eval_quote(args: &[LispExpr]) -> Result { - let arity = Arity::Exact(1); - if !arity.is_valid(args) { - return Err(arity.to_error()); - } else { - match &args[0] { - LispExpr::Quote(item, depth) => Ok(LispExpr::Quote(item.clone(), depth + 1)), - i @ LispExpr::Unit - | i @ LispExpr::StringLit(_) - | i @ LispExpr::Char(_) - | i @ LispExpr::Number(_) - | i @ LispExpr::BoolLit(_) => Ok(i.clone()), - _ => { - let quoted_expr = Box::new(args[0].clone()); - Ok(LispExpr::Quote(quoted_expr, 1)) - } - } - } -} - -pub fn lookup_extended(env_list: &[Environment], key: &str) -> Result { +pub fn lookup(env_list: &[Environment], key: &str) -> Result { if env_list.is_empty() { return Err(EvalError::UnboundVariable(key.into()).into()); } else { @@ -238,55 +248,7 @@ pub fn lookup_extended(env_list: &[Environment], key: &str) -> Result LispExpr { - let mut parser = Parser::new(Lexer::new(code, 0)); - eval(&parser.parse_single_expr().unwrap(), app).unwrap() - } - - #[test] - fn eval_all() { - let sdl_context = sdl2::init().unwrap(); - let ttf_context = sdl2::ttf::init().unwrap(); - let mut app = AppState::init(100, 100, &sdl_context, &ttf_context, None, None); - eval_arithmetic(&mut app); - eval_logical(&mut app); - } - - fn eval_arithmetic(app: &mut AppState) { - assert_eq!( - run("(+ 1 2 3)", app), - LispExpr::Number(LispNumber::Integer(6)) - ); - assert_eq!( - run("(+ 1.1 2.2 3.3)", app), - LispExpr::Number(LispNumber::Float(6.6)) - ); - assert_eq!( - run("(* 1 2 3 4 5)", app), - LispExpr::Number(LispNumber::Integer(120)) - ); - assert_eq!(run("(< 1 2)", app), LispExpr::BoolLit(true)); - assert_eq!(run("(> 6 5 4 3 2 1)", app), LispExpr::BoolLit(true)); - assert_eq!(run("(< 1 2 3 4 5 6)", app), LispExpr::BoolLit(true)); - assert_eq!(run("(>= 5 5 4 3 2 1)", app), LispExpr::BoolLit(true)); - assert_eq!(run("(<= 2 2 3 4 5 6)", app), LispExpr::BoolLit(true)); - } - - fn eval_logical(app: &mut AppState) { - assert_eq!(run("(and #t #t)", app), LispExpr::BoolLit(true)); - assert_eq!(run("(or #f #t)", app), LispExpr::BoolLit(true)); - assert_eq!(run("(not #t)", app), LispExpr::BoolLit(false)); - assert_eq!(run("(not #f)", app), run("(not (not #t))", app)); - } -} diff --git a/src/lisp/expr.rs b/src/lisp/expr.rs index 9f2dc8d..1f3e0fb 100644 --- a/src/lisp/expr.rs +++ b/src/lisp/expr.rs @@ -3,7 +3,7 @@ use std::{cmp::PartialEq, convert::TryFrom, fmt}; use crate::app::AppState; use crate::lisp::{ error::{EvalError, LispError}, - eval::lookup_extended, + eval::{lookup, Evaluator}, number::LispNumber, EnvList, }; @@ -36,6 +36,7 @@ impl Arity { pub struct PrimitiveFunc { pub arity: Arity, // minimim arity pub closure: fn(&[LispExpr], &mut AppState) -> Result, + pub name: &'static str, } impl PrimitiveFunc { @@ -123,8 +124,8 @@ impl LispExpr { (LispExpr::Char(s), LispExpr::Char(o)) => Ok(s == o), (LispExpr::BoolLit(s), LispExpr::BoolLit(o)) => Ok(s == o), (LispExpr::Ident(s), LispExpr::Ident(o)) => { - let s = lookup_extended(envs, s)?; - let o = lookup_extended(envs, o)?; + let s = lookup(envs, s)?; + let o = lookup(envs, o)?; s.compare(&o, envs) } (LispExpr::PrimitiveFunc(s), LispExpr::PrimitiveFunc(o)) => { diff --git a/src/lisp/prelude.rs b/src/lisp/prelude.rs index dffd9f4..27e7ad2 100644 --- a/src/lisp/prelude.rs +++ b/src/lisp/prelude.rs @@ -2,7 +2,7 @@ use crate::{ brush::Brush, lisp::{ error::{EvalError, LispError}, - eval::eval, + eval::Evaluator, expr::{Arity, LispExpr}, number::LispNumber, Environment, @@ -21,6 +21,7 @@ macro_rules! primitive { let val = crate::lisp::expr::LispExpr::PrimitiveFunc(crate::lisp::expr::PrimitiveFunc { arity: $arity, closure: $closure, + name: $name, }); let _ = $env.insert($name.to_string(), val); }; @@ -241,41 +242,15 @@ pub fn new_env() -> Result { 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::Exact(2), "filter", |args, app| { - let mut apply_filter = - |func: &LispExpr, ls: &Vec| -> Result, LispError> { - let mut result = vec![]; - for arg in ls.into_iter() { - if eval(&LispExpr::List(vec![func.clone(), arg.clone()]), app)?.cast_bool() { - result.push(arg.clone()) - } - } - Ok(result) - }; - if matches!(&args[0], LispExpr::Function(_) | LispExpr::PrimitiveFunc(_)) { - match &args[1] { - LispExpr::List(ls) => return Ok(LispExpr::List(apply_filter(&args[0], ls)?)), - _ => 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 { - return Err(EvalError::TypeMismatch.into()); + let mut rest = args[1].unwrap_list(); + rest.insert(0, args[0].clone()); + return Ok(LispExpr::List(rest)); } }); @@ -332,5 +307,17 @@ pub fn new_env() -> Result { } }); + 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) } diff --git a/src/lisp/std.lisp b/src/lisp/std.lisp index c723a13..8350482 100644 --- a/src/lisp/std.lisp +++ b/src/lisp/std.lisp @@ -22,5 +22,27 @@ accumulator (cdr ls)))) +(define (map func ls) + (if (null? ls) + '() + (cons (func (car ls)) + (map func (cdr ls))))) + +(define (filter pred ls) + (if (null? ls) + '() + (if (pred (car ls)) + (cons (car ls) (filter pred (cdr ls))) + (filter pred (cdr ls))))) + +(define (member? item ls) + (if (null? ls) + #f + (or (eq? item (car ls)) + (member? item (cdr ls))))) + +(define (assert expr) + (assert-eq #t expr)) + (define (sum ls) (fold 0 + ls)) (define (product ls) (fold 1 * ls)) diff --git a/src/utils.rs b/src/utils.rs index fc96615..dcf652d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,7 @@ use crate::{ consts::FONT_PATH, lisp::{ error::{EvalError, LispError, ParseError}, - eval::eval, + eval::Evaluator, expr::LispExpr, lex::Lexer, parse::Parser, @@ -80,10 +80,14 @@ pub fn load_script>(path: P, app: &mut AppState) -> Result<(), Li .map_err(EvalError::ScriptLoadError)?; let mut parser = Parser::new(Lexer::new(&buf, 0)); + let mut evaluator = Evaluator { + app, + context: Vec::new(), + }; for expr in parser.parse_exprs().map_err(|err| { LispError::Stringified(err.display(&buf, path.as_ref().to_str().unwrap_or(""))) })? { - eval(&expr, app)?; + evaluator.eval(&expr)?; } return Ok(()); } -- cgit v1.2.3