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:
teo3300
2023-11-21 23:09:39 +09:00
parent 136ef726f3
commit 78dee9c848
8 changed files with 229 additions and 170 deletions

View File

@ -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")
)
}

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

@ -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(&params));
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)));
}

View File

@ -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,

View File

@ -1,22 +1,26 @@
(def! func ; ole
(fn* (a b c) ; this is all a bunch
; "not" logic function
(def! not (fn* (a)
(if a false true)))
"This function does nothing useful"
; binary "or" logic function
(def! or (fn* (a b)
(if a true
(if b
true
false))))
; of useless comments
(def! d (+ a b)) ; to check if the parser
(- c d))) ; is able to ignore them
; binary "and" logic function
(def! and (fn* (a b)
(if a
(if b
true
false)
false)))
(test 1 2 14) (help +)
(help -)(func 1 2 3)
(help test)
(help func)
(help 1)
; Maaged to fix the do form
(do
(+ 2 2)
(+ 3 4))
; I even implemented return statement
(exit (func 1 2 3))
; binary "xor" logic function
(def! xor (fn* (a b)
; use this strange definition to make it independent from
; "or" and "and" definition
(if a
(if b false true)
(if b true false))))