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:
teo3300
2023-07-24 23:31:42 +02:00
parent 6f28ca5df6
commit 67a9c6d0ae
4 changed files with 143 additions and 19 deletions

View File

@ -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))
)
}

View File

@ -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
View 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))
}

View File

@ -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))
}
}
}