From dbcaa0df655bdd11c6d01ce28e018fc1e80ed394 Mon Sep 17 00:00:00 2001 From: Akshay Date: Tue, 30 Mar 2021 16:52:15 +0530 Subject: better parse errors; include lisp stdlib --- src/lisp/error.rs | 25 ++++++++++--- src/lisp/eval.rs | 52 ++++++++++++++++---------- src/lisp/expr.rs | 60 ++++++++++++++++++++++++++---- src/lisp/lex.rs | 22 +++++++---- src/lisp/prelude.rs | 104 +++++++++++++++++++++++++++++++++++++++++++++------- src/lisp/std.lisp | 26 +++++++++++++ 6 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 src/lisp/std.lisp (limited to 'src') diff --git a/src/lisp/error.rs b/src/lisp/error.rs index 6d28c22..53681d8 100644 --- a/src/lisp/error.rs +++ b/src/lisp/error.rs @@ -3,19 +3,23 @@ use crate::lisp::{ lex::{Span, SpanDisplay}, }; -use std::fmt; +use std::{fmt, io}; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug)] pub enum LispError { Parse(ParseError), Eval(EvalError), + Stringified(String), } +impl std::error::Error for LispError {} + impl fmt::Display for LispError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Parse(p) => write!(f, "parse error: {}", p.kind), Self::Eval(e) => write!(f, "eval error: {}", e), + Self::Stringified(s) => write!(f, "{}", s), } } } @@ -30,9 +34,12 @@ impl ParseError { pub fn new(span: Span, kind: ParseErrorKind) -> Self { Self { span, kind } } - pub fn display(&self, text: &str) -> String { - let SpanDisplay { line, col, .. } = SpanDisplay::highlight_span(self.span, text); - format!("line {}, col {}: {}", line, col, self.kind) + pub fn display(&self, text: &str, file_name: &str) -> String { + let SpanDisplay { line, col, .. } = SpanDisplay::highlight_span(self.span, text, file_name); + format!( + "in file `{}`; line {}, col {}: {}", + file_name, line, col, self.kind + ) } } @@ -82,7 +89,7 @@ impl From for LispError { } } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug)] pub enum EvalError { ArgumentCount(Arity), BadForm, @@ -90,7 +97,10 @@ pub enum EvalError { DivByZero, TypeMismatch, NoFileName, + AccessEmptyList, + ScriptLoadError(io::Error), CustomInternal(&'static str), + Custom(String), } impl fmt::Display for EvalError { @@ -113,7 +123,10 @@ impl fmt::Display for EvalError { Self::TypeMismatch => write!(f, "mismatched types"), 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::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 3a3a61e..44540c0 100644 --- a/src/lisp/eval.rs +++ b/src/lisp/eval.rs @@ -6,6 +6,7 @@ use crate::{ number::LispNumber, Environment, }, + type_match, }; use std::convert::TryInto; @@ -20,7 +21,14 @@ pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result LispExpr::Number(_) => Ok(expr.clone()), LispExpr::BoolLit(_) => Ok(expr.clone()), LispExpr::Ident(ref id) => lookup_extended(&app.lisp_env, id), - LispExpr::Quote(_, _) => Ok(expr.clone()), + 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 { @@ -29,6 +37,7 @@ pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result "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()); @@ -99,7 +108,7 @@ pub fn define_var(args: &[LispExpr], app: &mut AppState) -> Result { // (define (func arg) ) shorthand - let id = unwrap_ident(shorthand[0].clone()); + let id = shorthand[0].unwrap_ident(); let params = if shorthand.len() > 1 { &shorthand[1..] } else { @@ -163,13 +172,12 @@ pub fn create_lambda(cdr: &[LispExpr]) -> Result { } info!("creating lambda"); match cdr { - [LispExpr::List(params), LispExpr::List(body)] - if params.iter().all(|p| matches!(p, LispExpr::Ident(_))) => + [LispExpr::List(params), LispExpr::List(body)] if type_match!(params, (..) => LispExpr::Ident(_)) => { return Ok(LispExpr::Function(LispFunction { params: params .into_iter() - .map(|p| unwrap_ident(p.clone())) + .map(|p| p.unwrap_ident()) .collect::>(), body: body.clone(), })); @@ -202,6 +210,26 @@ pub fn eval_if(args: &[LispExpr], app: &mut AppState) -> Result 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 { if env_list.is_empty() { return Err(EvalError::UnboundVariable(key.into()).into()); @@ -215,20 +243,6 @@ pub fn lookup_extended(env_list: &[Environment], key: &str) -> Result &LispNumber { - match n { - LispExpr::Number(i) => i, - _ => panic!("unwrap_number expected number"), - } -} - -pub fn unwrap_ident(i: LispExpr) -> String { - match i { - LispExpr::Ident(i) => i, - _ => panic!("unwrap_ident expected string"), - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/lisp/expr.rs b/src/lisp/expr.rs index 934bb4a..9f2dc8d 100644 --- a/src/lisp/expr.rs +++ b/src/lisp/expr.rs @@ -117,6 +117,8 @@ impl LispExpr { .iter() .zip(o) .all(|(a, b)| matches!(a.compare(b, envs), Ok(true)))), + (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), @@ -142,6 +144,56 @@ impl LispExpr { _ => Err(EvalError::TypeMismatch.into()), } } + + // have these be code gen'd somehow + pub fn unwrap_number(&self) -> LispNumber { + match &self { + LispExpr::Number(p) => p.clone(), + _ => panic!("attempt to call `unwrap_number` on invalid type"), + } + } + + pub fn unwrap_list(&self) -> Vec { + match &self { + LispExpr::List(p) => p.clone(), + _ => panic!("attempt to call `unwrap_number` on invalid type"), + } + } + + pub fn unwrap_stringlit(&self) -> String { + match &self { + LispExpr::StringLit(p) => p.clone(), + _ => panic!("attempt to call `unwrap_stringlit` on invalid type"), + } + } + + pub fn unwrap_ident(&self) -> String { + match &self { + LispExpr::Ident(p) => p.clone(), + _ => panic!("attempt to call `unwrap_ident` on invalid type"), + } + } + + pub fn unwrap_primitive_func(&self) -> PrimitiveFunc { + match &self { + LispExpr::PrimitiveFunc(p) => p.clone(), + _ => panic!("attempt to call `unwrap_primitive_func` on invalid type"), + } + } + + pub fn unwrap_function(&self) -> LispFunction { + match &self { + LispExpr::Function(p) => p.clone(), + _ => panic!("attempt to call `unwrap_function` on invalid type"), + } + } + + pub fn cast_bool(&self) -> bool { + match &self { + LispExpr::BoolLit(false) => false, + _ => true, + } + } } impl fmt::Display for LispExpr { @@ -270,11 +322,3 @@ impl TryFrom for BoolLit { } } } - -pub fn is_ident>(expr: E) -> bool { - matches!(expr.as_ref(), LispExpr::Ident(_)) -} - -pub fn is_number>(expr: E) -> bool { - matches!(expr.as_ref(), LispExpr::Number(_)) -} diff --git a/src/lisp/lex.rs b/src/lisp/lex.rs index e514d7f..5d3030b 100644 --- a/src/lisp/lex.rs +++ b/src/lisp/lex.rs @@ -1,6 +1,6 @@ use std::{fmt, str::CharIndices}; -use crate::lisp::error::{LispError, ParseError, ParseErrorKind}; +use crate::lisp::error::{ParseError, ParseErrorKind}; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Token<'a> { @@ -75,21 +75,27 @@ impl Span { } #[derive(Debug, Clone)] -pub struct SpanDisplay<'src> { +pub struct SpanDisplay<'src, 'file> { + pub file_name: &'file str, pub source: &'src str, pub line: usize, pub col: usize, } -impl<'src> SpanDisplay<'src> { - pub fn highlight_span(span: Span, source: &'src str) -> Self { +impl<'src, 'file> SpanDisplay<'src, 'file> { + pub fn highlight_span(span: Span, source: &'src str, file_name: &'file str) -> Self { let line_start = match source[..span.low as usize].rfind('\n') { Some(pos) => pos + 1, None => 0, }; let line = source[..line_start].chars().filter(|&c| c == '\n').count() + 1; let col = source[line_start..span.low as usize].chars().count(); - Self { source, line, col } + Self { + file_name, + source, + line, + col, + } } } @@ -108,7 +114,7 @@ impl<'a> Lexer<'a> { } } - pub fn next_token(&mut self) -> Result<(Span, Token<'a>), LispError> { + pub fn next_token(&mut self) -> Result<(Span, Token<'a>), ParseError> { let mut chars = self.input.char_indices(); while let Some((ind, chr)) = chars.next() { @@ -142,13 +148,13 @@ impl<'a> Lexer<'a> { let (size, token) = match res { Ok(v) => v, Err(kind) => { - return Err(LispError::Parse(ParseError { + return Err(ParseError { span: Span { low, high: low + chr.len_utf8() as u32, }, kind, - })) + }) } }; self.cur_pos += size as u32; diff --git a/src/lisp/prelude.rs b/src/lisp/prelude.rs index d8a930b..dffd9f4 100644 --- a/src/lisp/prelude.rs +++ b/src/lisp/prelude.rs @@ -3,11 +3,12 @@ use crate::{ lisp::{ error::{EvalError, LispError}, eval::eval, - expr::{is_ident, Arity, LispExpr}, + expr::{Arity, LispExpr}, number::LispNumber, Environment, }, primitive, + utils::load_script, }; use std::{convert::TryInto, fs::File, io::Write}; @@ -27,7 +28,7 @@ macro_rules! primitive { #[macro_export] macro_rules! type_match { - ($args:expr, $($range:expr => $kind:pat),+) => { + ($args:expr, $($range:literal => $kind:pat),+) => { { let mut temp_vec = vec![]; $( @@ -35,10 +36,21 @@ macro_rules! type_match { )+ 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() -> Environment { +pub fn new_env() -> Result { let mut env = Environment::new(); primitive!(env, Arity::Atleast(2), "+", |args, _| { @@ -113,7 +125,7 @@ pub fn new_env() -> Environment { }); primitive!(env, Arity::Exact(1), "not", |args, _| { - if matches!(&args[0], LispExpr::BoolLit(false)) { + if type_match!(args, 0 => LispExpr::BoolLit(false)) { Ok(LispExpr::BoolLit(true)) } else { Ok(LispExpr::BoolLit(false)) @@ -121,11 +133,7 @@ pub fn new_env() -> Environment { }); 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()) - } + Ok(args.into_iter().last().unwrap().clone()) }); primitive!(env, Arity::Exact(0), "quit", |_, app| { @@ -136,7 +144,7 @@ pub fn new_env() -> Environment { primitive!(env, Arity::Exact(2), "eq?", |args, app| { let s = &args[0]; let o = &args[1]; - info!("comparing {} {}", s, o); + info!("comparing s: {} and o: {}", s, o); let result = s.compare(o, &app.lisp_env); result.map(LispExpr::BoolLit) }); @@ -188,7 +196,7 @@ pub fn new_env() -> Environment { 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, + args[0].unwrap_stringlit().len() as i64, ))) } else { Err(EvalError::TypeMismatch.into()) @@ -218,7 +226,7 @@ pub fn new_env() -> Environment { 0 }; if let [LispExpr::Quote(kind, _)] = args { - if is_ident(kind) { + if matches!(kind.as_ref(), LispExpr::Ident(_)) { match (&**kind).as_ref() { "fill" => app.brush = Brush::Fill, "circle" => app.brush = Brush::new(old_size), @@ -250,9 +258,79 @@ pub fn new_env() -> Environment { } }); + 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()), + } + } else { + return Err(EvalError::TypeMismatch.into()); + } + }); + + 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())); }); - env + 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()); + } + }); + + Ok(env) } diff --git a/src/lisp/std.lisp b/src/lisp/std.lisp new file mode 100644 index 0000000..c723a13 --- /dev/null +++ b/src/lisp/std.lisp @@ -0,0 +1,26 @@ +(define (caar ls) (car (car ls))) +(define (caaar ls) (car (caar ls))) +(define (caaaar ls) (car (caaar ls))) +(define (caaaaar ls) (car (caaaar ls))) + +(define (cddr ls) (cdr (cdr ls))) +(define (cdddr ls) (cdr (cddr ls))) +(define (cddddr ls) (cdr (cdddr ls))) +(define (cdddddr ls) (cdr (cddddr ls))) + +(define (null? ls) (eq? ls '())) + +(define (length ls) + (if (null? ls) + 0 + (+ 1 (length (cdr ls))))) + +(define (fold init accumulator ls) + (if (null? ls) + init + (fold (accumulator init (car ls)) + accumulator + (cdr ls)))) + +(define (sum ls) (fold 0 + ls)) +(define (product ls) (fold 1 * ls)) -- cgit v1.2.3