Files
rust-mal/src/env.rs
teo3300 1af7d68261 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:
2025-07-22 11:49:19 +09:00

285 lines
7.8 KiB
Rust

use crate::eval::eval;
use crate::types::MalErr;
use crate::types::{Frac, MalMap, MalRet, MalType};
use std::cell::RefCell;
use std::rc::Rc;
use std::usize;
#[derive(Clone)]
pub struct EnvType {
data: RefCell<MalMap>,
pub outer: Option<Env>,
}
impl EnvType {
pub fn keys(&self) -> Vec<String> {
let mut keys = self
.data
.borrow()
.iter()
.map(|(k, _)| k.to_string())
.collect::<Vec<String>>();
keys.sort_unstable();
keys
}
}
pub type Env = Rc<EnvType>;
// Following rust implementation, using shorthand to always pas Reference count
pub fn env_new(outer: Option<Env>) -> Env {
Env::new(EnvType {
data: RefCell::new(MalMap::new()),
outer,
})
}
pub fn env_set(env: &Env, sym: &str, val: &MalType) {
env.data.borrow_mut().insert(sym.into(), val.clone());
}
pub fn env_get(env: &Env, sym: &str) -> MalRet {
let mut iter_env = env;
loop {
if let Some(val) = iter_env.data.borrow().get(sym) {
return Ok(val.clone());
}
if let Some(outer) = &iter_env.outer {
iter_env = &outer;
continue;
}
return Err(MalErr::unrecoverable(
format!("symbol {:?} not defined", sym).as_str(),
));
}
// Recursive was prettier, but we hate recursion
}
pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env, MalErr> {
let env = env_new(Some(outer));
let binds = binds.if_vec()?;
let binl = binds.len();
let expl = exprs.len();
if binl < expl {
return Err(MalErr::unrecoverable(
format!("Expected {} args, got {}", binl, expl).as_str(),
));
}
for (bind, expr) in binds.iter().zip(exprs.iter()) {
let bind = bind.if_symbol()?;
env_set(&env, bind, expr);
}
// All arguments are optional, if an argument is not specified, set it to nil
for bind in binds.iter().take(binl).skip(expl) {
env_set(&env, bind.if_symbol()?, &M::Nil);
}
Ok(env)
}
macro_rules! scream {
() => {
panic!("If this messagge occurs, something went terribly wrong")
};
}
use crate::printer::prt;
use crate::types::MalType as M;
pub enum CallFunc {
Builtin(MalType),
MalFun(MalType, Env),
}
pub type CallRet = Result<CallFunc, MalErr>;
pub fn call_func(func: &MalType, args: &[MalType]) -> CallRet {
match func {
M::Fun(func, _) => Ok(CallFunc::Builtin(func(args)?)),
M::MalFun {
// eval,
params,
ast,
env,
..
} => {
let inner_env = env_binds(env.clone(), params, args)?;
// It's fine to clone the environment here
// since this is when the function is actually called
match ast.as_ref() {
M::List(list) => {
for x in &list[0..list.len() - 1] {
eval(x, inner_env.clone())?;
}
Ok(CallFunc::MalFun(
list.last().unwrap_or_default().clone(),
inner_env,
))
}
_ => scream!(),
}
}
M::Map(m) => {
if args.is_empty() {
return Err(MalErr::unrecoverable("No key provided to Map construct"));
}
match &args[0] {
M::Str(s) | M::Key(s) => {
Ok(CallFunc::Builtin(m.get(s).unwrap_or_default().clone()))
}
_ => Err(MalErr::unrecoverable("Map argument must be string or key")),
}
}
M::Vector(v) => {
if args.is_empty() {
return Err(MalErr::unrecoverable("No key provided to Vector construct"));
}
match &args[0] {
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))
}
}
_ => Err(MalErr::unrecoverable("Map argument must be string or key")),
}
}
_ => Err(MalErr::unrecoverable(
format!("{:?} is not a function", prt(func)).as_str(),
)),
}
}
pub fn any_zero(list: &[MalType]) -> Result<&[MalType], MalErr> {
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(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();
}
}
// Always simplify at the end
left.simplify()
}
}))
}
use MalType::{Nil, T};
pub fn comparison_op(f: fn(Frac, Frac) -> bool, args: &[MalType]) -> MalRet {
if args.is_empty() {
return Ok(Nil);
}
let (left, rights) = car_cdr(args)?;
let mut left = left.if_number()?;
for right in rights {
let right = right.if_number()?;
if !f(left, right) {
return Ok(Nil);
}
left = right;
}
Ok(T)
}
pub fn car(list: &[MalType]) -> Result<&MalType, MalErr> {
match list.len() {
0 => Err(MalErr::unrecoverable("Expected at least one argument")),
_ => Ok(&list[0]),
}
}
pub fn cdr(list: &[MalType]) -> &[MalType] {
if list.len() > 1 {
&list[1..]
} else {
&list[0..0]
}
}
pub fn mal_cdr(arg: &MalType) -> MalRet {
let list = arg.if_list()?;
Ok(MalType::List(cdr(list).into()))
}
pub fn mal_car(arg: &MalType) -> MalRet {
let list = arg.if_list()?;
if list.is_empty() {
Ok(Nil)
} else {
Ok(list[0].clone())
}
}
/// Extract the car and cdr from a list
pub fn car_cdr(list: &[MalType]) -> Result<(&MalType, &[MalType]), MalErr> {
Ok((car(list)?, cdr(list)))
}
// TODO: fix these chonky functions
pub fn mal_cons(args: &[MalType]) -> MalRet {
match args.len() {
2 => {
let mut car = vec![args[0].clone()];
let cdr = args[1].if_list()?;
car.extend_from_slice(cdr);
Ok(M::List(car.into()))
}
_ => Err(MalErr::unrecoverable("cons: requires 2 arguments")),
}
}
fn first(list: &[MalType]) -> &[MalType] {
if list.len() > 1 {
&list[..list.len() - 1]
} else {
&list[0..0]
}
}
// FIXME: Treat as result for now, change later
fn last(list: &[MalType]) -> Result<&MalType, MalErr> {
match list.len() {
0 => Ok(&MalType::Nil),
_ => Ok(&list[list.len() - 1]),
}
}
pub fn first_last(list: &[MalType]) -> (&[MalType], Result<&MalType, MalErr>) {
(first(list), last(list))
}
use std::process::exit;
pub fn mal_exit(list: &[MalType]) -> MalRet {
match car(list)? {
MalType::Num(val) => exit(val.int() as i32),
_ => exit(-1),
}
}
// TODO: find another way to process strings
pub fn mal_boom(args: &[MalType]) -> MalRet {
let string = car(args)?.if_string()?;
Ok(M::List(string.chars().map(M::Ch).collect()))
}