mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
Improved file loading and repl
- multiple line instruction - multiple intstruction line Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
26
src/env.rs
26
src/env.rs
@ -1,4 +1,4 @@
|
||||
use crate::types::MalType::*;
|
||||
use crate::types::{MalErr, MalType::*};
|
||||
use crate::types::{MalMap, MalRet, MalType};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
@ -32,7 +32,7 @@ macro_rules! env_init {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnvType {
|
||||
data: RefCell<MalMap>,
|
||||
outer: Option<Env>,
|
||||
@ -57,34 +57,36 @@ pub fn env_get(env: &Env, sym: &String) -> MalRet {
|
||||
Some(val) => Ok(val.clone()),
|
||||
None => match env.outer.clone() {
|
||||
Some(outer) => env_get(&outer, sym),
|
||||
None => Err(format!("symbol {:?} not defined", sym)),
|
||||
None => Err(MalErr::unrecoverable(
|
||||
format!("symbol {:?} not defined", sym).as_str(),
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
use crate::printer::prt;
|
||||
|
||||
pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env, String> {
|
||||
pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env, MalErr> {
|
||||
let env = env_new(Some(outer));
|
||||
match binds {
|
||||
List(binds) => {
|
||||
if binds.len() != exprs.len() {
|
||||
return Err("Env init with unmatched length".to_string());
|
||||
return Err(MalErr::unrecoverable("Env init with unmatched length"));
|
||||
} // 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) => env_set(&env, sym, expr),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Initializing environment: {:?} is not a symbol",
|
||||
prt(bind)
|
||||
return Err(MalErr::unrecoverable(
|
||||
format!("Initializing environment: {:?} is not a symbol", prt(bind))
|
||||
.as_str(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(env)
|
||||
}
|
||||
_ => Err("init: first argument must be a list".to_string()),
|
||||
_ => Err(MalErr::unrecoverable("init: first argument must be a list")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +94,7 @@ use crate::types::MalType::{Fun, Str};
|
||||
use crate::types::{arithmetic_op, comparison_op};
|
||||
use std::process::exit;
|
||||
|
||||
fn panic() -> MalRet {
|
||||
pub fn scream() -> MalRet {
|
||||
panic!("If this messagge occurs, something went terribly wrong")
|
||||
}
|
||||
|
||||
@ -100,7 +102,7 @@ pub fn env_init() -> Env {
|
||||
env_init!(None,
|
||||
"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"),
|
||||
"help" => Fun(|_| {scream()}, "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"),
|
||||
@ -111,4 +113,4 @@ pub fn env_init() -> Env {
|
||||
">=" => 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")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
51
src/eval.rs
51
src/eval.rs
@ -1,9 +1,9 @@
|
||||
use crate::env::Env;
|
||||
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::env::{scream, Env};
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType::*;
|
||||
use crate::types::{car_cdr, MalErr};
|
||||
use crate::types::{MalArgs, MalMap, MalRet, MalType};
|
||||
|
||||
fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
|
||||
match func {
|
||||
@ -19,10 +19,12 @@ fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
|
||||
// since this is when the function is actually called
|
||||
match eval(ast, inner_env)? {
|
||||
List(list) => Ok(list.last().unwrap_or(&Nil).clone()),
|
||||
_ => Err("This should not happen".to_string()),
|
||||
_ => scream(),
|
||||
}
|
||||
}
|
||||
_ => Err(format!("{:?} is not a function", prt(func))),
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!("{:?} is not a function", prt(func)).as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,12 +57,16 @@ fn def_bang_form(list: &[MalType], env: &Env) -> MalRet {
|
||||
env_set(&env, sym.as_str(), &cdr);
|
||||
Ok(cdr)
|
||||
}
|
||||
_ => Err(format!(
|
||||
"def! Assigning {:?} to {:?}, which is not a symbol",
|
||||
prt(&list[1]), prt(&list[0])
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!(
|
||||
"def! Assigning {:?} to {:?}, which is not a symbol",
|
||||
prt(&list[1]),
|
||||
prt(&list[0])
|
||||
)
|
||||
.as_str(),
|
||||
)),
|
||||
},
|
||||
_ => Err("def! form: needs 2 arguments".to_string()),
|
||||
_ => Err(MalErr::unrecoverable("def! form: needs 2 arguments")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +91,9 @@ fn let_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
eval(&cdr[0], inner_env)
|
||||
}
|
||||
}
|
||||
_ => Err("First argument of let* must be a list of pair definitions".to_string()),
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
"First argument of let* must be a list of pair definitions",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,17 +102,19 @@ fn let_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
/// result of the last evaluation
|
||||
fn do_form(list: &[MalType], env: Env) -> MalRet {
|
||||
if list.is_empty() {
|
||||
return Err("do form: provide a list as argument".to_string());
|
||||
return Err(MalErr::unrecoverable("do form: provide a list as argument"));
|
||||
}
|
||||
match eval_ast(&list[0], env)? {
|
||||
List(list) => Ok(list.last().unwrap_or(&Nil).clone()),
|
||||
_ => Err("do form: argument must be a list".to_string()),
|
||||
_ => Err(MalErr::unrecoverable("do form: argument must be a list")),
|
||||
}
|
||||
}
|
||||
|
||||
fn if_form(list: &[MalType], env: Env) -> MalRet {
|
||||
if !(2..=3).contains(&list.len()) {
|
||||
return Err("if form: number of arguments".to_string());
|
||||
return Err(MalErr::unrecoverable(
|
||||
"if form: number of arguments is wrong",
|
||||
));
|
||||
}
|
||||
let (cond, branches) = car_cdr(list);
|
||||
match eval(cond, env.clone())? {
|
||||
@ -118,7 +128,7 @@ fn if_form(list: &[MalType], env: Env) -> MalRet {
|
||||
|
||||
fn fn_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
if list.is_empty() {
|
||||
return Err("fn* form: specify lambda arguments".to_string());
|
||||
return Err(MalErr::unrecoverable("fn* form: specify lambda arguments"));
|
||||
}
|
||||
let (binds, exprs) = car_cdr(list);
|
||||
Ok(MalFun {
|
||||
@ -136,11 +146,10 @@ pub fn help_form(list: &[MalType], env: Env) -> MalRet {
|
||||
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))
|
||||
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));
|
||||
@ -173,7 +182,7 @@ pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
}
|
||||
|
||||
/// Separately evaluate all elements in a collection (list or vector)
|
||||
fn eval_collection(list: &MalArgs, env: Env) -> Result<MalArgs, String> {
|
||||
fn eval_collection(list: &MalArgs, env: Env) -> Result<MalArgs, MalErr> {
|
||||
let mut ret = MalArgs::new();
|
||||
for el in list {
|
||||
ret.push(eval(el, env.clone())?);
|
||||
|
||||
62
src/main.rs
62
src/main.rs
@ -1,62 +1,66 @@
|
||||
// io lib to read input and print output
|
||||
use std::io::{self, Write};
|
||||
use std::env::args;
|
||||
use std::io::{self, Write};
|
||||
|
||||
mod env;
|
||||
mod eval;
|
||||
mod printer;
|
||||
mod reader;
|
||||
mod types;
|
||||
use env::{Env,env_init};
|
||||
use env::{env_init, Env};
|
||||
use regex::Regex;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufRead};
|
||||
use std::io::{BufRead, BufReader};
|
||||
use types::MalErr;
|
||||
|
||||
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 last: Result<Vec<String>, MalErr> = Ok(Vec::new());
|
||||
|
||||
let comment_line = Regex::new(r#"^[\s]*;.*"#).unwrap();
|
||||
|
||||
let mut input = String::new();
|
||||
for line in reader.lines() {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
// Read line to compose program input
|
||||
input.push_str(&line);
|
||||
// Read line to compose program inpu
|
||||
|
||||
// TODO: Horrible way to deal with this but I don't want to fix it now
|
||||
if input == "" || input.chars().next() == Some(';') {
|
||||
continue;
|
||||
if line == "" || comment_line.is_match(&line) {
|
||||
continue; // Don't even add it
|
||||
} else {
|
||||
input.push_str(format!("{}\n", &line).as_str());
|
||||
}
|
||||
|
||||
match rep(&input, env) {
|
||||
Ok(_) => {
|
||||
last = Ok(());
|
||||
input = String::new()},
|
||||
Err((err, Severity::Unrecoverable)) => {
|
||||
last = Ok(());
|
||||
println!("; Error @ {}", err);
|
||||
},
|
||||
_ => {last = Err(())}
|
||||
last = match rep(&input, env) {
|
||||
Err(error) if error.is_recoverable() => Err(error),
|
||||
tmp => {
|
||||
input = String::new();
|
||||
tmp.map_err(|error| {
|
||||
println!("; Error @ {}", error.message());
|
||||
error
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(err) => eprintln!("Error reading line: {}", err),
|
||||
}
|
||||
}
|
||||
match last {
|
||||
Err(()) => println!("; ERROR parsing: '{}'\n; the environment is in an unknown state", filename),
|
||||
Err(error) => println!(
|
||||
"; ERROR parsing: '{}'\n; {}\n; the environment is in an unknown state",
|
||||
filename,
|
||||
error.message()
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
let reply_env = env_init();
|
||||
|
||||
// setup env
|
||||
@ -83,12 +87,12 @@ fn main() {
|
||||
if input != "\n" {
|
||||
// Perform rep on whole available input
|
||||
match rep(&input, &reply_env) {
|
||||
Ok(output) => println!("[{}]> {}", num, output),
|
||||
Err((err, sev)) => {
|
||||
if sev == Severity::Recoverable && line != "\n" {
|
||||
continue
|
||||
Ok(output) => output.iter().for_each(|el| println!("[{}]> {}", num, el)),
|
||||
Err(error) => {
|
||||
if error.is_recoverable() && line != "\n" {
|
||||
continue;
|
||||
}
|
||||
println!("; [{}]> Error @ {}", num, err);
|
||||
println!("; [{}]> Error @ {}", num, error.message());
|
||||
}
|
||||
}
|
||||
num += 1;
|
||||
|
||||
@ -44,14 +44,17 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
|
||||
}
|
||||
|
||||
pub fn prt(ast: &MalType) -> String {
|
||||
return pr_str(ast, true)
|
||||
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")
|
||||
List(list) => {
|
||||
for el in list {
|
||||
println!("; {}", prt(&el))
|
||||
}
|
||||
}
|
||||
_ => panic!("Function body is not a list"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
// Specyfy components in "types"
|
||||
use crate::types::*;
|
||||
// By specifying enum variants it's possible to omit namespace
|
||||
@ -7,34 +10,36 @@ use regex::Regex;
|
||||
|
||||
pub struct Reader {
|
||||
tokens: Vec<String>,
|
||||
ptr: usize,
|
||||
ptr: Cell<usize>,
|
||||
}
|
||||
|
||||
type Tokens = Vec<String>;
|
||||
|
||||
// TODO: instead of panic on missing ")" try implementing a multi line parsing
|
||||
// Status on return should always be The last element of the last opened lists
|
||||
// (append to the "last" list) while traversing
|
||||
impl Reader {
|
||||
fn new(tokens: Vec<String>) -> Reader {
|
||||
Reader { tokens, ptr: 0 }
|
||||
pub fn new(input: &str) -> Reader {
|
||||
Reader { tokens: tokenize(input), ptr: Cell::new(0) }
|
||||
}
|
||||
|
||||
// May be improved
|
||||
fn get_token(&self, i: usize) -> Result<String, MalErr> {
|
||||
self.tokens
|
||||
.get(i)
|
||||
.ok_or("Unexpected EOF".to_string())
|
||||
.ok_or(MalErr::recoverable("Unexpected EOF"))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Returns the token at the current position
|
||||
fn peek(&self) -> Result<String, MalErr> {
|
||||
self.get_token(self.ptr)
|
||||
self.get_token(self.ptr.get())
|
||||
}
|
||||
|
||||
/// Returns the token at current position and increment current position
|
||||
fn next(&mut self) -> Result<String, MalErr> {
|
||||
self.ptr += 1;
|
||||
self.get_token(self.ptr - 1)
|
||||
fn next(&self) -> Result<String, MalErr> {
|
||||
self.ptr.set(self.ptr.get() + 1);
|
||||
self.get_token(self.ptr.get() - 1)
|
||||
}
|
||||
|
||||
/// Repeatedly calls `read_form` of the reader object until it finds a ")" token
|
||||
@ -42,7 +47,7 @@ impl Reader {
|
||||
/// Accumulates results into a MalList
|
||||
/// NOTE: `read_list` calls `read_form` -> enable recursion
|
||||
/// (lists can contains other lists)
|
||||
fn read_list(&mut self, terminator: &str) -> MalRet {
|
||||
fn read_list(&self, terminator: &str) -> MalRet {
|
||||
self.next()?;
|
||||
|
||||
let mut vector = MalArgs::new();
|
||||
@ -56,14 +61,16 @@ impl Reader {
|
||||
")" => Ok(List(vector)),
|
||||
"]" => Ok(Vector(vector)),
|
||||
"}" => make_map(vector),
|
||||
_ => Err("Unknown collection terminator".to_string()),
|
||||
t => Err(MalErr::unrecoverable(
|
||||
format!("Unknown collection terminator: {}", t).as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read atomic token and return appropriate scalar ()
|
||||
fn read_atom(&mut self) -> MalRet {
|
||||
fn read_atom(&self) -> MalRet {
|
||||
match &self.next()?[..] {
|
||||
")" | "]" | "}" => Err("Missing open parenthesis".to_string()),
|
||||
")" | "]" | "}" => Err(MalErr::unrecoverable("Missing open parenthesis")),
|
||||
"false" => Ok(Bool(false)),
|
||||
"true" => Ok(Bool(true)),
|
||||
"nil" => Ok(Nil),
|
||||
@ -86,7 +93,7 @@ impl Reader {
|
||||
/// Switch on the first character
|
||||
/// "(" -> call `read_list`
|
||||
/// otherwise -> call `read_atom`
|
||||
fn read_form(&mut self) -> MalRet {
|
||||
fn read_form(&self) -> MalRet {
|
||||
// String slice containing the whole string
|
||||
match &self.peek()?[..] {
|
||||
// Consume "(" and parse list
|
||||
@ -96,22 +103,27 @@ impl Reader {
|
||||
_ => self.read_atom(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ended(&self) -> bool {
|
||||
self.tokens.len() == self.ptr.get()
|
||||
}
|
||||
}
|
||||
|
||||
/// Call `tokenize` on a string
|
||||
/// Create anew Reader with the tokens
|
||||
/// Call read_from with the reader instance
|
||||
pub fn read_str(input: &str) -> MalRet {
|
||||
Reader::new(tokenize(input)).read_form()
|
||||
pub fn read_str(reader: &Reader) -> MalRet {
|
||||
reader.read_form()
|
||||
}
|
||||
|
||||
/// Read a string and return a list of tokens in it (following regex in README)
|
||||
// Add error handling for strings that are not terminated
|
||||
fn tokenize(input: &str) -> Vec<String> {
|
||||
Regex::new(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*\n|[^\s\[\]{}('"`,;)]*)"###)
|
||||
fn tokenize(input: &str) -> Tokens {
|
||||
let tokens = Regex::new(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*\n|[^\s\[\]{}('"`,;)]*)"###)
|
||||
.unwrap()
|
||||
.captures_iter(input)
|
||||
.map(|e| e[1].to_string())
|
||||
.filter(|e| !e.is_empty() || !e.starts_with(';'))
|
||||
.collect::<Vec<String>>()
|
||||
.filter(|e| !(e.is_empty() || e.starts_with(';')))
|
||||
.collect::<Vec<String>>();
|
||||
tokens
|
||||
}
|
||||
|
||||
@ -7,19 +7,19 @@
|
||||
use crate::env::Env;
|
||||
use crate::eval::eval;
|
||||
use crate::printer::pr_str;
|
||||
use crate::reader::read_str;
|
||||
use crate::types::{MalRet, MalType};
|
||||
use crate::reader::{read_str, Reader};
|
||||
use crate::types::{MalErr, 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))
|
||||
fn READ(input: &Reader) -> MalRet {
|
||||
read_str(input).map_err(|err| MalErr::new(format!("READ: {}", err.message()), err.severity()))
|
||||
}
|
||||
|
||||
#[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| MalErr::new(format!("EVAL: {}", err.message()), err.severity()))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
@ -28,12 +28,13 @@ fn PRINT(output: MalType) -> String {
|
||||
pr_str(&output, true)
|
||||
}
|
||||
|
||||
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))
|
||||
pub fn rep(input: &str, env: &Env) -> Result<Vec<String>, MalErr> {
|
||||
let reader = Reader::new(input);
|
||||
let mut ret_str = Vec::new();
|
||||
loop {
|
||||
let ast = READ(&reader)?;
|
||||
let out = EVAL(ast, env.clone())?;
|
||||
ret_str.push(PRINT(out));
|
||||
if reader.ended(){break Ok(ret_str);}
|
||||
}
|
||||
}
|
||||
|
||||
61
src/types.rs
61
src/types.rs
@ -2,7 +2,7 @@ use crate::env::Env;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// All Mal types should inherit from this
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MalType {
|
||||
List(MalArgs),
|
||||
Vector(MalArgs),
|
||||
@ -22,23 +22,56 @@ pub enum MalType {
|
||||
Nil,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum Severity {
|
||||
Recoverable,
|
||||
Unrecoverable
|
||||
Unrecoverable,
|
||||
}
|
||||
|
||||
pub struct MalErr {
|
||||
message: String,
|
||||
severity: Severity,
|
||||
}
|
||||
|
||||
impl MalErr {
|
||||
pub fn new(message: String, severity: Severity) -> Self {
|
||||
Self {
|
||||
message: message,
|
||||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> String {
|
||||
self.message.to_string()
|
||||
}
|
||||
|
||||
pub fn severity(&self) -> Severity {
|
||||
self.severity
|
||||
}
|
||||
|
||||
pub fn is_recoverable(&self) -> bool {
|
||||
self.severity == Severity::Recoverable
|
||||
}
|
||||
|
||||
pub fn recoverable(message: &str) -> Self {
|
||||
Self::new(message.to_owned(), Severity::Recoverable)
|
||||
}
|
||||
|
||||
pub fn unrecoverable(message: &str) -> Self {
|
||||
Self::new(message.to_owned(), Severity::Unrecoverable)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
use MalType::{Key, Map, Str};
|
||||
|
||||
pub fn make_map(list: MalArgs) -> MalRet {
|
||||
if list.len() % 2 != 0 {
|
||||
return Err("Map length is odd: missing value".to_string());
|
||||
return Err(MalErr::unrecoverable("Map length is odd: missing value"));
|
||||
}
|
||||
|
||||
let mut map = MalMap::new();
|
||||
@ -49,7 +82,11 @@ 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: {}", prt(&list[i]))),
|
||||
_ => {
|
||||
return Err(MalErr::unrecoverable(
|
||||
format!("Map key not valid: {}", prt(&list[i])).as_str(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Map(map))
|
||||
@ -86,10 +123,12 @@ pub fn car_cdr(list: &[MalType]) -> (&MalType, &[MalType]) {
|
||||
|
||||
use MalType::Int;
|
||||
|
||||
fn if_number(val: &MalType) -> Result<isize, String> {
|
||||
fn if_number(val: &MalType) -> Result<isize, MalErr> {
|
||||
match val {
|
||||
Int(val) => Ok(*val),
|
||||
_ => Err(format!("{:?} is not a number", prt(&val))),
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!("{:?} is not a number", prt(&val)).as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +152,9 @@ 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()),
|
||||
0 => Err(MalErr::unrecoverable(
|
||||
"Comparison requires at least 1 argument",
|
||||
)),
|
||||
_ => {
|
||||
let (left, rights) = car_cdr(args);
|
||||
let mut left = if_number(left)?;
|
||||
|
||||
Reference in New Issue
Block a user