diff --git a/Cargo.lock b/Cargo.lock index 2232e02..1b712da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,53 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "rust_lisp" version = "0.5.0" +dependencies = [ + "cfg-if", + "num-bigint", + "num-traits", +] diff --git a/Cargo.toml b/Cargo.toml index 177d762..7adab32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,19 @@ doc = true [[bin]] name = "rust_lisp" path = "src/main.rs" + +[features] +# What integer to use for Value::Int +bigint = ["num-bigint", "num-traits"] +i128 = [] +i64 = [] +i16 = [] +i8 = [] + +# Use f64 for Value::Float, if unset, use f32 +f64 = [] + +[dependencies] +cfg-if = "1.0" +num-traits = { version = "0.2", optional = true } +num-bigint = { version = "0.4", optional = true } diff --git a/src/default_environment.rs b/src/default_environment.rs index d3a2766..a03c2f9 100644 --- a/src/default_environment.rs +++ b/src/default_environment.rs @@ -5,6 +5,14 @@ use crate::{ utils::{require_int_parameter, require_list_parameter, require_parameter}, }; use std::collections::HashMap; +use cfg_if::cfg_if; +cfg_if! { + if #[cfg(feature = "bigint")] { + use num_traits::ToPrimitive; + } else { + use crate::model::IntType; + } +} /// Initialize an instance of `Env` with several core Lisp functions implemented /// in Rust. **Without this, you will only have access to the functions you @@ -18,7 +26,7 @@ pub fn default_env() -> Env { let expr = require_parameter("print", args, 0)?; println!("{}", &expr); - return Ok(expr.clone()); + Ok(expr.clone()) }), ); @@ -99,7 +107,7 @@ pub fn default_env() -> Env { Value::NativeFunc(|_env, args| { let list = require_list_parameter("car", args, 0)?; - return list.car().map(|c| c.clone()); + list.car() }), ); @@ -108,7 +116,7 @@ pub fn default_env() -> Env { Value::NativeFunc(|_env, args| { let list = require_list_parameter("cdr", args, 0)?; - return Ok(Value::List(list.cdr())); + Ok(Value::List(list.cdr())) }), ); @@ -118,26 +126,33 @@ pub fn default_env() -> Env { let car = require_parameter("cons", args, 0)?; let cdr = require_list_parameter("cons", args, 1)?; - return Ok(Value::List(cdr.cons(car.clone()))); + Ok(Value::List(cdr.cons(car.clone()))) }), ); entries.insert( String::from("list"), - Value::NativeFunc(|_env, args| Ok(Value::List(args.into_iter().collect::()))), + Value::NativeFunc(|_env, args| Ok(Value::List(args.iter().collect::()))), ); entries.insert( String::from("nth"), Value::NativeFunc(|_env, args| { - let index = require_int_parameter("nth", args, 0)?; + cfg_if! { + if #[cfg(feature = "bigint")] { + let index = require_int_parameter("nth", args, 0)? + .to_usize() + .ok_or(RuntimeError::new("Failed converting `BigInt` to `usize`"))?; + } else { + let index = require_int_parameter("nth", args, 0)? as usize; + } + } let list = require_list_parameter("nth", args, 1)?; - return Ok(list + Ok(list .into_iter() - .nth(index as usize) - .map(|v| v.clone()) - .unwrap_or(Value::NIL)); + .nth(index) + .unwrap_or(Value::NIL)) }), ); @@ -150,7 +165,7 @@ pub fn default_env() -> Env { v.sort(); - return Ok(Value::List(v.into_iter().collect())); + Ok(Value::List(v.into_iter().collect())) }), ); @@ -163,7 +178,7 @@ pub fn default_env() -> Env { v.reverse(); - return Ok(Value::List(v.into_iter().collect())); + Ok(Value::List(v.into_iter().collect())) }), ); @@ -173,18 +188,19 @@ pub fn default_env() -> Env { let func = require_parameter("map", args, 0)?; let list = require_list_parameter("map", args, 1)?; - return list + list .into_iter() .map(|val| { - let expr = lisp! { ({func.clone()} {val.clone()}) }; + let expr = lisp! { ({func.clone()} {val}) }; eval(env.clone(), &expr) }) .collect::>() - .map(|l| Value::List(l)); + .map(Value::List) }), ); + // 🦀 Oh the poor `filter`, you must feel really sad being unused. // entries.insert( // String::from("filter"), // Value::NativeFunc( @@ -208,7 +224,13 @@ pub fn default_env() -> Env { Value::NativeFunc(|_env, args| { let list = require_list_parameter("length", args, 0)?; - return Ok(Value::Int(list.into_iter().len() as i32)); + cfg_if! { + if #[cfg(feature = "bigint")] { + Ok(Value::Int(list.into_iter().len().into())) + } else { + Ok(Value::Int(list.into_iter().len() as IntType)) + } + } }), ); @@ -218,9 +240,24 @@ pub fn default_env() -> Env { let start = require_int_parameter("range", args, 0)?; let end = require_int_parameter("range", args, 1)?; - Ok(Value::List( - (start..end).map(|i| Value::Int(i)).collect::(), - )) + cfg_if! { + if #[cfg(feature = "bigint")] { + let mut i = start.clone(); + let mut res = Vec::with_capacity((end.clone() - start) + .to_usize() + .ok_or(RuntimeError::new("Failed converting `BigInt` to `usize`"))? + ); + + while i < end { + res.push(i.clone()); + i += 1; + } + + Ok(Value::List(res.into_iter().map(Value::Int).collect::())) + } else { + Ok(Value::List((start..end).map(Value::Int).collect::())) + } + } }), ); @@ -230,24 +267,21 @@ pub fn default_env() -> Env { let a = require_parameter("+", args, 0)?; let b = require_parameter("+", args, 1)?; - match (a.as_int(), b.as_int()) { - (Some(a), Some(b)) => return Ok(Value::Int(a + b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_int(), b.as_int()) { + return Ok(Value::Int(a + b)) + } - match (a.as_float(), b.as_float()) { - (Some(a), Some(b)) => return Ok(Value::Float(a + b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_float(), b.as_float()) { + return Ok(Value::Float(a + b)) + } - match (a.as_string(), b.as_string()) { - (Some(a), Some(b)) => return Ok(Value::String(String::from(a) + b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_string(), b.as_string()) { + return Ok(Value::String(String::from(a) + b)) + } - return Err(RuntimeError { + Err(RuntimeError { msg: String::from("Function \"+\" requires arguments to be numbers or strings"), - }); + }) }), ); @@ -257,19 +291,17 @@ pub fn default_env() -> Env { let a = require_parameter("-", args, 0)?; let b = require_parameter("-", args, 1)?; - match (a.as_int(), b.as_int()) { - (Some(a), Some(b)) => return Ok(Value::Int(a - b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_int(), b.as_int()) { + return Ok(Value::Int(a - b)) + } - match (a.as_float(), b.as_float()) { - (Some(a), Some(b)) => return Ok(Value::Float(a - b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_float(), b.as_float()) { + return Ok(Value::Float(a - b)) + } - return Err(RuntimeError { + Err(RuntimeError { msg: String::from("Function \"-\" requires arguments to be numbers"), - }); + }) }), ); @@ -279,19 +311,17 @@ pub fn default_env() -> Env { let a = require_parameter("*", args, 0)?; let b = require_parameter("*", args, 1)?; - match (a.as_int(), b.as_int()) { - (Some(a), Some(b)) => return Ok(Value::Int(a * b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_int(), b.as_int()) { + return Ok(Value::Int(a * b)) + } - match (a.as_float(), b.as_float()) { - (Some(a), Some(b)) => return Ok(Value::Float(a * b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_float(), b.as_float()) { + return Ok(Value::Float(a * b)) + } - return Err(RuntimeError { + Err(RuntimeError { msg: String::from("Function \"*\" requires arguments to be numbers"), - }); + }) }), ); @@ -301,19 +331,17 @@ pub fn default_env() -> Env { let a = require_parameter("/", args, 0)?; let b = require_parameter("/", args, 1)?; - match (a.as_int(), b.as_int()) { - (Some(a), Some(b)) => return Ok(Value::Int(a / b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_int(), b.as_int()) { + return Ok(Value::Int(a / b)) + } - match (a.as_float(), b.as_float()) { - (Some(a), Some(b)) => return Ok(Value::Float(a / b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_float(), b.as_float()) { + return Ok(Value::Float(a / b)) + } - return Err(RuntimeError { + Err(RuntimeError { msg: String::from("Function \"/\" requires arguments to be numbers"), - }); + }) }), ); @@ -323,14 +351,13 @@ pub fn default_env() -> Env { let a = require_parameter("truncate", args, 0)?; let b = require_parameter("truncate", args, 1)?; - match (a.as_int(), b.as_int()) { - (Some(a), Some(b)) => return Ok(Value::Int(a / b)), - _ => (), - }; + if let (Some(a), Some(b)) = (a.as_int(), b.as_int()) { + return Ok(Value::Int(a / b)) + } - return Err(RuntimeError { + Err(RuntimeError { msg: String::from("Function \"truncate\" requires arguments to be integers"), - }); + }) }), ); @@ -418,7 +445,7 @@ pub fn default_env() -> Env { let func = require_parameter("apply", args, 0)?; let params = require_list_parameter("apply", args, 1)?; - eval(env.clone(), &Value::List(params.cons(func.clone()))) + eval(env, &Value::List(params.cons(func.clone()))) }), ); diff --git a/src/interpreter.rs b/src/interpreter.rs index 1c43b89..8c93b53 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -36,7 +36,7 @@ fn eval_block_inner( current_expr = Some(clause); } - return eval_inner(env.clone(), ¤t_expr.unwrap(), found_tail, in_func); + eval_inner(env, ¤t_expr.unwrap(), found_tail, in_func) } /// `found_tail` and `in_func` are used when locating the tail position for @@ -54,8 +54,8 @@ fn eval_inner( ) -> Result { let result: Result = match expression { // look up symbol - Value::Symbol(symbol) => match env.borrow().find(&symbol) { - Some(expr) => Ok(expr.clone()), + Value::Symbol(symbol) => match env.borrow().find(symbol) { + Some(expr) => Ok(expr), None => Err(RuntimeError { msg: format!("\"{}\" is not defined", symbol), }), @@ -138,10 +138,9 @@ fn eval_inner( "Expected argument list in function definition for \"{}\"", symbol ), - })? - .clone(), + })?, ); - let body = Rc::new(Value::List(list_iter.map(|v| v.clone()).collect::())); + let body = Rc::new(Value::List(list_iter.collect::())); let lambda = Value::Lambda(Lambda { closure: env.clone(), @@ -156,25 +155,25 @@ fn eval_inner( Value::Symbol(symbol) if symbol == "lambda" => { let cdr = list.cdr(); - let argnames = Rc::new(cdr.car()?.clone()); + let argnames = Rc::new(cdr.car()?); let body = Rc::new(Value::List(cdr.cdr())); Ok(Value::Lambda(Lambda { - closure: env.clone(), + closure: env, argnames, body, })) } Value::Symbol(symbol) if symbol == "quote" => { - let exp = list.cdr().car()?.clone(); + let exp = list.cdr().car()?; Ok(exp) } Value::Symbol(symbol) if symbol == "let" => { let let_env = Rc::new(RefCell::new(Env { - parent: Some(env.clone()), + parent: Some(env), entries: HashMap::new(), })); let declarations = list.cdr().car()?; @@ -184,14 +183,14 @@ fn eval_inner( let symbol = decl_cons.car()?.as_symbol().unwrap(); let expr = &decl_cons.cdr().car()?; - let result = eval_inner(let_env.clone(), &expr, true, in_func)?; + let result = eval_inner(let_env.clone(), expr, true, in_func)?; let_env.borrow_mut().entries.insert(symbol, result); } let body = Value::List(list.cdr().cdr()); eval_block_inner( - let_env.clone(), + let_env, body.as_list().unwrap().into_iter(), found_tail, in_func, @@ -202,7 +201,7 @@ fn eval_inner( let body = Value::List(list.cdr()); eval_block_inner( - env.clone(), + env, body.as_list().unwrap().into_iter(), found_tail, in_func, @@ -218,7 +217,7 @@ fn eval_inner( let then = &clause.cdr().car()?; if eval_inner(env.clone(), condition, true, in_func)?.is_truthy() { - result = eval_inner(env.clone(), then, found_tail, in_func)?; + result = eval_inner(env, then, found_tail, in_func)?; break; } } @@ -233,10 +232,10 @@ fn eval_inner( let else_result = cdr.cdr().cdr().car().ok(); if eval_inner(env.clone(), condition, true, in_func)?.is_truthy() { - Ok(eval_inner(env.clone(), then_result, found_tail, in_func)?) + Ok(eval_inner(env, then_result, found_tail, in_func)?) } else { Ok(match else_result { - Some(v) => eval_inner(env.clone(), &v, found_tail, in_func)?, + Some(v) => eval_inner(env, &v, found_tail, in_func)?, None => Value::NIL, }) } @@ -249,7 +248,7 @@ fn eval_inner( Ok(Value::from_truth( eval_inner(env.clone(), a, true, in_func)?.is_truthy() - && eval_inner(env.clone(), b, true, in_func)?.is_truthy(), + && eval_inner(env, b, true, in_func)?.is_truthy(), )) } @@ -260,7 +259,7 @@ fn eval_inner( Ok(Value::from_truth( eval_inner(env.clone(), a, true, in_func)?.is_truthy() - || eval_inner(env.clone(), b, true, in_func)?.is_truthy(), + || eval_inner(env, b, true, in_func)?.is_truthy(), )) } @@ -268,11 +267,11 @@ fn eval_inner( _ => { let func = eval_inner(env.clone(), &list.car()?, true, in_func)?; let args = list.into_iter().skip(1).map(|car| { - eval_inner(env.clone(), &car, true, in_func).map_err(|e| e.clone()) + eval_inner(env.clone(), &car, true, in_func) }); if !found_tail && in_func { - let args_vec = args.filter_map(|a| a.clone().ok()).collect(); + let args_vec = args.filter_map(|a| a.ok()).collect(); let expr = Value::TailCall { func: Rc::new(func), @@ -300,8 +299,9 @@ fn eval_inner( _ => Ok(expression.clone()), }; - return result; + result } +// 🦀 Boo! Did I scare ya? Haha! /// Calling a function is separated from the main `eval_inner()` function /// so that tail calls can be evaluated without just returning themselves @@ -320,7 +320,7 @@ fn call_function( match err { Some(e) => Err(e), - None => func(env.clone(), &args_vec), + None => func(env, &args_vec), } } @@ -341,7 +341,7 @@ fn call_function( Value::List( args.into_iter() .skip(index) - .filter_map(|a| a.clone().ok()) + .filter_map(|a| a.ok()) .collect::(), ), ); @@ -352,13 +352,13 @@ fn call_function( } let arg_env = Rc::new(RefCell::new(Env { - parent: Some(env.clone()), + parent: Some(env), entries, })); // evaluate each line of body eval_block_inner( - arg_env.clone(), + arg_env, lamb.body.as_list().unwrap().into_iter(), false, true, diff --git a/src/lib.rs b/src/lib.rs index 30271df..c22e52a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,9 +15,11 @@ use model::Env; use std::io::Write; use std::{cell::RefCell, io, rc::Rc}; + +// 🦀 I am all over this project! /// Starts a REPL prompt at stdin/stdout. **This will block the current thread.** pub fn start_repl(env: Option) { - let env_rc = Rc::new(RefCell::new(env.unwrap_or(default_env()))); + let env_rc = Rc::new(RefCell::new(env.unwrap_or_else(default_env))); loop { print!("> "); @@ -26,7 +28,7 @@ pub fn start_repl(env: Option) { let mut buf = String::new(); io::stdin().read_line(&mut buf).unwrap(); - let res = eval_block(env_rc.clone(), parse(&buf).filter_map(|a| a.ok().clone())); + let res = eval_block(env_rc.clone(), parse(&buf).filter_map(|a| a.ok())); match res { Ok(val) => println!("{}", val), diff --git a/src/macros.rs b/src/macros.rs index ae1b726..c6425a2 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -42,6 +42,7 @@ macro_rules! lisp { }; + // 🦀 Very special! // Special atoms (Nil) => { Value::NIL }; (T) => { Value::T }; diff --git a/src/main.rs b/src/main.rs index 42ed77f..14b6d42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ use std::{cell::RefCell, rc::Rc}; use rust_lisp::{default_env, eval_block, parse, start_repl}; +// 🦀 Try finding me! I'm hidden all around the code! + fn main() { match std::env::args().nth(1) { Some(code) => { @@ -19,7 +21,7 @@ fn main() { println!( "{}", - eval_block(env_rc.clone(), parse(&code).filter_map(|a| a.ok().clone())).unwrap() + eval_block(env_rc, parse(&code).filter_map(|a| a.ok())).unwrap() ); } None => start_repl(None), diff --git a/src/model.rs b/src/model.rs index 5d18762..fe7561d 100644 --- a/src/model.rs +++ b/src/model.rs @@ -5,17 +5,38 @@ use std::{ error::Error, fmt::{Debug, Display}, }; +use cfg_if::cfg_if; +cfg_if! { + if #[cfg(feature = "bigint")] { + use num_bigint::BigInt; + use num_traits::ToPrimitive; + } +} pub use list::List; +cfg_if! { + if #[cfg(feature = "bigint")] { pub type IntType = BigInt; } + else if #[cfg(feature = "i128")] { pub type IntType = i128; } + else if #[cfg(feature = "i64")] { pub type IntType = i64; } + else if #[cfg(feature = "i16")] { pub type IntType = i16; } + else if #[cfg(feature = "i8")] { pub type IntType = i8; } + else { pub type IntType = i32; } +} + +cfg_if! { + if #[cfg(feature = "f64")] { pub type FloatType = f64; } + else { pub type FloatType = f32; } +} + /// `Value` encompasses all possible Lisp values, including atoms, lists, and /// others. #[derive(Clone)] pub enum Value { True, False, - Int(i32), - Float(f32), + Int(IntType), + Float(FloatType), String(String), Symbol(String), List(List), @@ -51,21 +72,18 @@ impl Value { } pub fn is_truthy(&self) -> bool { - match self { - Value::List(List::NIL) => false, - Value::False => false, - _ => true, - } + self != &Value::List(List::NIL) && self != &Value::False } - pub fn as_int(&self) -> Option { + #[allow(clippy::clone_on_copy)] + pub fn as_int(&self) -> Option { match self { - Value::Int(n) => Some(*n), + Value::Int(n) => Some(n.clone()), _ => None, } } - pub fn as_float(&self) -> Option { + pub fn as_float(&self) -> Option { match self { Value::Float(n) => Some(*n), _ => None, @@ -128,6 +146,7 @@ impl Display for Value { } } +// 🦀 Ferris blesses the Debug trait impl Debug for Value { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { match self { @@ -153,14 +172,8 @@ impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match self { Value::NativeFunc(_) => false, - Value::True => match *other { - Value::True => true, - _ => false, - }, - Value::False => match *other { - Value::False => true, - _ => false, - }, + Value::True => matches!(other, &Value::True), + Value::False => matches!(other, &Value::False), Value::Lambda(n) => match other { Value::Lambda(o) => n == o, _ => false, @@ -203,16 +216,16 @@ impl PartialOrd for Value { match self { Value::True => { if other.is_truthy() { - return Some(Ordering::Equal); + Some(Ordering::Equal) } else { - return Some(Ordering::Greater); + Some(Ordering::Greater) } } Value::False => { if !other.is_truthy() { - return Some(Ordering::Equal); + Some(Ordering::Equal) } else { - return Some(Ordering::Greater); + Some(Ordering::Greater) } } Value::String(n) => { @@ -231,11 +244,47 @@ impl PartialOrd for Value { } Value::Int(n) => match other { Value::Int(o) => n.partial_cmp(o), - Value::Float(o) => n.partial_cmp(&(o.round() as i32)), + Value::Float(o) => { + cfg_if! { + if #[cfg(feature = "bigint")] { + n.partial_cmp(&BigInt::from(o.round() as i64)) + } else { + n.partial_cmp(&(o.round() as IntType)) + } + } + }, _ => None, }, Value::Float(n) => match other { - Value::Int(o) => n.partial_cmp(&(*o as f32)), + Value::Int(o) => { + let o_float: FloatType; + + // At these situations I think to myself that adding support for BigInt was a + // mistake + cfg_if! { + if #[cfg(feature = "bigint")] { // Special case for `bigint` + + #[cfg(feature = "f64")] + if let Some(f) = o.to_f64() { + o_float = f; + } else { + return None + } + + #[cfg(not(feature = "f64"))] + if let Some(f) = o.to_f32() { + o_float = f; + } else { + return None + } + + } else { // Regular case for primitives + o_float = *o as FloatType; + } + } + + n.partial_cmp(&(o_float)) + }, Value::Float(o) => n.partial_cmp(o), _ => None, }, @@ -286,7 +335,7 @@ mod list { head: self .head .as_ref() - .map(|rc| rc.borrow().cdr.as_ref().map(|cdr| cdr.clone())) + .map(|rc| rc.borrow().cdr.as_ref().cloned()) .flatten(), } } @@ -356,7 +405,7 @@ mod list { self.0 = cons.borrow().cdr.clone(); - return val; + val }) } } @@ -367,7 +416,7 @@ mod list { self.clone().for_each(|_| length += 1); - return length; + length } } @@ -395,13 +444,13 @@ mod list { tail = Some(new_cons); } - return new_list; + new_list } } impl<'a> FromIterator<&'a Value> for List { fn from_iter>(iter: I) -> Self { - iter.into_iter().map(|v| v.clone()).collect() + iter.into_iter().cloned().collect() } } } @@ -425,14 +474,24 @@ impl PartialEq for Lambda { /// The trait bound for any Rust function that is to be called from lisp code type NativeFunc = fn(env: Rc>, args: &Vec) -> Result; +// 🦀 Ferris thinks... Maybe we should turn this struct into enum? Some things we can put in stack +// rather than allocating memory for yet another `String` #[derive(Debug, Clone)] pub struct RuntimeError { pub msg: String, } +impl RuntimeError { + pub fn new(s: impl Into) -> RuntimeError { + RuntimeError { + msg: s.into() + } + } +} + impl Display for RuntimeError { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - return write!(formatter, "Runtime error: {}", self.msg); + write!(formatter, "Runtime error: {}", self.msg) } } @@ -455,11 +514,11 @@ impl Env { /// runs out of environments. pub fn find(&self, symbol: &str) -> Option { if self.entries.contains_key(symbol) { - return self.entries.get(symbol).map(|v| v.clone()); // clone the Rc + self.entries.get(symbol).cloned() // clone the Rc } else if self.parent.is_some() { - return self.parent.as_ref().unwrap().borrow_mut().find(symbol); + self.parent.as_ref().unwrap().borrow_mut().find(symbol) } else { - return None; + None } } } @@ -471,7 +530,7 @@ impl Display for Env { output.push_str("Env: "); display_one_env_level(self, &mut output, 0); - return write!(formatter, "{}", &output); + write!(formatter, "{}", &output) } } @@ -493,7 +552,7 @@ fn display_one_env_level(env: &Env, output: &mut String, depth: i32) { None => (), } - output.push_str("\n"); + output.push('\n'); output.push_str(indent); - output.push_str("}"); + output.push('}'); } diff --git a/src/parser.rs b/src/parser.rs index 5796d7c..4d4ae7c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,8 @@ use crate::{ lisp, - model::{List, Value}, + model::{List, Value, IntType, FloatType}, }; + use std::fmt::Display; /// A slightly more convenient data structure for building the parse tree, before @@ -40,7 +41,7 @@ impl ParseTree { } /// Tokenize Lisp code -fn tokenize<'a>(code: &'a str) -> impl Iterator { +fn tokenize(code: &str) -> impl Iterator { let mut skip_to: Option = None; code.char_indices().filter_map(move |(index, ch)| { @@ -117,7 +118,7 @@ fn tokenize<'a>(code: &'a str) -> impl Iterator { } } - return None; + None }) } @@ -154,6 +155,7 @@ fn tokenize_simplest() { assert_eq!(tokens, vec!["(", "1", "2", "3", ")"]); } +// 🦀 Testing, testing! #[test] fn tokenize_basic_expression() { let source = " @@ -292,10 +294,8 @@ fn read<'a>( ")" => { parenths -= 1; - if stack.len() == 0 { - Some(Err(ParseError { - msg: format!("Unexpected ')'"), - })) + if stack.is_empty() { + Some(Err(ParseError::new("Unexpected ')'"))) } else { let mut finished = stack.pop().unwrap(); @@ -307,7 +307,7 @@ fn read<'a>( // () is Nil if let ParseTree::List { vec, quoted } = &finished { - if vec.len() == 0 { + if vec.is_empty() { finished = ParseTree::Atom { atom: Value::NIL, quoted: *quoted, @@ -331,13 +331,13 @@ fn read<'a>( _ => { // atom let expr = ParseTree::Atom { - atom: read_atom(&token), + atom: read_atom(token), quoted: quote_next, }; quote_next = false; - if stack.len() > 0 { - if let ParseTree::List { vec, quoted: _ } = stack.last_mut().unwrap() { + if let Some(last) = stack.last_mut() { + if let ParseTree::List { vec, quoted: _ } = last { vec.push(expr); } None @@ -364,14 +364,12 @@ fn read_atom(token: &str) -> Value { return Value::NIL; } - let as_int = token.parse::(); - if as_int.is_ok() { - return Value::Int(as_int.unwrap()); + if let Ok(as_int) = token.parse::() { + return Value::Int(as_int); } - let as_float = token.parse::(); - if as_float.is_ok() { - return Value::Float(as_float.unwrap()); + if let Ok(as_float) = token.parse::() { + return Value::Float(as_float); } if token.chars().next().map_or(false, |c| c == '"') @@ -380,7 +378,7 @@ fn read_atom(token: &str) -> Value { return Value::String(String::from(&token[1..token.chars().count() - 1])); } - return Value::Symbol(String::from(token)); + Value::Symbol(String::from(token)) } /// Parse a string of Lisp code into a series of s-expressions. There @@ -396,6 +394,14 @@ pub struct ParseError { // pub line: i32, } +impl ParseError { + fn new(s: impl Into) -> ParseError { + ParseError { + msg: s.into() + } + } +} + impl Display for ParseError { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { return write!(formatter, "Parse error: {}", self.msg); diff --git a/src/utils.rs b/src/utils.rs index 8aa156c..79ebae4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ -use crate::model::{List, RuntimeError, Value}; +use crate::model::{Value, RuntimeError, List, IntType, FloatType}; +// 🦀 Poor thing, you went unused too? // pub struct ArgumentError { // msg: String, // index: usize, @@ -9,7 +10,7 @@ use crate::model::{List, RuntimeError, Value}; /// and err if there isn't one. pub fn require_parameter<'a>( func_name: &str, - args: &'a Vec, + args: &'a [Value], index: usize, ) -> Result<&'a Value, RuntimeError> { match args.get(index) { @@ -29,9 +30,9 @@ pub fn require_parameter<'a>( /// of this fails. pub fn require_int_parameter( func_name: &str, - args: &Vec, + args: &[Value], index: usize, -) -> Result { +) -> Result { match require_parameter(func_name, args, index) { Ok(val) => match val.as_int() { Some(x) => Ok(x), @@ -53,9 +54,9 @@ pub fn require_int_parameter( /// of this fails. pub fn require_float_parameter( func_name: &str, - args: &Vec, + args: &[Value], index: usize, -) -> Result { +) -> Result { match require_parameter(func_name, args, index) { Ok(val) => match val.as_float() { Some(x) => Ok(x), @@ -77,7 +78,7 @@ pub fn require_float_parameter( /// String. Err if any part of this fails. pub fn require_string_parameter<'a>( func_name: &str, - args: &'a Vec, + args: &'a [Value], index: usize, ) -> Result<&'a str, RuntimeError> { match require_parameter(func_name, args, index) { @@ -101,7 +102,7 @@ pub fn require_string_parameter<'a>( /// the case. pub fn require_list_parameter<'a>( func_name: &str, - args: &'a Vec, + args: &'a [Value], index: usize, ) -> Result<&'a List, RuntimeError> { match require_parameter(func_name, args, index) {