From 21a7c9e695944b400a87c38af7b4c655936bd4bc Mon Sep 17 00:00:00 2001 From: teo3300 Date: Wed, 25 Oct 2023 15:31:44 +0900 Subject: [PATCH] Implementing side-stuff - Added error severity, mainly to distinguish between READ and EVAL errors, can be later expanded to allow other distinction - Improved repl using the above function (EVAL errors interrupts the repl right away, without the need to double-return) - Added an helper function to provide infos about builtin and composed functions - Possibility to pass files as arguments to setup the environment Signed-off-by: teo3300 --- src/env.rs | 35 +++++++++++++++---------- src/eval.rs | 25 +++++++++++++++--- src/main.rs | 61 ++++++++++++++++++++++++++++++++++++++----- src/printer.rs | 15 ++++++++++- src/step4_if_fn_do.rs | 14 ++++++---- src/types.rs | 23 +++++++--------- test.mal | 9 +++++++ 7 files changed, 140 insertions(+), 42 deletions(-) create mode 100644 test.mal diff --git a/src/env.rs b/src/env.rs index f652004..8c343eb 100644 --- a/src/env.rs +++ b/src/env.rs @@ -32,7 +32,7 @@ macro_rules! env_init { }; } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct EnvType { data: RefCell, outer: Option, @@ -62,6 +62,8 @@ pub fn env_get(env: &Env, sym: &String) -> MalRet { } } +use crate::printer::prt; + pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result { let env = env_new(Some(outer)); match binds { @@ -75,7 +77,7 @@ pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result { return Err(format!( "Initializing environment: {:?} is not a symbol", - bind + prt(bind) )) } } @@ -90,18 +92,23 @@ use crate::types::MalType::{Fun, Str}; use crate::types::{arithmetic_op, comparison_op}; use std::process::exit; +fn panic() -> MalRet { + panic!("If this messagge occurs, something went terribly wrong") +} + 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| 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)) + "test" => Fun(|_| Ok(Str("This is a test function".to_string())), "Just a test function"), + "quit" => Fun(|_| {exit(0)}, "Quits the program with success status (0)"), + "help" => Fun(|_| {panic()}, "Gets information about the symbols"), + "+" => Fun(|a| arithmetic_op(0, |a, b| a + b, a), "Returns the sum of the arguments"), + "-" => Fun(|a| arithmetic_op(0, |a, b| a - b, a), "Returns the difference of the arguments"), + "*" => Fun(|a| arithmetic_op(1, |a, b| a * b, a), "Returns the product of the arguments"), + "/" => Fun(|a| arithmetic_op(1, |a, b| a / b, a), "Returns the division of the arguments"), + "=" => Fun(|a| comparison_op(|a, b| a == b, a), "Returns true if the arguments are equals, 'nil' otherwise"), + ">" => Fun(|a| comparison_op(|a, b| a > b, a), "Returns true if the arguments are in strictly descending order, 'nil' otherwise"), + "<" => Fun(|a| comparison_op(|a, b| a > b, a), "Returns true if the arguments are in strictly ascending order, 'nil' otherwise"), + ">=" => Fun(|a| comparison_op(|a, b| a >= b, a), "Returns true if the arguments are in descending order, 'nil' otherwise"), + "<=" => Fun(|a| comparison_op(|a, b| a <= b, a), "Returns true if the arguments are in ascending order, 'nil' otherwise") ) -} +} \ No newline at end of file diff --git a/src/eval.rs b/src/eval.rs index b379d4d..7abe2bf 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -3,10 +3,11 @@ use crate::env::{env_binds, env_get, env_new, env_set}; use crate::types::car_cdr; use crate::types::MalType::*; use crate::types::{MalArgs, MalMap, MalRet, MalType}; +use crate::printer::prt; fn call_func(func: &MalType, args: &[MalType]) -> MalRet { match func { - Fun(func) => func(args), + Fun(func, _) => func(args), MalFun { eval, params, @@ -21,7 +22,7 @@ fn call_func(func: &MalType, args: &[MalType]) -> MalRet { _ => Err("This should not happen".to_string()), } } - _ => Err(format!("{:?} is not a function", func)), + _ => Err(format!("{:?} is not a function", prt(func))), } } @@ -56,7 +57,7 @@ fn def_bang_form(list: &[MalType], env: &Env) -> MalRet { } _ => Err(format!( "def! Assigning {:?} to {:?}, which is not a symbol", - &list[1], &list[0] + prt(&list[1]), prt(&list[0]) )), }, _ => Err("def! form: needs 2 arguments".to_string()), @@ -128,6 +129,23 @@ fn fn_star_form(list: &[MalType], env: Env) -> MalRet { }) } +use crate::printer::print_malfun; + +pub fn help_form(list: &[MalType], env: Env) -> MalRet { + for sym in list { + match sym { + Sym(sym_str) => match eval(sym, env.clone())? { + Fun(_, desc) => println!("{}\t[builtin]: {}", sym_str, desc), + MalFun {params, ast, .. } + => print_malfun(sym_str, *params, *ast), + _ => println!("{:?} is not defined as a function", sym_str) + } + _ => println!("{:?} is not a symbol", prt(sym)) + } + } + return Ok(Bool(true)); +} + /// Intermediate function to discern special forms from defined symbols fn apply(list: &MalArgs, env: Env) -> MalRet { let (car, cdr) = car_cdr(list); @@ -137,6 +155,7 @@ fn apply(list: &MalArgs, env: Env) -> MalRet { Sym(sym) if sym == "do" => do_form(cdr, env), Sym(sym) if sym == "if" => if_form(cdr, env), Sym(sym) if sym == "fn*" => fn_star_form(cdr, env), + Sym(sym) if sym == "help" => help_form(cdr, env), // Filter out special forms _ => eval_func(&eval_ast(&List(list.to_vec()), env)?), } diff --git a/src/main.rs b/src/main.rs index 9756f79..91d1586 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,69 @@ // io lib to read input and print output use std::io::{self, Write}; +use std::env::args; mod env; mod eval; mod printer; mod reader; mod types; -use env::env_init; +use env::{Env,env_init}; +use std::fs::File; +use std::io::{BufReader, BufRead}; mod step4_if_fn_do; use step4_if_fn_do::rep; +use crate::types::Severity; + +fn load_file(filename: &str, env: &Env) -> io::Result<()> { + + let file = File::open(filename)?; + let reader = BufReader::new(file); + let mut last: Result<(),()> = Ok(()); + + let mut input = String::new(); + for line in reader.lines() { + match line { + Ok(line) => { + // Read line to compose program input + input.push_str(&line); + + if input == ""{continue;} + + match rep(&input, env) { + Ok(_) => { + last = Ok(()); + input = String::new()}, + Err((err, Severity::Unrecoverable)) => { + last = Ok(()); + println!("; Error @ {}", err); + }, + _ => {last = Err(())} + } + }, + Err(err) => eprintln!("Error reading line: {}", err), + } + } + match last { + Err(()) => println!("; ERROR parsing: '{}'\n; the environment is in an unknown state", filename), + _ => {} + } + Ok(()) +} + fn main() { - let mut num = 0; + let reply_env = env_init(); + // setup env + let args: Vec = args().collect(); + for filename in &args[1..] { + let _ = load_file(filename, &reply_env); + } + + let mut num = 0; + loop { let mut input = String::new(); loop { @@ -32,11 +81,11 @@ fn main() { // Perform rep on whole available input match rep(&input, &reply_env) { Ok(output) => println!("[{}]> {}", num, output), - Err(err) => { - if line != "\n" { - continue; + Err((err, sev)) => { + if sev == Severity::Recoverable && line != "\n" { + continue } - println!("; [{}]> Error {}", num, err); + println!("; [{}]> Error @ {}", num, err); } } num += 1; diff --git a/src/printer.rs b/src/printer.rs index ee31f1b..7bf3f00 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -38,7 +38,20 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String { .collect::>() .join(" ") ), - Fun(func) => format!("{:?}", func), + Fun(_, desc) => format!("{}", desc), MalFun { .. } => "#".to_string(), } } + +pub fn prt(ast: &MalType) -> String { + return pr_str(ast, true) +} + +pub fn print_malfun(sym: &String, params: MalType, ast: MalType) { + print!("; {} {}:\n", sym, prt(¶ms)); + match ast { + List(list) => for el in list { + println!("; {}", prt(&el))}, + _ => panic!("Function body is not a list") + } +} \ No newline at end of file diff --git a/src/step4_if_fn_do.rs b/src/step4_if_fn_do.rs index ff60745..eaea42a 100644 --- a/src/step4_if_fn_do.rs +++ b/src/step4_if_fn_do.rs @@ -13,13 +13,13 @@ 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)) + read_str(input).map_err(|err| format!("READ: {}", err)) } #[allow(non_snake_case)] /// Evaluate the generated ast fn EVAL(ast: MalType, env: Env) -> MalRet { - eval(&ast, env).map_err(|err| format!("@ EVAL: {}", err)) + eval(&ast, env).map_err(|err| format!("EVAL: {}", err)) } #[allow(non_snake_case)] @@ -28,8 +28,12 @@ fn PRINT(output: MalType) -> String { pr_str(&output, true) } -pub fn rep(input: &str, env: &Env) -> Result { - let ast = READ(input)?; - let out = EVAL(ast, env.clone())?; +use crate::types::Severity; + +pub fn rep(input: &str, env: &Env) -> Result { + let ast = READ(input) + .map_err(|err| (err, Severity::Recoverable))?; + let out = EVAL(ast, env.clone()) + .map_err(|err| (err, Severity::Unrecoverable))?; Ok(PRINT(out)) } diff --git a/src/types.rs b/src/types.rs index 210e0d8..c22143f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,12 +2,12 @@ use crate::env::Env; use std::collections::HashMap; // All Mal types should inherit from this -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum MalType { List(MalArgs), Vector(MalArgs), Map(MalMap), - Fun(fn(&[MalType]) -> MalRet), // Used for base functions, implemented using the underlying language (rust) + Fun(fn(&[MalType]) -> MalRet, &'static str), // Used for base functions, implemented using the underlying language (rust) MalFun { eval: fn(ast: &MalType, env: Env) -> MalRet, params: Box, @@ -22,22 +22,19 @@ pub enum MalType { Nil, } -// Stolen, but this way it's easier to handle errors +#[derive(PartialEq)] +pub enum Severity { + Recoverable, + Unrecoverable +} -/* -#[derive(Debug)] -pub enum MalErr { - Str(String), // Messages to the user - // Val(MalType), - // Messages to the program -} TEMP TEMP */ pub type MalErr = String; - pub type MalArgs = Vec; pub type MalMap = HashMap; pub type MalRet = Result; use MalType::{Key, Map, Str}; +use crate::printer::prt; pub fn make_map(list: MalArgs) -> MalRet { if list.len() % 2 != 0 { @@ -52,7 +49,7 @@ pub fn make_map(list: MalArgs) -> MalRet { let v = &list[i + 1]; map.insert(k.to_string(), v.clone()); } - _ => return Err(format!("Map key not valid: {:?}", list[i])), + _ => return Err(format!("Map key not valid: {}", prt(&list[i]))), } } Ok(Map(map)) @@ -92,7 +89,7 @@ use MalType::Int; fn if_number(val: &MalType) -> Result { match val { Int(val) => Ok(*val), - _ => Err(format!("{:?} is not a number", val)), + _ => Err(format!("{:?} is not a number", prt(&val))), } } diff --git a/test.mal b/test.mal new file mode 100644 index 0000000..9212f1c --- /dev/null +++ b/test.mal @@ -0,0 +1,9 @@ +(def! func + (fn* (a b c) + + (def! d (+ a b)) + (- c d))) + +(test 1 2 14) +(help test) +(quit) \ No newline at end of file