diff --git a/core/core.mal b/core/core.mal index 7337dd7..36f0e3a 100644 --- a/core/core.mal +++ b/core/core.mal @@ -36,8 +36,8 @@ (>= b a))) ; Other functions in core.rs -(def! int? (fn* [a] - (= (type a) :int))) +(def! num? (fn* [a] + (= (type a) :number))) (def! sym? (fn* [a] (= (type a) :symbol))) diff --git a/src/core.rs b/src/core.rs index 7d300b4..7202b20 100644 --- a/src/core.rs +++ b/src/core.rs @@ -38,8 +38,11 @@ macro_rules! env_init { use crate::parse_tools::read_file; use crate::printer::{pr_str, prt}; use crate::reader::{read_str, Reader}; -use crate::types::MalType::{Atom, Fun, Int, List, Nil, Str}; use crate::types::{mal_equals, reset_bang, MalErr}; +use crate::types::{ + Frac, + MalType::{Atom, Fun, List, Nil, Num, Str}, +}; macro_rules! if_atom { ($val:expr) => {{ @@ -72,10 +75,14 @@ pub fn ns_init() -> Env { "println" => Fun(|a| {a.iter().for_each(|a| print!("{}", pr_str(a, false))); println!(); Ok(Nil) }, "Print readably all the arguments"), "list" => Fun(|a| Ok(List(a.into())), "Return the arguments as a list"), "type" => Fun(|a| Ok(car(a)?.label_type()), "Returns a label indicating the type of it's argument"), - "count" => Fun(|a| Ok(Int(car(a)?.if_list()?.len() as isize)), "Return the number of elements in the first argument"), + "count" => Fun(|a| Ok(Num(Frac::num(car(a)?.if_list()?.len() as isize))), "Return the number of elements in the first argument"), "=" => Fun(mal_equals, "Return true if the first two parameters are the same type and content, in case of lists propagate to all elements (NOT IMPLEMENTED for 'Map', 'Fun' and 'MalFun')"), "car" => Fun(|a| mal_car(car(a)?), "Returns the first element of the list, NIL if its empty"), "cdr" => Fun(|a| mal_cdr(car(a)?), "Returns all the list but the first element"), + // Number functions, still to decide how to handle + "num" => Fun(|a| Ok(Num(Frac::num(car(a)?.if_number()?.get_num()))), "Get numerator of the number"), + "den" => Fun(|a| Ok(Num(Frac::num(car(a)?.if_number()?.get_den() as isize))), "Get denominator of the number"), + "floor" => Fun(|a| Ok(Num(Frac::num(car(a)?.if_number()?.int()))), "Approximate the number to the closest smaller integer"), // A tribute to PHP's explode (PHP, a language I never used) "boom" => Fun(mal_boom, "Split a string into a list of characters\n; BE CAREFUL WHEN USING"), "read-string" => Fun(|a| read_str(Reader::new().push(car(a)?.if_string()?)).map_err(MalErr::severe), "Tokenize and read the first argument"), diff --git a/src/env.rs b/src/env.rs index 65a12e8..507660a 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,8 +1,9 @@ use crate::eval::eval; use crate::types::MalErr; -use crate::types::{MalMap, MalRet, MalType}; +use crate::types::{Frac, MalMap, MalRet, MalType}; use std::cell::RefCell; use std::rc::Rc; +use std::usize; #[derive(Clone)] pub struct EnvType { @@ -132,9 +133,9 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> CallRet { return Err(MalErr::unrecoverable("No key provided to Vector construct")); } match &args[0] { - M::Int(i) => { - if { 0..v.len() as isize }.contains(i) { - Ok(CallFunc::Builtin(v[*i as usize].clone())) + M::Num(i) => { + if { 0..v.len() as isize }.contains(&i.int()) { + Ok(CallFunc::Builtin(v[i.int() as usize].clone())) } else { Ok(CallFunc::Builtin(M::Nil)) } @@ -149,29 +150,41 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> CallRet { } pub fn any_zero(list: &[MalType]) -> Result<&[MalType], MalErr> { - if list.iter().any(|x| matches!(x, M::Int(0))) { + if list + .iter() + .any(|x| matches!(x, M::Num(v) if v.exact_zero())) + { return Err(MalErr::unrecoverable("Attempting division by 0")); } Ok(list) } -pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet { - Ok(M::Int(match args.len() { - 0 => set, - 1 => f(set, args[0].if_number()?), +pub fn arithmetic_op(set: isize, f: fn(Frac, Frac) -> Frac, args: &[MalType]) -> MalRet { + Ok(M::Num(match args.len() { + 0 => Frac::num(set), + 1 => f(Frac::num(set), args[0].if_number()?), _ => { // TODO: Maybe an accumulator let mut left = args[0].if_number()?; for el in &args[1..] { left = f(left, el.if_number()?); + + const SIM_TRIG: usize = usize::isqrt(std::isize::MAX as usize); + + // TODO: consider if simplifying at every operation or every N + // or just at the end, no idea on how it scale + if std::cmp::max(left.get_num().abs() as usize, left.get_den()) > SIM_TRIG { + left = left.simplify(); + } } - left + // Always simplify at the end + left.simplify() } })) } use MalType::{Nil, T}; -pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet { +pub fn comparison_op(f: fn(Frac, Frac) -> bool, args: &[MalType]) -> MalRet { if args.is_empty() { return Ok(Nil); } @@ -259,7 +272,7 @@ use std::process::exit; pub fn mal_exit(list: &[MalType]) -> MalRet { match car(list)? { - MalType::Int(val) => exit(*val as i32), + MalType::Num(val) => exit(val.int() as i32), _ => exit(-1), } } diff --git a/src/printer.rs b/src/printer.rs index 9aaef74..e38b74f 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -17,7 +17,7 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String { M::T => "t".to_string(), M::Sym(sym) => sym.to_string(), M::Key(sym) => sym[2..].to_string(), - M::Int(val) => val.to_string(), + M::Num(val) => val.to_string(), M::Str(str) => { if print_readably { escape_str(str) diff --git a/src/reader.rs b/src/reader.rs index 5118039..2308bdf 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -95,8 +95,11 @@ impl Reader { "t" => Ok(T), "nil" => Ok(Nil), tk => { - if Regex::new(r"^-?[0-9]+$").unwrap().is_match(tk) { - return Ok(Int(tk.parse::().unwrap())); + if Regex::new(r"^[-\+]?[0-9]+(/[0-9]+)?$") + .unwrap() + .is_match(tk) + { + return Ok(Num(Frac::from_str(&tk))); } if tk.starts_with('\"') { if tk.len() > 1 && tk.ends_with('\"') { @@ -180,7 +183,7 @@ mod tests { use crate::{ reader::read_str, - types::{MalMap, MalType as M}, + types::{Frac, MalMap, MalType as M}, }; use super::{tokenize, Reader}; @@ -270,7 +273,9 @@ mod tests { let r = Reader::new(); r.push("nil 1 t a \"s\" :a ) ] }"); assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Nil))); - assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Int(1)))); + assert!( + matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Num(v) if v == Frac::num(1))) + ); assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::T))); assert!( matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Sym(v) if matches!(v.borrow(), "a"))) @@ -298,7 +303,7 @@ mod tests { if matches!(x.clone(), M::List(list) if list.len() == expected.len() && list.iter().zip(expected) - .all(|(x, y)| matches!(x, M::Int(v) if (*v as isize) == y))))); + .all(|(x, y)| matches!(x, M::Num(v) if v.int() == y))))); r.clear(); // Test vector @@ -309,7 +314,7 @@ mod tests { if matches!(x.clone(), M::Vector(list) if list.len() == exp.len() && list.iter().zip(exp) - .all(|(x, y)| matches!(x, M::Int(v) if (*v as isize) == y))))); + .all(|(x, y)| matches!(x, M::Num(v) if v.int() == y))))); r.clear(); // Test map @@ -329,7 +334,7 @@ mod tests { }; assert!(matches!(t.get("n"), Some(x) if matches!(&x, M::Nil))); assert!(matches!(t.get("t"), Some(x) if matches!(&x, M::T))); - assert!(matches!(t.get("i"), Some(x) if matches!(&x, M::Int(v) if *v == 1))); + assert!(matches!(t.get("i"), Some(x) if matches!(&x, M::Num(v) if v.int() == 1))); assert!( matches!(t.get("s"), Some(x) if matches!(&x, M::Str(v) if matches!(v.borrow(), "str"))) ); diff --git a/src/types.rs b/src/types.rs index 1a03c7a..23987e7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,11 +1,145 @@ use crate::env::{car_cdr, Env}; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{ + cell::RefCell, + cmp::Ordering, + collections::HashMap, + ops::{Add, Div, Mul, Sub}, + rc::Rc, +}; pub type MalStr = Rc; pub type MalArgs = Rc<[MalType]>; pub type MalMap = HashMap; pub type MalRet = Result; +#[derive(Clone, Copy)] +pub struct Frac { + num: isize, + den: usize, +} + +impl Add for Frac { + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + num: self.num * other.den as isize + other.num * self.den as isize, + den: self.den * other.den, + } + } +} + +impl Sub for Frac { + type Output = Self; + fn sub(self, other: Self) -> Self { + Self { + num: self.num * other.den as isize - other.num * self.den as isize, + den: self.den * other.den, + } + } +} + +impl Mul for Frac { + type Output = Self; + fn mul(self, other: Self) -> Self { + Self { + num: self.num * other.num, + den: self.den * other.den, + } + } +} + +impl Div for Frac { + type Output = Self; + fn div(self, other: Frac) -> Self { + let other_sign = other.num.signum(); + Self { + num: self.num * other.den as isize * other_sign, + den: self.den * other.num.abs() as usize, + } + } +} + +impl PartialOrd for Frac { + fn partial_cmp(&self, other: &Self) -> Option { + Some((self.num * other.den as isize).cmp(&(other.num * self.den as isize))) + } +} + +impl PartialEq for Frac { + fn eq(&self, other: &Self) -> bool { + (!((self.num < 0) ^ (other.num < 0))) + && self.num.abs() as usize * other.den == other.num.abs() as usize * self.den + } +} + +impl Frac { + pub fn frac(num: isize, den: usize) -> Self { + Self { num, den } + } + + pub fn num(num: isize) -> Self { + Self { num, den: 1 } + } + + pub fn exact_zero(&self) -> bool { + self.num == 0 + } + + pub fn get_num(&self) -> isize { + self.num + } + + pub fn get_den(&self) -> usize { + self.den + } + + fn _gcd(&self) -> usize { + let mut t: usize; + let mut a = self.num.abs() as usize; + let mut b = self.den; + while b > 0 { + t = b; + b = a % b; + a = t; + } + return a; + } + + pub fn simplify(&self) -> Frac { + // Euclid's algorithm to reduce fraction + // TODO: (decide if implementing this automathically once fraction + // numbers become bigger than specified) + let gcd = self._gcd(); + return Frac { + num: self.num / gcd as isize, + den: self.den / gcd, + }; + } + + pub fn int(&self) -> isize { + self.num / self.den as isize + } + + pub fn to_string(&self) -> String { + let mut tmp = self.num.to_string(); + if self.den != 1 { + tmp = tmp + "/" + &self.den.to_string() + } + return tmp; + } + // return Ok(Num(Frac::num(tk.parse::().unwrap()))); + + pub fn from_str(tk: &str) -> Self { + match tk.find("/") { + Some(v) => Self { + num: tk[0..v].parse::().unwrap(), + den: tk[v + 1..tk.len()].parse::().unwrap(), + }, + None => Frac::num(tk.parse::().unwrap()), + } + } +} + // All Mal types should inherit from this #[derive(Clone)] pub enum MalType { @@ -24,7 +158,7 @@ pub enum MalType { Key(MalStr), Str(MalStr), Ch(char), - Int(isize), + Num(Frac), Atom(Rc>), Nil, T, @@ -37,9 +171,9 @@ impl Default for &MalType { } impl MalType { - pub fn if_number(&self) -> Result { + pub fn if_number(&self) -> Result { match self { - Self::Int(val) => Ok(*val), + Self::Num(val) => Ok(val.clone()), _ => Err(MalErr::unrecoverable( format!("{:?} is not a number", prt(self)).as_str(), )), @@ -87,7 +221,7 @@ impl MalType { + match self { M::Nil => "nil", M::T => "t", - M::Int(_) => "int", + M::Num(_) => "number", M::Fun(_, _) | M::MalFun { .. } => "lambda", M::Key(_) => "key", M::Str(_) => "string", @@ -109,7 +243,7 @@ fn mal_compare(args: (&MalType, &MalType)) -> bool { match (args.0, args.1) { (M::Nil, M::Nil) => true, (M::T, M::T) => true, - (M::Int(a), M::Int(b)) => a == b, + (M::Num(a), M::Num(b)) => a == b, (M::Ch(a), M::Ch(b)) => a == b, (M::Key(a), M::Key(b)) | (M::Str(a), M::Str(b)) | (M::Sym(a), M::Sym(b)) => a == b, (M::List(a), M::List(b)) | (M::Vector(a), M::Vector(b)) => { diff --git a/tests/arithmetic.mal b/tests/arithmetic.mal index 33bc015..a790c87 100644 --- a/tests/arithmetic.mal +++ b/tests/arithmetic.mal @@ -1,23 +1,28 @@ ; + -(assert (= (+ 1 -4) -3)) -(assert (= (+ 1 1) 2)) - +(assert-eq (+ 1 -4) -3) +(assert-eq (+ 1 1) 2) ; - -(assert (= (- 2 1) 1)) -(assert (= (- 1 2) -1)) +(assert-eq (- 2 1) 1) +(assert-eq (- 1 2) -1) ; * -(assert (= (* 2 3) 6)) -(assert (= (* -2 3) -6)) -(assert (= (* -2 -3) 6)) +(assert-eq (* 2 3) 6) +(assert-eq (* -2 3) -6) +(assert-eq (* -2 -3) 6) ; / -(assert (= (/ 3 2) 1)) -(assert (= (/ 2 3) 0)) -(assert (= (/ -10 2) -5)) -(assert (= (/ -10 -2) 5)) +(assert-eq (/ 3 2) 3/2) +(assert-eq (floor (/ 3 2)) 1) +(assert-eq (/ 2 3) 2/3) +(assert-eq (floor (/ 2 3)) 0) +(assert-eq (/ -10 2) -5) +(assert-eq (/ -10 -2) 5) (assert (not (ok? (/ 12 0)))) +; frac rart +(assert-eq (num 3/2) 3) +(assert-eq (den 3/2) 2) + ; > (assert (> 3 2)) (assert (not (> 1 2))) diff --git a/tests/fil.mal b/tests/fil.mal index 8ec945f..9b7c0a5 100644 --- a/tests/fil.mal +++ b/tests/fil.mal @@ -1,4 +1,4 @@ ; filter with builtin function (assert-eq (list '(1) '(2) '(3)) (filter car (list '(1) '() '(2) '() '(3)))) ; filter with lambda function -(assert-eq (list 1 2 3) (filter int? (list 1 "string" 2 'symbol 3 :label))) +(assert-eq (list 1 2 3) (filter num? (list 1 "string" 2 'symbol 3 :label)))