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 <matteo.rogora@live.it>
This commit is contained in:
teo3300
2023-10-25 15:31:44 +09:00
parent 88e9964d9a
commit 21a7c9e695
7 changed files with 140 additions and 42 deletions

View File

@ -32,7 +32,7 @@ macro_rules! env_init {
};
}
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct EnvType {
data: RefCell<MalMap>,
outer: Option<Env>,
@ -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<Env, String> {
let env = env_new(Some(outer));
match binds {
@ -75,7 +77,7 @@ pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env,
_ => {
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")
)
}
}

View File

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

View File

@ -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<String> = 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;

View File

@ -38,7 +38,20 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
.collect::<Vec<String>>()
.join(" ")
),
Fun(func) => format!("{:?}", func),
Fun(_, desc) => format!("{}", desc),
MalFun { .. } => "#<function>".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(&params));
match ast {
List(list) => for el in list {
println!("; {}", prt(&el))},
_ => panic!("Function body is not a list")
}
}

View File

@ -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<String, String> {
let ast = READ(input)?;
let out = EVAL(ast, env.clone())?;
use crate::types::Severity;
pub fn rep(input: &str, env: &Env) -> Result<String, (String, Severity)> {
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))
}

View File

@ -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<MalType>,
@ -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<MalType>;
pub type MalMap = HashMap<String, MalType>;
pub type MalRet = Result<MalType, MalErr>;
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<isize, String> {
match val {
Int(val) => Ok(*val),
_ => Err(format!("{:?} is not a number", val)),
_ => Err(format!("{:?} is not a number", prt(&val))),
}
}

9
test.mal Normal file
View File

@ -0,0 +1,9 @@
(def! func
(fn* (a b c)
(def! d (+ a b))
(- c d)))
(test 1 2 14)
(help test)
(quit)