mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 01:05:32 +01:00
Implementing conditional and integer comparators
- added `init` function, will be used later to implement functions - moved `car_cdr` to `types.rs` - improved some error messages - implemented `do` and `if` special forms - implemented comparison between numeric values (integers) - improved comment Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
36
src/env.rs
36
src/env.rs
@ -1,4 +1,5 @@
|
||||
use crate::types::{MalMap, MalRet, MalType};
|
||||
use crate::types::{comparison_op, MalType::*};
|
||||
use crate::types::{MalArgs, MalMap, MalRet, MalType};
|
||||
|
||||
// This is the first time I implement a macro, and I'm copying it
|
||||
// so I will comment this a LOT
|
||||
@ -43,6 +44,24 @@ impl Env {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, binds: MalArgs, exprs: MalArgs) -> Result<&mut Self, String> {
|
||||
if binds.len() != exprs.len() {
|
||||
return Err("Env init with unmatched length".to_string());
|
||||
} // TODO: May be possible to leave this be and not set additional elements at all
|
||||
for (bind, expr) in binds.iter().zip(exprs.iter()) {
|
||||
match bind {
|
||||
Sym(sym) => self.set(sym, expr),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Initializing environment: {:?} is not a symbol",
|
||||
bind
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, sym: &str, val: &MalType) {
|
||||
self.data.insert(sym.to_string(), val.clone());
|
||||
}
|
||||
@ -58,7 +77,7 @@ impl Env {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::types::int_op;
|
||||
use crate::types::arithmetic_op;
|
||||
use crate::types::MalType::{Fun, Str};
|
||||
use std::process::exit;
|
||||
|
||||
@ -66,9 +85,14 @@ pub fn env_init() -> Env {
|
||||
env_init!(None,
|
||||
"test" => Fun(|_| Ok(Str("This is a test function".to_string()))),
|
||||
"quit" => Fun(|_| {println!("Bye!"); exit(0)}),
|
||||
"+" => Fun(|a| int_op(0, |a, b| a + b, a)),
|
||||
"-" => Fun(|a| int_op(0, |a, b| a - b, a)),
|
||||
"*" => Fun(|a| int_op(1, |a, b| a * b, a)),
|
||||
"/" => Fun(|a| int_op(1, |a, b| a / b, a))
|
||||
"+" => Fun(|a| arithmetic_op(0, |a, b| a + b, a)),
|
||||
"-" => Fun(|a| arithmetic_op(0, |a, b| a - b, a)),
|
||||
"*" => Fun(|a| arithmetic_op(1, |a, b| a * b, a)),
|
||||
"/" => Fun(|a| arithmetic_op(1, |a, b| a / b, a)),
|
||||
"=" => Fun(|a| comparison_op(|a, b| a == b, a)),
|
||||
">" => Fun(|a| comparison_op(|a, b| a > b, a)),
|
||||
"<" => Fun(|a| comparison_op(|a, b| a > b, a)),
|
||||
">=" => Fun(|a| comparison_op(|a, b| a >= b, a)),
|
||||
"<=" => Fun(|a| comparison_op(|a, b| a <= b, a))
|
||||
)
|
||||
}
|
||||
|
||||
57
src/eval.rs
57
src/eval.rs
@ -1,4 +1,5 @@
|
||||
use crate::env::Env;
|
||||
use crate::types::car_cdr;
|
||||
use crate::types::MalType::*;
|
||||
use crate::types::{MalArgs, MalMap, MalRet, MalType};
|
||||
|
||||
@ -9,27 +10,20 @@ fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the first element of the list as the function name and call it
|
||||
/// with the other elements as arguments
|
||||
fn eval_func(list: &MalType) -> MalRet {
|
||||
match list {
|
||||
List(list) => {
|
||||
let (func, args) = car_cdr(list);
|
||||
call_func(func, args)
|
||||
}
|
||||
_ => todo!("Yep! I hate it"),
|
||||
_ => todo!("This should never happen, if you see this message I probably broke the code"),
|
||||
}
|
||||
}
|
||||
|
||||
fn car_cdr(list: &[MalType]) -> (&MalType, &[MalType]) {
|
||||
(
|
||||
&list[0],
|
||||
if list.len() > 1 {
|
||||
&list[1..]
|
||||
} else {
|
||||
&list[0..0]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// def! special form:
|
||||
/// Evaluate the second expression and assign it to the first symbol
|
||||
fn def_bang(list: &[MalType], env: &mut Env) -> MalRet {
|
||||
match list.len() {
|
||||
2 => match &list[0] {
|
||||
@ -47,6 +41,9 @@ fn def_bang(list: &[MalType], env: &mut Env) -> MalRet {
|
||||
}
|
||||
}
|
||||
|
||||
/// let* special form:
|
||||
/// Create a temporary inner environment, assigning pair of elements in
|
||||
/// the first list and returning the evaluation of the second expression
|
||||
fn let_star(list: &[MalType], env: &Env) -> MalRet {
|
||||
// Create the inner environment
|
||||
let mut inner_env = Env::new(Some(Box::new(env.clone())));
|
||||
@ -69,16 +66,49 @@ fn let_star(list: &[MalType], env: &Env) -> MalRet {
|
||||
}
|
||||
}
|
||||
|
||||
/// do special form:
|
||||
/// Evaluate all the elements in a list using eval_ast and return the
|
||||
/// result of the last evaluation
|
||||
fn do_form(list: &[MalType], env: &mut Env) -> MalRet {
|
||||
let mut last_ret = Nil;
|
||||
for element in list.iter() {
|
||||
last_ret = eval_ast(element, env)?;
|
||||
// TODO: may use just "eval" to allow other expressions
|
||||
}
|
||||
Ok(last_ret)
|
||||
}
|
||||
|
||||
fn if_form(list: &[MalType], env: &mut Env) -> MalRet {
|
||||
if !(2..=3).contains(&list.len()) {
|
||||
return Err("Wrong number of arguments".to_string());
|
||||
}
|
||||
let (cond, branches) = car_cdr(list);
|
||||
match eval(cond, env)? {
|
||||
Nil | Bool(false) => match branches.len() {
|
||||
1 => Ok(Nil),
|
||||
_ => eval(&branches[1], env),
|
||||
},
|
||||
_ => eval(&branches[0], env),
|
||||
}
|
||||
}
|
||||
|
||||
/// Intermediate function to discern special forms from defined symbols
|
||||
fn apply(list: &MalArgs, env: &mut Env) -> MalRet {
|
||||
let (car, cdr) = car_cdr(list);
|
||||
match car {
|
||||
Sym(sym) if sym == "def!" => def_bang(cdr, env),
|
||||
Sym(sym) if sym == "let*" => let_star(cdr, env),
|
||||
Sym(sym) if sym == "do" => do_form(cdr, env),
|
||||
Sym(sym) if sym == "if" => if_form(cdr, env),
|
||||
// Filter out special forms
|
||||
Sym(_) => eval_func(&eval_ast(&List(list.to_vec()), env)?),
|
||||
_ => Err(format!("{:?} is not a symbol", car)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch ast evaluation depending on it being a list or not and return the
|
||||
/// result of the evaluation, this function calls "eval_ast" to recursively
|
||||
/// evaluate asts
|
||||
pub fn eval(ast: &MalType, env: &mut Env) -> MalRet {
|
||||
match &ast {
|
||||
List(list) if list.is_empty() => Ok(ast.clone()),
|
||||
@ -87,6 +117,7 @@ pub fn eval(ast: &MalType, env: &mut Env) -> MalRet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Separetely evaluate all elements in a collection (list or vector)
|
||||
fn eval_collection(list: &MalArgs, env: &mut Env) -> Result<MalArgs, String> {
|
||||
let mut ret = MalArgs::new();
|
||||
for el in list {
|
||||
@ -95,6 +126,7 @@ fn eval_collection(list: &MalArgs, env: &mut Env) -> Result<MalArgs, String> {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Evaluate the values of a map
|
||||
fn eval_map(map: &MalMap, env: &mut Env) -> MalRet {
|
||||
let mut ret = MalMap::new();
|
||||
for (k, v) in map {
|
||||
@ -103,6 +135,7 @@ fn eval_map(map: &MalMap, env: &mut Env) -> MalRet {
|
||||
Ok(Map(ret))
|
||||
}
|
||||
|
||||
/// Eval the provided ast
|
||||
fn eval_ast(ast: &MalType, env: &mut Env) -> MalRet {
|
||||
match ast {
|
||||
Sym(sym) => env.get(sym),
|
||||
|
||||
35
src/step4_if_fn_do.rs
Normal file
35
src/step4_if_fn_do.rs
Normal file
@ -0,0 +1,35 @@
|
||||
// Structure the main functions of the interpreter
|
||||
//
|
||||
// For now just act as an echo, note that each function should not modify the
|
||||
// input, thus this can be referenced by the previous step without the need
|
||||
// to allocate more memory
|
||||
|
||||
use crate::env::Env;
|
||||
use crate::eval::eval;
|
||||
use crate::printer::pr_str;
|
||||
use crate::reader::read_str;
|
||||
use crate::types::{MalRet, MalType};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
/// Read input and generate an ast
|
||||
fn READ(input: &str) -> MalRet {
|
||||
read_str(input).map_err(|err| format!("@ READ: {}", err))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
/// Evaluate the generated ast
|
||||
fn EVAL(ast: MalType, env: &mut Env) -> MalRet {
|
||||
eval(&ast, env).map_err(|err| format!("@ EVAL: {}", err))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
/// Print out the result of the evaluation
|
||||
fn PRINT(output: MalType) -> String {
|
||||
pr_str(&output, true)
|
||||
}
|
||||
|
||||
pub fn rep(input: &str, env: &mut Env) -> Result<String, String> {
|
||||
let ast = READ(input)?;
|
||||
let out = EVAL(ast, env)?;
|
||||
Ok(PRINT(out))
|
||||
}
|
||||
34
src/types.rs
34
src/types.rs
@ -68,6 +68,18 @@ pub fn unescape_str(s: &str) -> String {
|
||||
.replace("\\\"", "\"")
|
||||
}
|
||||
|
||||
/// Extract the car and cdr from a list
|
||||
pub fn car_cdr(list: &[MalType]) -> (&MalType, &[MalType]) {
|
||||
(
|
||||
&list[0],
|
||||
if list.len() > 1 {
|
||||
&list[1..]
|
||||
} else {
|
||||
&list[0..0]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
use MalType::Int;
|
||||
|
||||
fn if_number(val: &MalType) -> Result<isize, String> {
|
||||
@ -77,7 +89,7 @@ fn if_number(val: &MalType) -> Result<isize, String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn int_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet {
|
||||
pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet {
|
||||
if args.is_empty() {
|
||||
return Ok(Int(set));
|
||||
}
|
||||
@ -92,3 +104,23 @@ pub fn int_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> Mal
|
||||
|
||||
Ok(Int(left))
|
||||
}
|
||||
|
||||
use MalType::{Bool, Nil};
|
||||
|
||||
pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet {
|
||||
match args.len() {
|
||||
0 => Err("Comparison requires at least 1 argument".to_string()),
|
||||
_ => {
|
||||
let (left, rights) = car_cdr(args);
|
||||
let mut left = if_number(left)?;
|
||||
for right in rights {
|
||||
let right = if_number(right)?;
|
||||
if !f(left, right) {
|
||||
return Ok(Nil);
|
||||
}
|
||||
left = right;
|
||||
}
|
||||
Ok(Bool(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user