mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
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:
35
src/env.rs
35
src/env.rs
@ -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")
|
||||
)
|
||||
}
|
||||
}
|
||||
25
src/eval.rs
25
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)?),
|
||||
}
|
||||
|
||||
61
src/main.rs
61
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<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;
|
||||
|
||||
@ -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(¶ms));
|
||||
match ast {
|
||||
List(list) => for el in list {
|
||||
println!("; {}", prt(&el))},
|
||||
_ => panic!("Function body is not a list")
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
|
||||
23
src/types.rs
23
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<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))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user