mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
Moving String to MalStr (Rc<str>)
Because someone, somewhere, said that's better Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
10
src/core.rs
10
src/core.rs
@ -35,7 +35,7 @@ use crate::parse_tools::read_file;
|
||||
use crate::printer::pr_str;
|
||||
use crate::reader::{read_str, Reader};
|
||||
use crate::types::MalType::{Fun, Int, List, Nil, Str};
|
||||
use crate::types::{mal_assert, mal_equals, MalArgs, MalErr};
|
||||
use crate::types::{mal_assert, mal_equals, MalErr};
|
||||
|
||||
pub fn ns_init() -> Env {
|
||||
env_init!(None,
|
||||
@ -51,11 +51,11 @@ pub fn ns_init() -> Env {
|
||||
">" => Fun(|a| comparison_op( |a, b| a > b, a), "Returns true if the first argument is strictly greater than 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"),
|
||||
"pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::<Vec<String>>().join(" "))), "Print readably all arguments"),
|
||||
"str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::<Vec<String>>().join(""))), "Print non 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"),
|
||||
"prn" => Fun(|a| {a.iter().for_each(|a| print!("{} ", pr_str(a, false))); 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(MalArgs::new(a.to_vec()))), "Return the arguments as a list"),
|
||||
"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"),
|
||||
"=" => 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')"),
|
||||
@ -63,7 +63,7 @@ pub fn ns_init() -> Env {
|
||||
"read-string" => Fun(|a| read_str(Reader::new().push(car(a)?.if_string()?)).map_err(MalErr::severe), "Tokenize and read the first argument"),
|
||||
"slurp" => Fun(|a| Ok(Str(read_file(car(a)?.if_string()?)?)), "Read a file and return the content as a string"),
|
||||
"env" => Fun(|a| match env::var(car(a)?.if_string()?) {
|
||||
Ok(s) => Ok(Str(s)),
|
||||
Ok(s) => Ok(Str(s.into())),
|
||||
_ => Ok(Nil),
|
||||
}, "Retrieve the specified environment variable, returns NIL if that variable does not exist")
|
||||
)
|
||||
|
||||
@ -34,7 +34,7 @@ pub fn env_new(outer: Option<Env>) -> Env {
|
||||
}
|
||||
|
||||
pub fn env_set(env: &Env, sym: &str, val: &MalType) {
|
||||
env.data.borrow_mut().insert(sym.to_string(), val.clone());
|
||||
env.data.borrow_mut().insert(sym.into(), val.clone());
|
||||
}
|
||||
|
||||
pub fn env_get(env: &Env, sym: &str) -> MalRet {
|
||||
|
||||
87
src/eval.rs
87
src/eval.rs
@ -4,6 +4,7 @@ use crate::env::{first_last, Env};
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType as M;
|
||||
use crate::types::{MalArgs, MalErr, MalMap, MalRet, MalType};
|
||||
use std::borrow::Borrow;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Resolve the first element of the list as the function name and call it
|
||||
@ -98,7 +99,7 @@ fn fn_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
Ok(M::MalFun {
|
||||
// eval: eval_ast,
|
||||
params: Rc::new(binds.clone()),
|
||||
ast: Rc::new(M::List(MalArgs::new(exprs.to_vec()))),
|
||||
ast: Rc::new(M::List(exprs.into())),
|
||||
env,
|
||||
})
|
||||
}
|
||||
@ -134,6 +135,21 @@ pub fn outermost(env: &Env) -> Env {
|
||||
env.clone()
|
||||
}
|
||||
|
||||
macro_rules! apply {
|
||||
($ast:expr, $env:expr) => {{
|
||||
let apply_list = &eval_ast(&$ast, $env.clone())?;
|
||||
let eval_ret = eval_func(apply_list)?;
|
||||
|
||||
match eval_ret {
|
||||
CallFunc::Builtin(ret) => return Ok(ret),
|
||||
CallFunc::MalFun(fun_ast, fun_env) => {
|
||||
$ast = fun_ast;
|
||||
$env = fun_env;
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Intermediate function to discern special forms from defined symbols
|
||||
pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
let mut ast = ast.clone();
|
||||
@ -143,46 +159,39 @@ pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
M::List(list) if list.is_empty() => return Ok(ast.clone()),
|
||||
M::List(list) => {
|
||||
let (symbol, args) = car_cdr(list)?;
|
||||
match symbol {
|
||||
M::Sym(sym) if sym == "def!" => return def_bang_form(args, env.clone()), // Set for env
|
||||
M::Sym(sym) if sym == "let*" => (ast, env) = let_star_form(args, env.clone())?,
|
||||
M::Sym(sym) if sym == "do" => ast = do_form(args, env.clone())?,
|
||||
M::Sym(sym) if sym == "if" => ast = if_form(args, env.clone())?,
|
||||
M::Sym(sym) if sym == "fn*" || sym == "λ" /* :) */ => {
|
||||
return fn_star_form(args, env.clone())
|
||||
}
|
||||
M::Sym(sym) if sym == "help" => return help_form(args, env.clone()),
|
||||
M::Sym(sym) if sym == "find" => return find_form(args, env.clone()),
|
||||
// Oh God, what have I done
|
||||
M::Sym(sym) if sym == "quote" => return Ok(car(args)?.clone()),
|
||||
M::Sym(sym) if sym == "ok?" => {
|
||||
return match eval(car(args)?, env.clone()) {
|
||||
Err(_) => Ok(M::Nil),
|
||||
_ => Ok(M::Bool(true)),
|
||||
if let M::Sym(sym) = symbol {
|
||||
match sym.borrow() {
|
||||
// I don't like to borrow tho
|
||||
"def!" => return def_bang_form(args, env.clone()), // Set for env
|
||||
"let*" => {(ast, env) = let_star_form(args, env.clone())?; continue;},
|
||||
"do" => {ast = do_form(args, env.clone())?; continue;},
|
||||
"if" => {ast = if_form(args, env.clone())?; continue;},
|
||||
"fn*" | "λ" /* :) */ => {
|
||||
return fn_star_form(args, env.clone())
|
||||
}
|
||||
}
|
||||
// Special form, sad
|
||||
// Bruh, is basically double eval
|
||||
M::Sym(sym) if sym == "eval" => {
|
||||
ast = eval(env::car(args)?, env.clone())?;
|
||||
// Climb to the outermost environment (The repl env)
|
||||
env = outermost(&env);
|
||||
}
|
||||
// Filter out special forms
|
||||
// "apply"/invoke
|
||||
_ => {
|
||||
let apply_list = &eval_ast(&ast, env.clone())?;
|
||||
let eval_ret = eval_func(apply_list)?;
|
||||
|
||||
match eval_ret {
|
||||
CallFunc::Builtin(ret) => return Ok(ret),
|
||||
CallFunc::MalFun(fun_ast, fun_env) => {
|
||||
ast = fun_ast;
|
||||
env = fun_env;
|
||||
"help" => return help_form(args, env.clone()),
|
||||
"find" => return find_form(args, env.clone()),
|
||||
// Oh God, what have I done
|
||||
"quote" => return Ok(car(args)?.clone()),
|
||||
"ok?" => {
|
||||
return match eval(car(args)?, env.clone()) {
|
||||
Err(_) => Ok(M::Nil),
|
||||
_ => Ok(M::Bool(true)),
|
||||
}
|
||||
}
|
||||
// Special form, sad
|
||||
// Bruh, is basically double eval
|
||||
"eval" => {
|
||||
ast = eval(env::car(args)?, env.clone())?;
|
||||
// Climb to the outermost environment (The repl env)
|
||||
env = outermost(&env);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
// "apply"/invoke
|
||||
apply!(ast, env)
|
||||
}
|
||||
_ => return eval_ast(&ast, env),
|
||||
}
|
||||
@ -195,14 +204,14 @@ fn eval_collection(list: &MalArgs, env: Env) -> Result<MalArgs, MalErr> {
|
||||
for el in list.as_ref() {
|
||||
ret.push(eval(el, env.clone())?);
|
||||
}
|
||||
Ok(MalArgs::new(ret))
|
||||
Ok(ret.into())
|
||||
}
|
||||
|
||||
/// Evaluate the values of a map
|
||||
fn eval_map(map: &MalMap, env: Env) -> MalRet {
|
||||
let mut ret = MalMap::new();
|
||||
for (k, v) in map {
|
||||
ret.insert(k.to_string(), eval(v, env.clone())?);
|
||||
ret.insert(k.clone(), eval(v, env.clone())?);
|
||||
}
|
||||
Ok(M::Map(ret))
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::env::Env;
|
||||
use crate::eval::eval;
|
||||
use crate::reader::{read_str, Reader};
|
||||
use crate::step6_file::rep;
|
||||
use crate::types::{MalErr, MalRet};
|
||||
use crate::types::{MalErr, MalRet, MalStr};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
@ -39,7 +39,7 @@ pub fn load_home_file(filename: &str, env: &Env, warn: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_file(filename: &str) -> Result<String, MalErr> {
|
||||
pub fn read_file(filename: &str) -> Result<MalStr, MalErr> {
|
||||
let mut file = File::open(filename).map_err(|_| {
|
||||
MalErr::unrecoverable(format!("Failed to open file '{}'", filename).as_str())
|
||||
})?;
|
||||
@ -49,7 +49,7 @@ pub fn read_file(filename: &str) -> Result<String, MalErr> {
|
||||
MalErr::unrecoverable(format!("Failed to read content of '{}'", filename).as_str())
|
||||
})?;
|
||||
|
||||
Ok(content)
|
||||
Ok(content.into())
|
||||
}
|
||||
|
||||
pub fn load_file(filename: &str, env: &Env) -> MalRet {
|
||||
|
||||
@ -5,9 +5,9 @@ use crate::types::{escape_str, MalType};
|
||||
|
||||
fn key_str(val: &str) -> MalType {
|
||||
if val.starts_with('ʞ') {
|
||||
M::Key(val.to_string())
|
||||
M::Key(val.into())
|
||||
} else {
|
||||
M::Str(val.to_string())
|
||||
M::Str(val.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -79,9 +79,9 @@ impl Reader {
|
||||
self.next()?;
|
||||
|
||||
match terminator {
|
||||
")" => Ok(List(MalArgs::new(vector))),
|
||||
"]" => Ok(Vector(MalArgs::new(vector))),
|
||||
"}" => make_map(MalArgs::new(vector)),
|
||||
")" => Ok(List(vector.into())),
|
||||
"]" => Ok(Vector(vector.into())),
|
||||
"}" => make_map(vector.into()),
|
||||
t => Err(MalErr::unrecoverable(
|
||||
format!("Unknown collection terminator: {}", t).as_str(),
|
||||
)),
|
||||
@ -100,15 +100,15 @@ impl Reader {
|
||||
return Ok(Int(tk.parse::<isize>().unwrap()));
|
||||
} else if tk.starts_with('\"') {
|
||||
if tk.len() > 1 && tk.ends_with('\"') {
|
||||
return Ok(Str(unescape_str(tk)));
|
||||
return Ok(Str(unescape_str(tk).into()));
|
||||
}
|
||||
return Err(MalErr::unrecoverable(
|
||||
"End of line reached without closing string",
|
||||
));
|
||||
} else if tk.starts_with(':') {
|
||||
return Ok(Key(format!("ʞ{}", tk)));
|
||||
return Ok(Key(format!("ʞ{}", tk).into()));
|
||||
}
|
||||
Ok(Sym(tk.to_string()))
|
||||
Ok(Sym(tk.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,8 +128,8 @@ impl Reader {
|
||||
// Ugly quote transformation for quote expansion
|
||||
"'" => {
|
||||
self.next()?;
|
||||
Ok(List(Rc::new(vec![
|
||||
MalType::Sym("quote".to_string()),
|
||||
Ok(List(Rc::new([
|
||||
MalType::Sym("quote".into()),
|
||||
self.read_form()?,
|
||||
])))
|
||||
}
|
||||
@ -168,6 +168,8 @@ fn tokenize(input: &str) -> Tokens {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use crate::{
|
||||
reader::read_str,
|
||||
types::{MalMap, MalType as M},
|
||||
@ -262,9 +264,15 @@ mod tests {
|
||||
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, M::Bool(true))));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Sym(v) if v == "a")));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Str(v) if v == "s")));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Key(v) if v == "ʞ:a")));
|
||||
assert!(
|
||||
matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Sym(v) if matches!(v.borrow(), "a")))
|
||||
);
|
||||
assert!(
|
||||
matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Str(v) if matches!(v.borrow(), "s")))
|
||||
);
|
||||
assert!(
|
||||
matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Key(v) if matches!(v.borrow(), "ʞ:a")))
|
||||
);
|
||||
assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable()));
|
||||
@ -314,7 +322,11 @@ 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::Bool(v) if *v)));
|
||||
assert!(matches!(t.get("i"), Some(x) if matches!(&x, M::Int(v) if *v == 1)));
|
||||
assert!(matches!(t.get("s"), Some(x) if matches!(&x, M::Str(v) if v == "str")));
|
||||
assert!(matches!(t.get("ʞ:s"), Some(x) if matches!(&x, M::Key(v) if v == "ʞ:sym")));
|
||||
assert!(
|
||||
matches!(t.get("s"), Some(x) if matches!(&x, M::Str(v) if matches!(v.borrow(), "str")))
|
||||
);
|
||||
assert!(
|
||||
matches!(t.get("ʞ:s"), Some(x) if matches!(&x, M::Key(v) if matches!(v.borrow(), "ʞ:sym")))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
44
src/types.rs
44
src/types.rs
@ -1,6 +1,11 @@
|
||||
use crate::env::{car_cdr, Env};
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
pub type MalStr = Rc<str>;
|
||||
pub type MalArgs = Rc<[MalType]>;
|
||||
pub type MalMap = HashMap<MalStr, MalType>;
|
||||
pub type MalRet = Result<MalType, MalErr>;
|
||||
|
||||
// All Mal types should inherit from this
|
||||
#[derive(Clone)]
|
||||
pub enum MalType {
|
||||
@ -15,9 +20,9 @@ pub enum MalType {
|
||||
env: Env,
|
||||
}, // Used for functions defined within mal
|
||||
// Use Rc so I can now clone like there's no tomorrow
|
||||
Sym(String),
|
||||
Key(String),
|
||||
Str(String),
|
||||
Sym(MalStr),
|
||||
Key(MalStr),
|
||||
Str(MalStr),
|
||||
Int(isize),
|
||||
Bool(bool),
|
||||
Nil,
|
||||
@ -70,20 +75,19 @@ impl MalType {
|
||||
}
|
||||
|
||||
pub fn label_type(&self) -> MalType {
|
||||
let mut labl = "ʞ:".to_string();
|
||||
labl.push_str(match self {
|
||||
M::Nil => "nil",
|
||||
M::Bool(_) => "bool",
|
||||
M::Int(_) => "int",
|
||||
M::Fun(_, _) | M::MalFun { .. } => "lambda",
|
||||
M::Key(_) => "key",
|
||||
M::Str(_) => "string",
|
||||
M::Sym(_) => "symbol",
|
||||
M::List(_) => "list",
|
||||
M::Vector(_) => "vector",
|
||||
M::Map(_) => "map",
|
||||
});
|
||||
M::Key(labl)
|
||||
Key(match self {
|
||||
M::Nil => "ʞ:nil",
|
||||
M::Bool(_) => "ʞ:bool",
|
||||
M::Int(_) => "ʞ:int",
|
||||
M::Fun(_, _) | M::MalFun { .. } => "ʞ:lambda",
|
||||
M::Key(_) => "ʞ:key",
|
||||
M::Str(_) => "ʞ:string",
|
||||
M::Sym(_) => "ʞ:symbol",
|
||||
M::List(_) => "ʞ:ist",
|
||||
M::Vector(_) => "ʞ:vector",
|
||||
M::Map(_) => "ʞ:map",
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,10 +167,6 @@ impl MalErr {
|
||||
}
|
||||
}
|
||||
|
||||
pub type MalArgs = Rc<Vec<MalType>>;
|
||||
pub type MalMap = HashMap<String, MalType>;
|
||||
pub type MalRet = Result<MalType, MalErr>;
|
||||
|
||||
use crate::printer::prt;
|
||||
use MalType::{Key, Map, Str};
|
||||
|
||||
@ -181,7 +181,7 @@ pub fn make_map(list: MalArgs) -> MalRet {
|
||||
match &list[i] {
|
||||
Key(k) | Str(k) => {
|
||||
let v = &list[i + 1];
|
||||
map.insert(k.to_string(), v.clone());
|
||||
map.insert(k.clone(), v.clone());
|
||||
}
|
||||
_ => {
|
||||
return Err(MalErr::unrecoverable(
|
||||
|
||||
Reference in New Issue
Block a user