mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
Added some definition to core
- "=" operator (for almost any type) - "prn" function - "list" and "list?" - "empty" and "count" Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
138
src/core.rs
138
src/core.rs
@ -1,91 +1,55 @@
|
||||
// This file should contain all the necessary function to define builtin functions
|
||||
use crate::env::{arithmetic_op, car, comparison_op, env_new, env_set, mal_exit, Env};
|
||||
|
||||
use crate::env::env_binds;
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType::{Fun, List, MalFun};
|
||||
use crate::types::{MalErr, MalRet, MalType};
|
||||
|
||||
use MalType::Int;
|
||||
|
||||
pub fn scream() -> MalRet {
|
||||
panic!("If this messagge occurs, something went terribly wrong")
|
||||
}
|
||||
|
||||
pub fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
|
||||
match func {
|
||||
Fun(func, _) => func(args),
|
||||
MalFun {
|
||||
eval,
|
||||
params,
|
||||
ast,
|
||||
env,
|
||||
..
|
||||
} => {
|
||||
let inner_env = env_binds(env.clone(), params, args)?;
|
||||
// It's fine to clone the environment here
|
||||
// since this is when the function is actually called
|
||||
match eval(ast, inner_env)? {
|
||||
List(list) => Ok(list.last().unwrap_or(&Nil).clone()),
|
||||
_ => scream(),
|
||||
// This is the first time I implement a macro, and I'm copying it
|
||||
// so I will comment this a LOT
|
||||
macro_rules! env_init {
|
||||
($outer:expr) => {
|
||||
// match any istance with no args
|
||||
{
|
||||
// the macro prevent the macro from disrupting the external code
|
||||
// this is the block of code that will substitute the macro
|
||||
env_new($outer)
|
||||
// returns an empty map
|
||||
}
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!("{:?} is not a function", prt(func)).as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet {
|
||||
if args.is_empty() {
|
||||
return Ok(Int(set));
|
||||
}
|
||||
|
||||
let mut left = args[0].if_number()?;
|
||||
if args.len() > 1 {
|
||||
let right = &args[1..];
|
||||
for el in right {
|
||||
left = f(left, el.if_number()?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Int(left))
|
||||
}
|
||||
|
||||
use MalType::{Bool, Nil};
|
||||
|
||||
pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet {
|
||||
let (left, rights) = car_cdr(args)?;
|
||||
let mut left = left.if_number()?;
|
||||
for right in rights {
|
||||
let right = right.if_number()?;
|
||||
if !f(left, right) {
|
||||
return Ok(Nil);
|
||||
}
|
||||
left = right;
|
||||
}
|
||||
Ok(Bool(true))
|
||||
}
|
||||
|
||||
/// Extract the car and cdr from a list
|
||||
pub fn car_cdr(list: &[MalType]) -> Result<(&MalType, &[MalType]), MalErr> {
|
||||
match list.len() {
|
||||
0 => Err(MalErr::unrecoverable("Expected at least one argument")),
|
||||
_ => Ok((
|
||||
&list[0],
|
||||
if list.len() > 1 {
|
||||
&list[1..]
|
||||
} else {
|
||||
&list[0..0]
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
pub fn core_exit(list: &[MalType]) -> MalRet {
|
||||
match car_cdr(list)?.0 {
|
||||
Int(val) => exit(*val as i32),
|
||||
_ => exit(-1),
|
||||
};
|
||||
($outer:expr, $($key:expr => $val:expr),*) => {
|
||||
// Only if previous statements did not match,
|
||||
// note that the expression with fat arrow is arbitrary,
|
||||
// could have been slim arrow, comma or any other
|
||||
// recognizable structure
|
||||
{
|
||||
// create an empty map
|
||||
let map = env_init!($outer);
|
||||
$( // Do this for all elements of the arguments list
|
||||
env_set(&map, $key, &$val);
|
||||
)*
|
||||
// return the new map
|
||||
map
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use crate::printer::prt;
|
||||
use crate::types::mal_comp;
|
||||
use crate::types::MalType::{Bool, Fun, Int, List, Nil, Str};
|
||||
|
||||
pub fn ns_init() -> Env {
|
||||
env_init!(None,
|
||||
"test" => Fun(|_| Ok(Str("This is a test function".to_string())), "Just a test function"),
|
||||
"exit" => Fun(mal_exit, "Quits the program with specified status"),
|
||||
"+" => 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 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"),
|
||||
"prn" => Fun(|a| { println!("{} ", prt(car(a)?)); Ok(Nil) }, "Print readably all the arguments passed to it"),
|
||||
"list" => Fun(|a| Ok(List(a.to_vec())), "Return the arguments as a list"),
|
||||
"list?" => Fun(|a| Ok(Bool(matches!(car(a)?, List(_)))), "Return true if the first argument is a list, false otherwise"),
|
||||
"empty?" => Fun(|a| Ok(Bool(car(a)?.if_list()?.is_empty())), "Return true if the first parameter is an empty list, false otherwise, returns an error if the element is not a list"),
|
||||
"count" => Fun(|a| Ok(Int(car(a)?.if_list()?.len() as isize)), "Return the number of elements in the first argument"),
|
||||
"=" => Fun(mal_comp, "Return true if the first two parameters are the same type and content, in case of lists propagate to all elements")
|
||||
)
|
||||
}
|
||||
|
||||
135
src/env.rs
135
src/env.rs
@ -1,38 +1,8 @@
|
||||
use crate::core::{arithmetic_op, comparison_op, core_exit};
|
||||
use crate::types::MalErr;
|
||||
use crate::types::{MalMap, MalRet, MalType};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
// This is the first time I implement a macro, and I'm copying it
|
||||
// so I will comment this a LOT
|
||||
macro_rules! env_init {
|
||||
($outer:expr) => {
|
||||
// match any istance with no args
|
||||
{
|
||||
// the macro prevent the macro from disrupting the external code
|
||||
// this is the block of code that will substitute the macro
|
||||
env_new($outer)
|
||||
// returns an empty map
|
||||
}
|
||||
};
|
||||
($outer:expr, $($key:expr => $val:expr),*) => {
|
||||
// Only if previous statements did not match,
|
||||
// note that the expression with fat arrow is arbitrary,
|
||||
// could have been slim arrow, comma or any other
|
||||
// recognizable structure
|
||||
{
|
||||
// create an empty map
|
||||
let map = env_init!($outer);
|
||||
$( // Do this for all elements of the arguments list
|
||||
env_set(&map, $key, &$val);
|
||||
)*
|
||||
// return the new map
|
||||
map
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnvType {
|
||||
data: RefCell<MalMap>,
|
||||
@ -79,20 +49,93 @@ pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env,
|
||||
Ok(env)
|
||||
}
|
||||
|
||||
use crate::types::MalType::{Fun, Str};
|
||||
|
||||
pub fn env_init() -> Env {
|
||||
env_init!(None,
|
||||
"test" => Fun(|_| Ok(Str("This is a test function".to_string())), "Just a test function"),
|
||||
"exit" => Fun(|a| {core_exit(a)}, "Quits the program with specified status"),
|
||||
"+" => 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")
|
||||
)
|
||||
pub fn scream() -> MalRet {
|
||||
panic!("If this messagge occurs, something went terribly wrong")
|
||||
}
|
||||
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType as M;
|
||||
|
||||
pub fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
|
||||
match func {
|
||||
M::Fun(func, _) => func(args),
|
||||
M::MalFun {
|
||||
eval,
|
||||
params,
|
||||
ast,
|
||||
env,
|
||||
..
|
||||
} => {
|
||||
let inner_env = env_binds(env.clone(), params, args)?;
|
||||
// It's fine to clone the environment here
|
||||
// since this is when the function is actually called
|
||||
match eval(ast, inner_env)? {
|
||||
M::List(list) => Ok(list.last().unwrap_or(&Nil).clone()),
|
||||
_ => scream(),
|
||||
}
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!("{:?} is not a function", prt(func)).as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet {
|
||||
if args.is_empty() {
|
||||
return Ok(M::Int(set));
|
||||
}
|
||||
|
||||
let mut left = args[0].if_number()?;
|
||||
if args.len() > 1 {
|
||||
let right = &args[1..];
|
||||
for el in right {
|
||||
left = f(left, el.if_number()?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(M::Int(left))
|
||||
}
|
||||
|
||||
use MalType::{Bool, Nil};
|
||||
|
||||
pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet {
|
||||
let (left, rights) = car_cdr(args)?;
|
||||
let mut left = left.if_number()?;
|
||||
for right in rights {
|
||||
let right = right.if_number()?;
|
||||
if !f(left, right) {
|
||||
return Ok(Nil);
|
||||
}
|
||||
left = right;
|
||||
}
|
||||
Ok(Bool(true))
|
||||
}
|
||||
|
||||
pub fn car(list: &[MalType]) -> Result<&MalType, MalErr> {
|
||||
match list.len() {
|
||||
0 => Err(MalErr::unrecoverable("Expected at least one argument")),
|
||||
_ => Ok(&list[0]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cdr(list: &[MalType]) -> &[MalType] {
|
||||
if list.len() > 1 {
|
||||
&list[1..]
|
||||
} else {
|
||||
&list[0..0]
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the car and cdr from a list
|
||||
pub fn car_cdr(list: &[MalType]) -> Result<(&MalType, &[MalType]), MalErr> {
|
||||
Ok((car(list)?, cdr(list)))
|
||||
}
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
pub fn mal_exit(list: &[MalType]) -> MalRet {
|
||||
match car_cdr(list)?.0 {
|
||||
MalType::Int(val) => exit(*val as i32),
|
||||
_ => exit(-1),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::core::{call_func, car_cdr};
|
||||
use crate::env::Env;
|
||||
use crate::env::{call_func, car_cdr};
|
||||
use crate::env::{env_get, env_new, env_set};
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType as M;
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@ -10,17 +10,17 @@ mod reader;
|
||||
mod step4_if_fn_do;
|
||||
mod types;
|
||||
|
||||
use env::env_init;
|
||||
use core::ns_init;
|
||||
use parse_tools::{interactive, load_file};
|
||||
|
||||
fn main() {
|
||||
let reply_env = env_init();
|
||||
let reply_env = ns_init();
|
||||
|
||||
// setup env
|
||||
let args: Vec<String> = args().collect();
|
||||
for filename in &args[1..] {
|
||||
let _ = load_file(filename, &reply_env);
|
||||
}
|
||||
//let args: Vec<String> = args().collect();
|
||||
args().collect::<Vec<String>>()[1..]
|
||||
.iter()
|
||||
.for_each(|f| load_file(f, &reply_env));
|
||||
|
||||
interactive(reply_env);
|
||||
}
|
||||
|
||||
@ -6,8 +6,15 @@ use regex::Regex;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
|
||||
pub fn load_file(filename: &str, env: &Env) -> io::Result<()> {
|
||||
let file = File::open(filename)?;
|
||||
pub fn load_file(filename: &str, env: &Env) {
|
||||
let file_desc = File::open(filename);
|
||||
let file = match file_desc {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
println!("Unable to open file: '{}'", filename);
|
||||
exit(1)
|
||||
}
|
||||
};
|
||||
let reader = BufReader::new(file);
|
||||
let mut last: Result<Vec<String>, MalErr> = Ok(Vec::new());
|
||||
|
||||
@ -18,9 +25,9 @@ pub fn load_file(filename: &str, env: &Env) -> io::Result<()> {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
// Read line to compose program inpu
|
||||
|
||||
if line.is_empty() || comment_line.is_match(&line) {
|
||||
continue; // Don't even add it
|
||||
last = Ok(Vec::new());
|
||||
continue;
|
||||
} else {
|
||||
parser.push(&line);
|
||||
}
|
||||
@ -46,10 +53,10 @@ pub fn load_file(filename: &str, env: &Env) -> io::Result<()> {
|
||||
error.message()
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::exit;
|
||||
|
||||
pub fn interactive(env: Env) {
|
||||
let mut num = 0;
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::types::escape_str;
|
||||
use crate::types::MalType;
|
||||
use crate::types::MalType as M;
|
||||
use crate::types::{escape_str, MalType};
|
||||
|
||||
fn key_str(val: &str) -> MalType {
|
||||
if val.starts_with('ʞ') {
|
||||
M::Key(val.to_string())
|
||||
} else {
|
||||
M::Str(val.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
|
||||
match ast {
|
||||
@ -36,7 +43,11 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
|
||||
M::Map(el) => format!(
|
||||
"{{{}}}",
|
||||
el.iter()
|
||||
.map(|sub| [sub.0.to_string(), pr_str(sub.1, print_readably)].join(" "))
|
||||
.map(|sub| [
|
||||
pr_str(&key_str(sub.0), print_readably),
|
||||
pr_str(sub.1, print_readably)
|
||||
]
|
||||
.join(" "))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
),
|
||||
@ -53,7 +64,7 @@ pub fn print_malfun(sym: &str, params: Rc<MalType>, ast: Rc<MalType>) {
|
||||
println!("{}\t[function]: {}", sym, prt(¶ms));
|
||||
ast.as_ref()
|
||||
.if_list()
|
||||
.unwrap_or_else(|_| &[])
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.for_each(|el| println!("; {}", prt(el)));
|
||||
.for_each(|el| println!("; {}", pr_str(el, true)));
|
||||
}
|
||||
|
||||
32
src/types.rs
32
src/types.rs
@ -1,4 +1,4 @@
|
||||
use crate::env::Env;
|
||||
use crate::env::{car_cdr, Env};
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
// All Mal types should inherit from this
|
||||
@ -52,6 +52,36 @@ impl MalType {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::types::MalType as M;
|
||||
|
||||
fn mal_eq(a: &M, b: &M) -> MalRet {
|
||||
Ok(M::Bool(match (a, b) {
|
||||
(M::Nil, M::Nil) => true,
|
||||
(M::Bool(a), M::Bool(b)) => a == b,
|
||||
(M::Int(a), M::Int(b)) => a == b,
|
||||
(M::Key(a), M::Key(b))
|
||||
| (M::Str(a), M::Str(b)) => a == b,
|
||||
(M::List(a), M::List(b))
|
||||
| (M::Vector(a), M::Vector(b)) => a
|
||||
.iter()
|
||||
.zip(b.iter())
|
||||
.all(|(a, b)| matches!(mal_eq(a, b), Ok(M::Bool(true)))),
|
||||
_ => {
|
||||
return Err(MalErr::unrecoverable(
|
||||
"Comparison not implemented for 'Map', 'Fun', 'MalFun' and 'Sym'",
|
||||
))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn mal_comp(args: &[MalType]) -> MalRet {
|
||||
let (car, cdr) = car_cdr(args)?;
|
||||
match cdr.len() {
|
||||
0 => Ok(M::Bool(true)),
|
||||
_ => mal_eq(car, &cdr[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum Severity {
|
||||
Recoverable,
|
||||
|
||||
Reference in New Issue
Block a user