Compare commits

11 Commits
dev ... main

Author SHA1 Message Date
0f2a32909c Nooooooo 2025-07-28 16:58:54 +09:00
8d704792b1 Some fix
Improved number parsing, now does not panic on fail
Clippy-cloppy some stuff
2025-07-24 13:28:22 +09:00
f14b2daaf3 Fixing division
Previously was returning error on
- (/ 0 n) with error: attempting division by zero
2025-07-23 10:21:16 +09:00
a1692214f6 Fixed *ARGV*
Arguments are now srings (were symbols previously)
2025-07-23 09:55:21 +09:00
75057723ff Fixed fractional insertion 2025-07-23 09:35:11 +09:00
4f528b832a Fixed core and libs
- Added ceil function for fractionals
- Fixed symbols in string library
2025-07-23 09:26:52 +09:00
92be9e4483 Merge branch 'dev' 2025-07-22 11:49:34 +09:00
ce9e50f2ed Merge branch 'dev' 2025-06-13 18:34:30 +09:00
da617713c3 Fixe documentation 2025-05-10 13:05:02 +09:00
de43cdfefb Renaming libraries 2025-04-15 17:52:32 +09:00
57c2590a84 style(Makefile): whoops 2025-04-15 13:46:39 +09:00
12 changed files with 141 additions and 78 deletions

View File

@ -23,6 +23,10 @@ conf: test
@test -s ${CONFIG_FILE} || (\ @test -s ${CONFIG_FILE} || (\
echo ";; Write here your mal config" >> ${MAL_HOME}/config.mal\ echo ";; Write here your mal config" >> ${MAL_HOME}/config.mal\
&& echo "; (def! BANNER \"\") ; Hide banner at startup" >> ${MAL_HOME}/config.mal\ && echo "; (def! BANNER \"\") ; Hide banner at startup" >> ${MAL_HOME}/config.mal\
&& echo '' >> ${MAL_HOME}/config.mal\
&& echo '(def! BANNER (str BANNER' >> ${MAL_HOME}/config.mal\
&& echo '";\n; **** To remove this banner and config mal, edit: ****\n"' >> ${MAL_HOME}/config.mal\
&& echo '"; /Users/rogora/.config/mal/config.mal\n"))' >> ${MAL_HOME}/config.mal\
) )
install: build-release test conf install: build-release test conf
@ -41,4 +45,4 @@ install: build-release test conf
@echo "To start mal run:" @echo "To start mal run:"
@printf "\tmal [path/to/script [args ...]]\n\n" @printf "\tmal [path/to/script [args ...]]\n\n"
@echo "To config mal edit:" @echo "To config mal edit:"
@printf "\t${MAL_HOME}/config.mal" @printf "\t${MAL_HOME}/config.mal\n"

View File

@ -51,6 +51,15 @@
(def! empty? (fn* [l] (def! empty? (fn* [l]
(= (count l) 0))) (= (count l) 0)))
(def! mod (fn* [a b]
(- a (* (floor (/ a b)) b))))
(def! ceil (fn* [n]
(if (= (num n) 1)
n
(+ (floor n) 1))))
; Other functions
(def! assert-msg (fn* [e m] (def! assert-msg (fn* [e m]
(if (not e) (if (not e)
(raise m)))) (raise m))))

View File

@ -23,13 +23,13 @@
(def! _split-ch (fn* [s c] (def! _split-ch (fn* [s c]
"Split the string at every occurrence of character sc" "Split the string at every occurrence of character sc"
(def! s (boom s)) (def! s (boom s))
(def! split-r (fn* [l t r] (def! split-r (fn* [l tt r]
(if (empty? l) (if (empty? l)
(cons t r) (cons tt r)
(do (def! cc (car l)) (do (def! cc (car l))
(if (= cc c) (if (= cc c)
(split-r (cdr l) "" (cons t r)) (split-r (cdr l) "" (cons tt r))
(split-r (cdr l) (str t cc) r)))))) (split-r (cdr l) (str tt cc) r))))))
(reverse (split-r s "" '())))) (reverse (split-r s "" '()))))
(def! split (fn* [string delimiter] (def! split (fn* [string delimiter]
@ -53,10 +53,10 @@
(def! join (fn* [l s] (def! join (fn* [l s]
"Join element of list l to a stiring, using s as separator" "Join element of list l to a stiring, using s as separator"
(def! s (or s "")) (def! s (or s ""))
(def! join-r (fn* [l t] (def! join-r (fn* [l tt]
(if (empty? l) (if (empty? l)
t tt
(join-r (cdr l) (str t s (car l)))))) (join-r (cdr l) (str tt s (car l))))))
(join-r (cdr l) (car l)))) (join-r (cdr l) (car l))))
(def! chsub (fn* [s c1 c2] (def! chsub (fn* [s c1 c2]

View File

@ -70,7 +70,7 @@ pub fn ns_init() -> Env {
"<=" => Fun(|a| comparison_op( |a, b| a <= b, a), "Returns true if the first argument is smaller than or equal to the second one, nil otherwise"), "<=" => Fun(|a| comparison_op( |a, b| a <= b, a), "Returns true if the first argument is smaller than or equal to the second one, nil otherwise"),
">=" => Fun(|a| comparison_op( |a, b| a >= b, a), "Returns true if the first argument is greater than or equal to the second one, nil otherwise"), ">=" => Fun(|a| comparison_op( |a, b| a >= b, a), "Returns true if the first argument is greater than or equal to the second one, nil otherwise"),
"pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::<Vec<String>>().join("").into())), "Print readably all arguments"), "pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::<Vec<String>>().join("").into())), "Print readably all arguments"),
"str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::<Vec<String>>().join("").into())), "Print non readably all arguments"), "str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::<Vec<String>>().join("").into())), "Concatenate all arguments as a string"),
"prn" => Fun(|a| {a.iter().for_each(|a| print!("{}", pr_str(a, false))); let _ = io::stdout().flush(); Ok(Nil) }, "Print readably all the arguments"), "prn" => Fun(|a| {a.iter().for_each(|a| print!("{}", pr_str(a, false))); let _ = io::stdout().flush(); Ok(Nil) }, "Print readably all the arguments"),
"println" => Fun(|a| {a.iter().for_each(|a| print!("{}", pr_str(a, false))); println!(); Ok(Nil) }, "Print readably all the arguments"), "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"), "list" => Fun(|a| Ok(List(a.into())), "Return the arguments as a list"),

View File

@ -3,7 +3,6 @@ use crate::types::MalErr;
use crate::types::{Frac, MalMap, MalRet, MalType}; use crate::types::{Frac, MalMap, MalRet, MalType};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::usize;
#[derive(Clone)] #[derive(Clone)]
pub struct EnvType { pub struct EnvType {
@ -16,8 +15,8 @@ impl EnvType {
let mut keys = self let mut keys = self
.data .data
.borrow() .borrow()
.iter() .keys()
.map(|(k, _)| k.to_string()) .map(|k| k.to_string())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
keys.sort_unstable(); keys.sort_unstable();
keys keys
@ -45,11 +44,11 @@ pub fn env_get(env: &Env, sym: &str) -> MalRet {
return Ok(val.clone()); return Ok(val.clone());
} }
if let Some(outer) = &iter_env.outer { if let Some(outer) = &iter_env.outer {
iter_env = &outer; iter_env = outer;
continue; continue;
} }
return Err(MalErr::unrecoverable( return Err(MalErr::unrecoverable(
format!("symbol {:?} not defined", sym).as_str(), format!("symbol {sym:?} not defined").as_str(),
)); ));
} }
// Recursive was prettier, but we hate recursion // Recursive was prettier, but we hate recursion
@ -57,12 +56,12 @@ pub fn env_get(env: &Env, sym: &str) -> MalRet {
pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env, MalErr> { pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env, MalErr> {
let env = env_new(Some(outer)); let env = env_new(Some(outer));
let binds = binds.if_vec()?; let binds = binds.if_list()?;
let binl = binds.len(); let binl = binds.len();
let expl = exprs.len(); let expl = exprs.len();
if binl < expl { if binl < expl {
return Err(MalErr::unrecoverable( return Err(MalErr::unrecoverable(
format!("Expected {} args, got {}", binl, expl).as_str(), format!("Expected {binl} args, got {expl}").as_str(),
)); ));
} }
for (bind, expr) in binds.iter().zip(exprs.iter()) { for (bind, expr) in binds.iter().zip(exprs.iter()) {
@ -128,7 +127,7 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> CallRet {
_ => Err(MalErr::unrecoverable("Map argument must be string or key")), _ => Err(MalErr::unrecoverable("Map argument must be string or key")),
} }
} }
M::Vector(v) => { M::Vector(v) | M::List(v) => {
if args.is_empty() { if args.is_empty() {
return Err(MalErr::unrecoverable("No key provided to Vector construct")); return Err(MalErr::unrecoverable("No key provided to Vector construct"));
} }
@ -150,14 +149,26 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> CallRet {
} }
pub fn any_zero(list: &[MalType]) -> Result<&[MalType], MalErr> { pub fn any_zero(list: &[MalType]) -> Result<&[MalType], MalErr> {
if list match list.len() {
1 => {
if list[0].if_number()?.get_num() == 0 {
Err(MalErr::unrecoverable("Attempting division by 0"))
} else {
Ok(list)
}
}
_ => {
if list[1..list.len()]
.iter() .iter()
.any(|x| matches!(x, M::Num(v) if v.exact_zero())) .any(|x| matches!(x, M::Num(v) if v.exact_zero()))
{ {
return Err(MalErr::unrecoverable("Attempting division by 0")); Err(MalErr::unrecoverable("Attempting division by 0"))
} } else {
Ok(list) Ok(list)
} }
}
}
}
pub fn arithmetic_op(set: isize, f: fn(Frac, Frac) -> Frac, args: &[MalType]) -> MalRet { pub fn arithmetic_op(set: isize, f: fn(Frac, Frac) -> Frac, args: &[MalType]) -> MalRet {
Ok(M::Num(match args.len() { Ok(M::Num(match args.len() {
@ -169,11 +180,11 @@ pub fn arithmetic_op(set: isize, f: fn(Frac, Frac) -> Frac, args: &[MalType]) ->
for el in &args[1..] { for el in &args[1..] {
left = f(left, el.if_number()?); left = f(left, el.if_number()?);
const SIM_TRIG: usize = usize::isqrt(std::isize::MAX as usize); const SIM_TRIG: usize = usize::isqrt(isize::MAX as usize);
// TODO: consider if simplifying at every operation or every N // TODO: consider if simplifying at every operation or every N
// or just at the end, no idea on how it scale // 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 { if std::cmp::max(left.get_num().unsigned_abs(), left.get_den()) > SIM_TRIG {
left = left.simplify(); left = left.simplify();
} }
} }

View File

@ -75,7 +75,7 @@ fn let_star_form(list: &[MalType], env: Env) -> Result<(MalType, Env), MalErr> {
let inner_env = env_new(Some(env.clone())); let inner_env = env_new(Some(env.clone()));
// change the inner environment // change the inner environment
let (car, cdr) = car_cdr(list)?; let (car, cdr) = car_cdr(list)?;
let list = car.if_vec()?; let list = car.if_list()?;
if list.len() % 2 != 0 { if list.len() % 2 != 0 {
return Err(MalErr::unrecoverable( return Err(MalErr::unrecoverable(
"let* form, number of arguments must be even", "let* form, number of arguments must be even",
@ -114,7 +114,7 @@ fn if_form(list: &[MalType], env: Env) -> MalRet {
fn fn_star_form(list: &[MalType], env: Env) -> MalRet { fn fn_star_form(list: &[MalType], env: Env) -> MalRet {
let (binds, exprs) = car_cdr(list)?; let (binds, exprs) = car_cdr(list)?;
binds.if_vec()?; binds.if_list()?;
Ok(M::MalFun { Ok(M::MalFun {
// eval: eval_ast, // eval: eval_ast,
params: Rc::new(binds.clone()), params: Rc::new(binds.clone()),
@ -129,7 +129,7 @@ pub fn help_form(list: &[MalType], env: Env) -> MalRet {
let (sym, _) = car_cdr(list)?; let (sym, _) = car_cdr(list)?;
let sym_str = sym.if_symbol()?; let sym_str = sym.if_symbol()?;
match eval(sym, env.clone())? { match eval(sym, env.clone())? {
M::Fun(_, desc) => eprintln!("{}\t[builtin]: {}\n", sym_str, desc), M::Fun(_, desc) => eprintln!("{sym_str}\t[builtin]: {desc}\n"),
M::MalFun { params, ast, .. } => print_malfun(sym_str, params, ast), M::MalFun { params, ast, .. } => print_malfun(sym_str, params, ast),
_ => eprintln!("{}\t[symbol]: {}\n", sym_str, prt(&env_get(&env, sym_str)?)), _ => eprintln!("{}\t[symbol]: {}\n", sym_str, prt(&env_get(&env, sym_str)?)),
} }

View File

@ -35,22 +35,21 @@ pub fn load_home_file(filename: &str, env: &Env, warn: bool) {
if Path::new(&full_filename).exists() { if Path::new(&full_filename).exists() {
if let Err(e) = load_file(&full_filename, env) { if let Err(e) = load_file(&full_filename, env) {
eprintln!("; reading \"{}\":", full_filename); eprintln!("; reading \"{full_filename}\":");
eprintln!("{}", e.message()); eprintln!("{}", e.message());
} }
} else if warn { } else if warn {
eprintln!("; WARNING: file \"{}\" does not exist", full_filename); eprintln!("; WARNING: file \"{full_filename}\" does not exist");
} }
} }
pub fn read_file(filename: &str) -> Result<MalStr, MalErr> { pub fn read_file(filename: &str) -> Result<MalStr, MalErr> {
let mut file = File::open(filename).map_err(|_| { let mut file = File::open(filename)
MalErr::unrecoverable(format!("Failed to open file '{}'", filename).as_str()) .map_err(|_| MalErr::unrecoverable(format!("Failed to open file '{filename}'").as_str()))?;
})?;
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content).map_err(|_| { file.read_to_string(&mut content).map_err(|_| {
MalErr::unrecoverable(format!("Failed to read content of '{}'", filename).as_str()) MalErr::unrecoverable(format!("Failed to read content of '{filename}'").as_str())
})?; })?;
Ok(content.into()) Ok(content.into())
@ -58,11 +57,7 @@ pub fn read_file(filename: &str) -> Result<MalStr, MalErr> {
pub fn load_file(filename: &str, env: &Env) -> MalRet { pub fn load_file(filename: &str, env: &Env) -> MalRet {
eval_str( eval_str(
format!( format!("(eval (read-string (str \"(do \" (slurp \"{filename}\") \"\nnil)\")))").as_str(),
"(eval (read-string (str \"(do \" (slurp \"{}\") \"\nnil)\")))",
filename
)
.as_str(),
env, env,
) )
} // WTF this is becoming ever less like rust and more like lisp, did I really completely skip the file reading? } // WTF this is becoming ever less like rust and more like lisp, did I really completely skip the file reading?
@ -70,15 +65,26 @@ pub fn load_file(filename: &str, env: &Env) -> MalRet {
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::DefaultEditor; use rustyline::DefaultEditor;
pub fn pre_load(argv: &Vec<String>, env: &Env) { pub fn pre_load(argv: &[String], env: &Env) {
eval_str(format!("(def! *ARGV* '({}))", argv[1..].iter().map(|x| x.to_string() + " ").collect::<String>()).as_str(), &env).unwrap(); eval_str(
format!(
"(def! *ARGV* '({}))",
argv[1..]
.iter()
.map(|x| "\"".to_string() + x + "\" ")
.collect::<String>()
)
.as_str(),
env,
)
.unwrap();
} }
pub fn interactive(env: Env) { pub fn interactive(env: Env) {
const HISTORY: &str = ".mal-history"; const HISTORY: &str = ".mal-history";
let home = get_home_path(&env).unwrap(); let home = get_home_path(&env).unwrap();
let history = home + "/" + HISTORY; let history = home + "/" + HISTORY;
eval_str(format!("(def! MAL_HISTORY \"{}\")", history).as_str(), &env).unwrap(); eval_str(format!("(def! MAL_HISTORY \"{history}\")").as_str(), &env).unwrap();
// Using "Editor" instead of the standard I/O because I hate myself but not this much // Using "Editor" instead of the standard I/O because I hate myself but not this much
// TODO: remove unwrap and switch to a better error handling // TODO: remove unwrap and switch to a better error handling
@ -107,14 +113,14 @@ pub fn interactive(env: Env) {
// TODO: should handle this in a different way // TODO: should handle this in a different way
rl.add_history_entry(&line).unwrap_or_default(); rl.add_history_entry(&line).unwrap_or_default();
rl.save_history(&history) rl.save_history(&history)
.unwrap_or_else(|e| eprintln!("; WARNING: saving history: {}", e)); .unwrap_or_else(|e| eprintln!("; WARNING: saving history: {e}"));
parser.push(&line); parser.push(&line);
// Perform rep on whole available input // Perform rep on whole available input
match rep(&parser, &env) { match rep(&parser, &env) {
Ok(output) => output.iter().for_each(|el| { Ok(output) => output.iter().for_each(|el| {
eprintln!("; [{}]> {}", num, el); eprintln!("; [{num}]> {el}");
num += 1; num += 1;
}), }),
Err(error) => { Err(error) => {
@ -134,7 +140,7 @@ pub fn interactive(env: Env) {
} }
Err(ReadlineError::Eof) => exit(0), Err(ReadlineError::Eof) => exit(0),
Err(err) => { Err(err) => {
eprint!("; Error reading lnie: {:?}", err); eprint!("; Error reading lnie: {err:?}");
break; break;
} }
} }

View File

@ -56,7 +56,7 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
M::Atom(sub) => format!("Atom({})", pr_str(&sub.borrow(), print_readably)), M::Atom(sub) => format!("Atom({})", pr_str(&sub.borrow(), print_readably)),
M::Ch(c) => { M::Ch(c) => {
if print_readably { if print_readably {
format!("#\\{}", c) format!("#\\{c}")
} else { } else {
c.to_string() c.to_string()
} }

View File

@ -83,7 +83,7 @@ impl Reader {
"]" => Ok(Vector(vector.into())), "]" => Ok(Vector(vector.into())),
"}" => make_map(vector.into()), "}" => make_map(vector.into()),
t => Err(MalErr::unrecoverable( t => Err(MalErr::unrecoverable(
format!("Unknown collection terminator: {}", t).as_str(), format!("Unknown collection terminator: {t}").as_str(),
)), )),
} }
} }
@ -99,7 +99,12 @@ impl Reader {
.unwrap() .unwrap()
.is_match(tk) .is_match(tk)
{ {
return Ok(Num(Frac::from_str(&tk))); return match Frac::from_str(tk) {
Some(v) => Ok(Num(v)),
None => Err(MalErr::unrecoverable(
format!("Cannot parse {tk} as a number").as_str(),
)),
};
} }
if tk.starts_with('\"') { if tk.starts_with('\"') {
if tk.len() > 1 && tk.ends_with('\"') { if tk.len() > 1 && tk.ends_with('\"') {
@ -110,7 +115,7 @@ impl Reader {
)); ));
} }
if tk.starts_with(':') { if tk.starts_with(':') {
return Ok(Key(format!("ʞ{}", tk).into())); return Ok(Key(format!("ʞ{tk}").into()));
} }
Ok(Sym(tk.into())) Ok(Sym(tk.into()))
} }
@ -191,7 +196,7 @@ mod tests {
fn reader_setup1() -> Reader { fn reader_setup1() -> Reader {
let r = Reader::new(); let r = Reader::new();
r.push("()[]{} \"str\" :key sym 1 ; comment"); r.push("()[]{} \"str\" :key sym 1 ; comment");
return r; r
} }
#[test] #[test]
@ -255,7 +260,7 @@ mod tests {
let r = reader_setup1(); let r = reader_setup1();
assert!(!r.ended()); assert!(!r.ended());
for _ in r.tokens.borrow().iter() { for _ in r.tokens.borrow().iter() {
assert!(matches!(r.next(), Ok(_))) assert!(r.next().is_ok())
} }
assert!(r.ended()); assert!(r.ended());
} }

View File

@ -54,7 +54,7 @@ impl Div for Frac {
let other_sign = other.num.signum(); let other_sign = other.num.signum();
Self { Self {
num: self.num * other.den as isize * other_sign, num: self.num * other.den as isize * other_sign,
den: self.den * other.num.abs() as usize, den: self.den * other.num.unsigned_abs(),
} }
} }
} }
@ -68,14 +68,26 @@ impl PartialOrd for Frac {
impl PartialEq for Frac { impl PartialEq for Frac {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
(!((self.num < 0) ^ (other.num < 0))) (!((self.num < 0) ^ (other.num < 0)))
&& self.num.abs() as usize * other.den == other.num.abs() as usize * self.den && self.num.unsigned_abs() * other.den == other.num.unsigned_abs() * self.den
}
}
use std::fmt;
impl fmt::Display for Frac {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.den == 1 {
write!(f, "{}", self.num)
} else {
write!(f, "{}/{}", self.num, self.den)
}
} }
} }
impl Frac { impl Frac {
pub fn frac(num: isize, den: usize) -> Self { //pub fn frac(num: isize, den: usize) -> Self {
Self { num, den } // Self { num, den }
} //}
pub fn num(num: isize) -> Self { pub fn num(num: isize) -> Self {
Self { num, den: 1 } Self { num, den: 1 }
@ -95,48 +107,64 @@ impl Frac {
fn _gcd(&self) -> usize { fn _gcd(&self) -> usize {
let mut t: usize; let mut t: usize;
let mut a = self.num.abs() as usize; let mut a = self.num.unsigned_abs();
let mut b = self.den; let mut b = self.den;
while b > 0 { while b > 0 {
t = b; t = b;
b = a % b; b = a % b;
a = t; a = t;
} }
return a; a
} }
// Ideally builtin functions ( + - * / ) can operate with the values in any
// form without problems, occasional simplification is done for numeric
// stability, ensure to simplify after insert and before using any funcition
// (floor, ceil, etc.)
pub fn simplify(&self) -> Frac { pub fn simplify(&self) -> Frac {
// Euclid's algorithm to reduce fraction // Euclid's algorithm to reduce fraction
// TODO: (decide if implementing this automathically once fraction // TODO: (decide if implementing this automathically once fraction
// numbers become bigger than specified) // numbers become bigger than specified)
let gcd = self._gcd(); let gcd = self._gcd();
return Frac { Frac {
num: self.num / gcd as isize, num: self.num / gcd as isize,
den: self.den / gcd, den: self.den / gcd,
}; }
} }
pub fn int(&self) -> isize { pub fn int(&self) -> isize {
self.num / self.den as 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()))); // return Ok(Num(Frac::num(tk.parse::<isize>().unwrap())));
pub fn from_str(tk: &str) -> Self { pub fn from_str(tk: &str) -> Option<Self> {
match tk.find("/") { let frac = match tk.find("/") {
Some(v) => Self { Some(v) => {
num: tk[0..v].parse::<isize>().unwrap(), let num = match tk[0..v].parse::<isize>() {
den: tk[v + 1..tk.len()].parse::<usize>().unwrap(), Ok(v) => v,
}, Err(_) => return None,
None => Frac::num(tk.parse::<isize>().unwrap()), };
let den = match tk[v + 1..tk.len()].parse::<usize>() {
Ok(v) => v,
Err(_) => return None,
};
Self { num, den }
} }
None => {
let num = match tk.parse::<isize>() {
Ok(v) => v,
Err(_) => return None,
};
Self::num(num)
}
};
// Ensure that value is simplified before being inserted
// otherwise
// (/ 4 4) results in 1/1
// 4/4 results in 4/4
// this breaks some functions (like ceil) and doesn't make much sense
Some(frac.simplify())
} }
} }
@ -173,7 +201,7 @@ impl Default for &MalType {
impl MalType { impl MalType {
pub fn if_number(&self) -> Result<Frac, MalErr> { pub fn if_number(&self) -> Result<Frac, MalErr> {
match self { match self {
Self::Num(val) => Ok(val.clone()), Self::Num(val) => Ok(*val),
_ => Err(MalErr::unrecoverable( _ => Err(MalErr::unrecoverable(
format!("{:?} is not a number", prt(self)).as_str(), format!("{:?} is not a number", prt(self)).as_str(),
)), )),
@ -182,9 +210,9 @@ impl MalType {
pub fn if_list(&self) -> Result<&[MalType], MalErr> { pub fn if_list(&self) -> Result<&[MalType], MalErr> {
match self { match self {
Self::List(list) => Ok(list), Self::List(list) | Self::Vector(list) => Ok(list),
_ => Err(MalErr::unrecoverable( _ => Err(MalErr::unrecoverable(
format!("{:?} is not a list", prt(self)).as_str(), format!("{:?} is not an iterable", prt(self)).as_str(),
)), )),
} }
} }