From f94f22a3d0aeff98cc9169c94b683aa139e9c81c Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 27 Mar 2021 13:16:25 +0530 Subject: implement char literals --- src/lisp/error.rs | 20 +++++++++++++++++--- src/lisp/eval.rs | 53 ++++++++++++++++++++++++++++++++++++++--------------- src/lisp/lex.rs | 25 +++++++++++++++++++++---- src/lisp/parse.rs | 7 ++++++- 4 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/lisp/error.rs b/src/lisp/error.rs index 4f90d14..b90e211 100644 --- a/src/lisp/error.rs +++ b/src/lisp/error.rs @@ -1,4 +1,7 @@ -use crate::lisp::lex::{Span, SpanDisplay}; +use crate::lisp::{ + expr::Arity, + lex::{Span, SpanDisplay}, +}; use std::fmt; @@ -81,23 +84,34 @@ impl From for LispError { #[derive(Debug, PartialEq, Clone)] pub enum EvalError { - ArgumentCount(Option), // expected + ArgumentCount(Arity), BadForm, UnboundVariable(String), DivByZero, TypeMismatch, + NoFileName, } impl fmt::Display for EvalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ArgumentCount(i) => { - write!(f, "invalid number of arguments, expected atleast {:?}", i) + write!(f, "invalid number of arguments, expected ")?; + match i { + Arity::Exact(a) => write!(f, "exactly {} argument(s)", a), + Arity::Atleast(a) => write!(f, "atleast {} argument(s)", a), + Arity::Atmost(a) => write!(f, "atmost {} argument(s)", a), + Arity::Range(low, high) => { + write!(f, "between {} and {} argument(s)", low, high) + } + Arity::None => write!(f, "any number of arguments"), + } } Self::BadForm => write!(f, "bad expression form"), Self::UnboundVariable(s) => write!(f, "unbound variable {}", s), Self::TypeMismatch => write!(f, "mismatched types"), Self::DivByZero => write!(f, "attempt to divide by zero"), + Self::NoFileName => write!(f, "no file name specified"), } } } diff --git a/src/lisp/eval.rs b/src/lisp/eval.rs index 5accec4..370b624 100644 --- a/src/lisp/eval.rs +++ b/src/lisp/eval.rs @@ -2,7 +2,7 @@ use crate::{ app::AppState, lisp::{ error::{EvalError, LispError}, - expr::{Ident, LispExpr, LispFunction}, + expr::{Arity, Ident, LispExpr, LispFunction}, number::LispNumber, EnvList, Environment, }, @@ -16,6 +16,7 @@ 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), @@ -26,6 +27,7 @@ pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result "define" => define_var(&li[1..], app), "set!" => set_var(&li[1..], app), "lambda" => create_lambda(&li[1..]), + "if" => eval_if(&li[1..], app), _ => { let func_expr = eval(&func_expr, app)?; match func_expr { @@ -37,17 +39,17 @@ pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result f.call(&args, app) } LispExpr::Function(f) => { - info!("eval custom func"); let mut args = Vec::new(); for item in li[1..].iter() { - args.push(eval(item, app)?); + let i = eval(item, app)?; + args.push(i); } if f.params.len() != args.len() { info!("too many or too little number of args"); - Err(EvalError::ArgumentCount(Some(f.params.len() as u32)) + Err(EvalError::ArgumentCount(Arity::Exact(f.params.len())) .into()) } else { - let mut nested_env: Environment = + let nested_env: Environment = f.params.into_iter().zip(args).collect(); app.lisp_env.push(nested_env); let result = if f.body.is_empty() { @@ -71,9 +73,9 @@ pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result } pub fn define_var(args: &[LispExpr], app: &mut AppState) -> Result { - if args.len() != 2 { - error!("Invalid arity for `define`"); - return Err(EvalError::ArgumentCount(Some(2)).into()); + let arity = Arity::Exact(2); + if !arity.is_valid(args) { + return Err(arity.to_error()); } match args { [LispExpr::Ident(id), expr] => { @@ -107,6 +109,7 @@ pub fn define_var(args: &[LispExpr], app: &mut AppState) -> Result Result Result { - if args.len() != 2 { - error!("Invalid arity for `define`"); - return Err(EvalError::ArgumentCount(Some(2)).into()); + let arity = Arity::Exact(2); + if !arity.is_valid(args) { + return Err(arity.to_error()); } match args { [LispExpr::Ident(id), expr] => { @@ -147,10 +150,9 @@ pub fn set_var(args: &[LispExpr], app: &mut AppState) -> Result Result { - if cdr.len() != 2 { - // needs params and body - error!("needs params and body"); - return Err(EvalError::ArgumentCount(Some(2)).into()); + let arity: Arity = Arity::Exact(2); + if !arity.is_valid(cdr) { + return Err(arity.to_error()); } info!("creating lambda"); match cdr { @@ -172,6 +174,27 @@ 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 lookup_extended(env_list: &[Environment], key: &str) -> Result { if env_list.is_empty() { return Err(EvalError::UnboundVariable(key.into()).into()); diff --git a/src/lisp/lex.rs b/src/lisp/lex.rs index b307e80..1a34e53 100644 --- a/src/lisp/lex.rs +++ b/src/lisp/lex.rs @@ -8,7 +8,7 @@ pub enum Token<'a> { RightParen, Float(&'a str), Integer(&'a str), - // Char(&'a str), + Char(&'a str), String(&'a str), Name(&'a str), // Keyword(&'a str), @@ -26,7 +26,7 @@ impl<'a> Token<'a> { Token::RightParen => ")", Token::Float(_) => "float", Token::Integer(_) => "integer", - // Token::Char(_) => "char", + Token::Char(_) => "char", Token::String(_) => "string", Token::Name(_) => "name", // Token::Keyword(_) => "keyword", @@ -46,7 +46,7 @@ impl<'a> fmt::Display for Token<'a> { Token::RightParen => write!(f, ")"), Token::Float(_) => write!(f, "float"), Token::Integer(_) => write!(f, "integer"), - // Token::Char(_) => write!(f, "char"), + Token::Char(_) => write!(f, "char"), Token::String(_) => write!(f, "string"), Token::Name(_) => write!(f, "name"), // Token::Keyword(_) => write!(f, "keyword"), @@ -122,7 +122,10 @@ impl<'a> Lexer<'a> { Some((_, '@')) => Ok((2, Token::CommaAt)), _ => Ok((1, Token::Comma)), }, - '#' => parse_name(&self.input[ind..]), + '#' => match chars.next() { + Some((_, '\\')) => parse_char(&self.input[ind..]), + _ => parse_name(&self.input[ind..]), + }, '-' | '0'..='9' => parse_number(&self.input[ind..]), '"' => parse_string(&self.input[ind..]), ';' => { @@ -254,6 +257,12 @@ fn parse_name<'a>(input: &'a str) -> Result<(usize, Token<'a>), ParseErrorKind> return Ok((input.len(), Token::Name(input))); } +fn parse_char<'a>(input: &'a str) -> Result<(usize, Token<'a>), ParseErrorKind> { + // first two chars of input are '#' and '\' + let chr = &input[..3]; + return Ok((chr.len(), Token::Char(chr))); +} + #[cfg(test)] mod tests { use super::*; @@ -282,6 +291,14 @@ mod tests { assert_eq!(parsed.1, Token::String(r#""hello there""#)); } + #[test] + fn char_parsing() { + let input = r##"#\a"##; + let parsed = parse_char(input).unwrap(); + assert_eq!(parsed.0, 3); + assert_eq!(parsed.1, Token::Char(r##"#\a"##)); + } + #[test] fn integer_parsing() { let input = "12345"; diff --git a/src/lisp/parse.rs b/src/lisp/parse.rs index 4e0f427..7cca434 100644 --- a/src/lisp/parse.rs +++ b/src/lisp/parse.rs @@ -58,7 +58,12 @@ impl<'lex> Parser<'lex> { .parse::() .map(|n| LispExpr::Number(LispNumber::Integer(n))) .map_err(|_| ParseError::new(span, ParseErrorKind::LiteralParseError).into()), - Token::String(s) => Ok(LispExpr::StringLit(s.into())), + Token::String(s) => Ok(LispExpr::StringLit(s[1..s.len() - 1].into())), + Token::Char(s) => Ok(LispExpr::Char(s.chars().nth(2).ok_or_else( + || -> LispError { + ParseError::new(span, ParseErrorKind::LiteralParseError).into() + }, + )?)), Token::Name(n) => Ok(name_expr(n)), Token::BackQuote => { total_backticks += 1; -- cgit v1.2.3