mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 01:05:32 +01:00
feat(types.rs): Implemented fractional numbers
Fracional numbers as extension of integernumbers, with Euclid's algorithm for fractional simplification, added functions "floor" "num" "den", added syntax [+/-]<num>/<den> to define fractional numbers BREAKING CHANGE:
This commit is contained in:
@ -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)))
|
||||
|
||||
11
src/core.rs
11
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"),
|
||||
|
||||
37
src/env.rs
37
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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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::<isize>().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")))
|
||||
);
|
||||
|
||||
146
src/types.rs
146
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<str>;
|
||||
pub type MalArgs = Rc<[MalType]>;
|
||||
pub type MalMap = HashMap<MalStr, MalType>;
|
||||
pub type MalRet = Result<MalType, MalErr>;
|
||||
|
||||
#[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<Ordering> {
|
||||
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::<isize>().unwrap())));
|
||||
|
||||
pub fn from_str(tk: &str) -> Self {
|
||||
match tk.find("/") {
|
||||
Some(v) => Self {
|
||||
num: tk[0..v].parse::<isize>().unwrap(),
|
||||
den: tk[v + 1..tk.len()].parse::<usize>().unwrap(),
|
||||
},
|
||||
None => Frac::num(tk.parse::<isize>().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<RefCell<MalType>>),
|
||||
Nil,
|
||||
T,
|
||||
@ -37,9 +171,9 @@ impl Default for &MalType {
|
||||
}
|
||||
|
||||
impl MalType {
|
||||
pub fn if_number(&self) -> Result<isize, MalErr> {
|
||||
pub fn if_number(&self) -> Result<Frac, MalErr> {
|
||||
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)) => {
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -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)))
|
||||
|
||||
Reference in New Issue
Block a user