Merge branch 'dev'

Implemented up until step6, most of the core primiives have been moved
inside a mal file: core.mal

Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
teo3300
2024-01-18 19:26:27 +09:00
21 changed files with 939 additions and 365 deletions

3
.gitignore vendored
View File

@ -5,3 +5,6 @@ src/#*
# emacs save
src/*~
# history file
.mal-history

289
Cargo.lock generated
View File

@ -4,24 +4,147 @@ version = 3
[[package]]
name = "aho-corasick"
version = "1.0.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "memchr"
version = "2.5.0"
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clipboard-win"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
dependencies = [
"error-code",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "error-code"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
[[package]]
name = "fd-lock"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93f7a0db71c99f68398f80653ed05afb0b00e062e1a20c7ff849c4edfabbbcc"
dependencies = [
"cfg-if",
"rustix",
"windows-sys",
]
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
]
[[package]]
name = "libc"
version = "0.2.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "regex"
version = "1.8.3"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
@ -30,13 +153,161 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.7.2"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rust-mal"
version = "0.1.0"
dependencies = [
"regex",
"rustyline",
]
[[package]]
name = "rustix"
version = "0.38.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustyline"
version = "13.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
dependencies = [
"bitflags",
"cfg-if",
"clipboard-win",
"fd-lock",
"home",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]]
name = "smallvec"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

View File

@ -6,4 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.8.3"
regex = "1.10.2" # An implementation of regular expressions for Rust. This implementation uses finite automata …
rustyline = "13.0.0" # Rustyline, a readline implementation based on Antirez's Linenoise

83
core/core.mal Normal file
View File

@ -0,0 +1,83 @@
;; Previously in core.rs
; Logic
(def! not (fn* [x]
(if x nil true)))
(def! and (fn* [a b]
(if a
(if b true))))
(def! or (fn* [a b]
(if a
true
(if b true))))
(def! xor (fn* [a b]
(if a
(not b)
(if b true))))
; Arithmetic
(def! > (fn* [a b]
(< b a)))
(def! >= (fn* [a b]
(not (< a b))))
(def! <= (fn* [a b]
(>= b a)))
(def! + (fn* [a b]
(- a (- 0 b))))
(def! abs (fn* [a]
(if (> a 0)
a
(- 0 a))))
(def! sign-product (fn* [op a b acc]
(let* [res (op (abs a) (abs b) acc)]
(if (xor (< 0 a) (< 0 b))
(- 0 res)
res))))
(def! * (fn* [a b]
"Recursive product based on sum"
(sign-product *-rec a b 0)))
(def! *-rec (fn* [a b acc]
(if (= b 0)
acc
(*-rec a (- b 1) (+ acc a)))))
(def! / (fn* [a b]
"recursive division based on subtraction"
(if (= b 0)
(raise "Attempting division by 0")
(sign-product /-rec a b 0))))
(def! /-rec (fn* [a b acc]
(if (>= (- a b) 0)
(/-rec (- a b) b (+ acc 1))
acc)))
(def! mod (fn* [a b]
(- a (* (/ a b) b))))
; Other functions in core.rs
(def! assert-eq (fn* [a b]
(assert (= a b))))
(def! empty? (fn* [l]
(= (count l) 0)))
;; File-interaction functions
(def! load-file (fn* [f]
(eval (read-string (str "(do " (slurp f) "\nnil)")))))
(def! conf-reload (fn* []
(load-file (str MAL_HOME "/" "config.mal"))))
;; Shorthand
(def! quit (fn* []
(exit 0)))

View File

@ -1,4 +1,6 @@
use crate::env::{arithmetic_op, car, comparison_op, env_new, env_set, mal_exit, Env};
use std::env;
use crate::env::{car, env_new, env_set, mal_exit, num_op, Env};
// This is the first time I implement a macro, and I'm copying it
// so I will comment this a LOT
@ -29,32 +31,34 @@ macro_rules! env_init {
};
}
use crate::printer::prt;
use crate::parse_tools::read_file;
use crate::printer::pr_str;
use crate::reader::{read_str, Reader};
use crate::types::MalType::{Bool, Fun, Int, List, Nil, Str};
use crate::types::{mal_assert, mal_comp, MalArgs};
use crate::types::{mal_assert, mal_comp, MalArgs, MalErr};
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| {a.iter().for_each(|a| print!("{} ", prt(a))); println!(); Ok(Nil) }, "Print readably all the arguments"),
"list" => Fun(|a| Ok(List(MalArgs::new(a.to_vec()))), "Return the arguments as a list"),
"list?" => Fun(|a| Ok(Bool(a.iter().all(|el| matches!(el, 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"),
"and" => Fun(|a| Ok(Bool(a.iter().all(|a| !matches!(a, Nil | Bool(false))))), "Returns false if at least one of the arguments is 'false' or 'nil', true otherwise"),
"or" => Fun(|a| Ok(Bool(a.iter().any(|a| !matches!(a, Nil | Bool(false))))), "Returns false if all the arguments are 'false' or 'nil', true otherwise"),
"xor" => Fun(|a| Ok(Bool(a.iter().filter(|a| !matches!(a, Nil | Bool(false))).count() == 1)), "Returns true if one of the arguments is different from 'nil' or 'false', false otherwise"),
"not" => Fun(|a| Ok(Bool(matches!(car(a)?, Nil | Bool(false)))), "Negate 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"),
"assert" => Fun(mal_assert, "Return an error if assertion fails")
// That's it, you are all going to be simpler functions
"exit" => Fun(mal_exit, "Quits the program with specified status"),
"raise" => Fun(|a| Err(MalErr::unrecoverable(car(a)?.if_string()?)), "Raise an unrecoverable error with the specified message"),
"-" => Fun(|a| num_op(|a, b| Int(a - b), a), "Returns the difference of the arguments"),
"*" => Fun(|a| num_op(|a, b| Int(a * b), a), "Returns the product of the arguments"),
"<" => Fun(|a| num_op(|a, b| Bool(a < b), a), "Returns true if the first argument is strictly smallerthan the second one, nil otherwise"),
"pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::<Vec<String>>().join(" "))), "Print readably all arguments"),
"str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::<Vec<String>>().join(""))), "Print non readably all arguments"),
"prn" => Fun(|a| {a.iter().for_each(|a| print!("{} ", pr_str(a, false))); Ok(Nil) }, "Print readably all the arguments"),
"println" => Fun(|a| {a.iter().for_each(|a| print!("{} ", pr_str(a, false))); println!(); Ok(Nil) }, "Print readably all the arguments"),
"list" => Fun(|a| Ok(List(MalArgs::new(a.to_vec()))), "Return the arguments as a list"),
"list?" => Fun(|a| Ok(Bool(a.iter().all(|el| matches!(el, List(_))))), "Return true if the first argument is a list, false otherwise"),
"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 (NOT IMPLEMENTED for 'Map', 'Fun' and 'MalFun')"),
"assert" => Fun(mal_assert, "Return an error if assertion fails"),
"read-string" => Fun(|a| read_str(Reader::new().push(car(a)?.if_string()?)).map_err(MalErr::severe), "Tokenize and read the first argument"),
"slurp" => Fun(|a| Ok(Str(read_file(car(a)?.if_string()?)?)), "Read a file and return the content as a string"),
"env" => Fun(|a| match env::var(car(a)?.if_string()?) {
Ok(s) => Ok(Str(s)),
_ => Ok(Nil),
}, "Retrieve the specified environment variable, returns NIL if that variable does not exist")
)
}

View File

@ -3,10 +3,23 @@ use crate::types::{MalMap, MalRet, MalType};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct EnvType {
data: RefCell<MalMap>,
outer: Option<Env>,
pub outer: Option<Env>,
}
impl EnvType {
pub fn keys(&self) -> Vec<String> {
let mut keys = self
.data
.borrow()
.iter()
.map(|(k, _)| k.clone())
.collect::<Vec<String>>();
keys.sort_unstable();
keys
}
}
pub type Env = Rc<EnvType>;
@ -38,7 +51,7 @@ pub fn env_get(env: &Env, sym: &str) -> MalRet {
pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env, MalErr> {
let env = env_new(Some(outer));
let binds = binds.if_list()?;
let binds = binds.if_vec()?;
if binds.len() != exprs.len() {
return Err(MalErr::unrecoverable(
format!("Expected {} args, got {}", binds.len(), exprs.len()).as_str(),
@ -51,18 +64,26 @@ pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env,
Ok(env)
}
pub fn scream() -> MalRet {
panic!("If this messagge occurs, something went terribly wrong")
macro_rules! scream {
() => {
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 {
pub enum CallFunc {
Builtin(MalType),
MalFun(MalType, Env),
}
pub type CallRet = Result<CallFunc, MalErr>;
pub fn call_func(func: &MalType, args: &[MalType]) -> CallRet {
match func {
M::Fun(func, _) => func(args),
M::Fun(func, _) => Ok(CallFunc::Builtin(func(args)?)),
M::MalFun {
eval,
// eval,
params,
ast,
env,
@ -71,9 +92,12 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
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(),
match ast.as_ref() {
M::List(list) => Ok(CallFunc::MalFun(
list.last().unwrap_or(&Nil).clone(),
inner_env,
)),
_ => scream!(),
}
}
_ => Err(MalErr::unrecoverable(
@ -82,39 +106,22 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
}
}
pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet {
Ok(M::Int(match args.len() {
0 => set,
1 => f(set, args[0].if_number()?),
_ => {
// TODO: Maybe an accumulator
let mut left = args[0].if_number()?;
for el in &args[1..] {
left = f(left, el.if_number()?);
}
left
}
}))
pub fn bin_unpack(list: &[MalType]) -> Result<(&MalType, &MalType), MalErr> {
if list.len() != 2 {
return Err(MalErr::unrecoverable("Two arguments required"));
}
Ok((&list[0], &list[1]))
}
use MalType::{Bool, Nil};
pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet {
if args.is_empty() {
return Ok(Nil);
}
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 num_op(f: fn(isize, isize) -> MalType, args: &[MalType]) -> MalRet {
let (car, cdr) = bin_unpack(args)?;
let car = car.if_number()?;
let cdr = cdr.if_number()?;
Ok(f(car, cdr))
}
use MalType::Nil;
pub fn car(list: &[MalType]) -> Result<&MalType, MalErr> {
match list.len() {
0 => Err(MalErr::unrecoverable("Expected at least one argument")),
@ -135,10 +142,30 @@ pub fn car_cdr(list: &[MalType]) -> Result<(&MalType, &[MalType]), MalErr> {
Ok((car(list)?, cdr(list)))
}
fn first(list: &[MalType]) -> &[MalType] {
if list.len() > 1 {
&list[..list.len() - 1]
} else {
&list[0..0]
}
}
// FIXME: Treat as result for now, change later
fn last(list: &[MalType]) -> Result<&MalType, MalErr> {
match list.len() {
0 => Err(MalErr::unrecoverable("Mi sono cacato le mutande")),
_ => Ok(&list[list.len() - 1]),
}
}
pub fn first_last(list: &[MalType]) -> (&[MalType], Result<&MalType, MalErr>) {
(first(list), last(list))
}
use std::process::exit;
pub fn mal_exit(list: &[MalType]) -> MalRet {
match car_cdr(list)?.0 {
match car(list)? {
MalType::Int(val) => exit(*val as i32),
_ => exit(-1),
}

View File

@ -1,15 +1,14 @@
use std::rc::Rc;
use crate::env::Env;
use crate::env::{call_func, car_cdr};
use crate::env::{self, call_func, car, car_cdr, CallFunc, CallRet};
use crate::env::{env_get, env_new, env_set};
use crate::env::{first_last, Env};
use crate::printer::prt;
use crate::types::MalType as M;
use crate::types::{MalArgs, MalErr, MalMap, MalRet, MalType};
use std::rc::Rc;
/// Resolve the first element of the list as the function name and call it
/// with the other elements as arguments
fn eval_func(list: &MalType) -> MalRet {
fn eval_func(list: &MalType) -> CallRet {
let list = list.if_list()?;
let (func, args) = car_cdr(list)?;
call_func(func, args)
@ -25,6 +24,16 @@ fn eval_func(list: &MalType) -> MalRet {
// It's not possible, however, to clone the outer when defining
// a new environment that will be used later (such as when using fn*)
macro_rules! inner_do {
($list:expr, $env:expr) => {{
let (first, last) = first_last($list);
for ast in first {
eval(ast, $env.clone())?;
}
last.cloned()
}};
}
/// def! special form:
/// Evaluate the second expression and assign it to the first symbol
fn def_bang_form(list: &[MalType], env: Env) -> MalRet {
@ -39,12 +48,12 @@ fn def_bang_form(list: &[MalType], env: Env) -> MalRet {
/// let* special form:
/// Create a temporary inner environment, assigning pair of elements in
/// the first list and returning the evaluation of the second expression
fn let_star_form(list: &[MalType], env: Env) -> MalRet {
fn let_star_form(list: &[MalType], env: Env) -> Result<(MalType, Env), MalErr> {
// Create the inner environment
let inner_env = env_new(Some(env.clone()));
// change the inner environment
let (car, cdr) = car_cdr(list)?;
let list = car.if_list()?;
let list = car.if_vec()?;
if list.len() % 2 != 0 {
return Err(MalErr::unrecoverable(
"let* form, number of arguments must be even",
@ -54,22 +63,15 @@ fn let_star_form(list: &[MalType], env: Env) -> MalRet {
for i in (0..list.len()).step_by(2) {
def_bang_form(&list[i..=i + 1], inner_env.clone())?;
}
let mut last = M::Nil;
for expr in cdr {
last = eval(expr, inner_env.clone())?;
}
Ok(last)
Ok((inner_do!(cdr, inner_env)?, inner_env))
}
/// do special form:
/// Evaluate all the elements in a list using eval_ast and return the
/// result of the last evaluation
fn do_form(list: &[MalType], env: Env) -> MalRet {
let mut ret = M::Nil;
for ast in list {
ret = eval(ast, env.clone())?;
}
Ok(ret)
inner_do!(list, env)
}
fn if_form(list: &[MalType], env: Env) -> MalRet {
@ -79,20 +81,20 @@ fn if_form(list: &[MalType], env: Env) -> MalRet {
));
}
let (cond, branches) = car_cdr(list)?;
match eval(cond, env.clone())? {
Ok(match eval(cond, env.clone())? {
M::Nil | M::Bool(false) => match branches.len() {
1 => Ok(M::Nil),
_ => eval(&branches[1], env),
1 => M::Nil,
_ => branches[1].clone(),
},
_ => eval(&branches[0], env),
}
_ => branches[0].clone(),
})
}
fn fn_star_form(list: &[MalType], env: Env) -> MalRet {
let (binds, exprs) = car_cdr(list)?;
binds.if_list()?;
binds.if_vec()?;
Ok(M::MalFun {
eval: eval_ast,
// eval: eval_ast,
params: Rc::new(binds.clone()),
ast: Rc::new(M::List(MalArgs::new(exprs.to_vec()))),
env,
@ -105,38 +107,94 @@ pub fn help_form(list: &[MalType], env: Env) -> MalRet {
let (sym, _) = car_cdr(list)?;
let sym_str = sym.if_symbol()?;
match eval(sym, env.clone())? {
M::Fun(_, desc) => println!("{}\t[builtin]: {}", sym_str, desc),
M::Fun(_, desc) => println!("{}\t[builtin]: {}\n", sym_str, desc),
M::MalFun { params, ast, .. } => print_malfun(sym_str, params, ast),
_ => println!("{}\t[symbol]: {}", sym_str, prt(&env_get(&env, sym_str)?)),
_ => eprintln!("{}\t[symbol]: {}\n", sym_str, prt(&env_get(&env, sym_str)?)),
}
Ok(M::Bool(true))
Ok(M::Nil)
}
pub fn find_form(list: &[MalType], env: Env) -> MalRet {
let mut filtered = env.keys();
for mat in list {
let mat = mat.if_symbol()?;
filtered.retain(|x| x.contains(mat));
}
eprintln!("\t[matches]:\n{}\n", filtered.join(" "));
Ok(M::Nil)
}
pub fn outermost(env: &Env) -> Env {
let mut env = env;
while let Some(ref e) = env.outer {
env = e;
}
env.clone()
}
/// Intermediate function to discern special forms from defined symbols
fn apply(list: &MalArgs, env: Env) -> MalRet {
let (car, cdr) = car_cdr(list)?;
match car {
M::Sym(sym) if sym == "def!" => def_bang_form(cdr, env), // Set for env
M::Sym(sym) if sym == "let*" => let_star_form(cdr, env), // Clone the env
M::Sym(sym) if sym == "do" => do_form(cdr, env),
M::Sym(sym) if sym == "if" => if_form(cdr, env),
M::Sym(sym) if sym == "fn*" => fn_star_form(cdr, env),
M::Sym(sym) if sym == "help" => help_form(cdr, env),
// Filter out special forms
_ => eval_func(&eval_ast(&M::List(MalArgs::new(list.to_vec())), env)?),
pub fn eval(ast: &MalType, env: Env) -> MalRet {
let mut ast = ast.clone();
let mut env = env;
loop {
match &ast {
M::List(list) if list.is_empty() => return Ok(ast.clone()),
M::List(list) => {
let (symbol, args) = car_cdr(list)?;
match symbol {
M::Sym(sym) if sym == "def!" => return def_bang_form(args, env.clone()), // Set for env
M::Sym(sym) if sym == "let*" => (ast, env) = let_star_form(args, env.clone())?,
M::Sym(sym) if sym == "do" => ast = do_form(args, env.clone())?,
M::Sym(sym) if sym == "if" => ast = if_form(args, env.clone())?,
M::Sym(sym) if sym == "fn*" => return fn_star_form(args, env.clone()),
M::Sym(sym) if sym == "help" => return help_form(args, env.clone()),
M::Sym(sym) if sym == "find" => return find_form(args, env.clone()),
// Oh God, what have I done
M::Sym(sym) if sym == "quote" => return Ok(car(args)?.clone()),
M::Sym(sym) if sym == "ok?" => {
return match eval(car(args)?, env.clone()) {
Err(_) => Ok(M::Nil),
_ => Ok(M::Bool(true)),
}
}
// Special form, sad
// Bruh, is basically double eval
M::Sym(sym) if sym == "eval" => {
ast = eval(env::car(args)?, env.clone())?;
// Climb to the outermost environment (The repl env)
env = outermost(&env);
}
// Filter out special forms
// "apply"/invoke
_ => {
let apply_list = &eval_ast(&ast, env.clone())?;
let eval_ret = eval_func(apply_list)?;
match eval_ret {
CallFunc::Builtin(ret) => return Ok(ret),
CallFunc::MalFun(fun_ast, fun_env) => {
ast = fun_ast;
env = fun_env;
}
}
}
};
}
_ => return eval_ast(&ast, env),
}
}
}
/// Switch ast evaluation depending on it being a list or not and return the
/// result of the evaluation, this function calls "eval_ast" to recursively
/// evaluate asts
pub fn eval(ast: &MalType, env: Env) -> MalRet {
/*pub fn eval(ast: &MalType, env: Env) -> MalRet {
match &ast {
M::List(list) if list.is_empty() => Ok(ast.clone()),
M::List(list) if !list.is_empty() => apply(list, env),
_ => eval_ast(ast, env),
}
}
}*/
/// Separately evaluate all elements in a collection (list or vector)
fn eval_collection(list: &MalArgs, env: Env) -> Result<MalArgs, MalErr> {
@ -199,7 +257,7 @@ mod tests {
}};
}
macro_rules! load_f {
/*macro_rules! load_f {
($input:expr, $env:expr) => {{
use crate::reader::{read_str, Reader};
use std::rc::Rc;
@ -227,7 +285,7 @@ mod tests {
[&[f], &args[1..]].concat()
}))
}};
}
}*/
fn _env_empty() -> Env {
use crate::env::env_new;
@ -237,10 +295,7 @@ mod tests {
mod forms {
use crate::env::env_get;
use crate::eval::tests::_env_empty;
use crate::eval::{
apply, def_bang_form, do_form, eval_ast, eval_func, fn_star_form, if_form,
let_star_form,
};
use crate::eval::{def_bang_form, fn_star_form, if_form, let_star_form};
use crate::types::MalType;
#[test]
@ -284,31 +339,31 @@ mod tests {
matches!(let_star_form(load!("(let* 1)"), env.clone()), Err(e) if !e.is_recoverable())
);
assert!(
matches!(let_star_form(load!("(let* (a))"), env.clone()), Err(e) if !e.is_recoverable())
);
assert!(matches!(
let_star_form(load!("(let* ())"), env.clone()),
Ok(MalType::Nil)
));
assert!(matches!(
let_star_form(load!("(let* (a 1))"), env.clone()),
Ok(MalType::Nil)
));
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
assert!(matches!(
let_star_form(load!("(let* (a 1 b 2) a b)"), env.clone()),
Ok(MalType::Int(2))
));
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
assert!(matches!(env_get(&env.clone(), "b"), Err(e) if !e.is_recoverable()));
assert!(matches!(
let_star_form(load!("(let* (a 1 b 2) (def! c 1) a b)"), env.clone()),
Ok(MalType::Int(2))
));
matches!(let_star_form(load!("(let* [a])"), env.clone()), Err(e) if !e.is_recoverable())
); /*
assert!(matches!(
let_star_form(load!("(let* ())"), env.clone()),
Ok(MalType::Nil)
));
assert!(matches!(
let_star_form(load!("(let* (a 1))"), env.clone()),
Ok(MalType::Nil)
));
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
assert!(matches!(
let_star_form(load!("(let* (a 1 b 2) a b)"), env.clone()),
Ok(MalType::Int(2))
));
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
assert!(matches!(env_get(&env.clone(), "b"), Err(e) if !e.is_recoverable()));
assert!(matches!(
let_star_form(load!("(let* (a 1 b 2) (def! c 1) a b)"), env.clone()),
Ok(MalType::Int(2))
));*/
assert!(matches!(env_get(&env.clone(), "c"), Err(e) if !e.is_recoverable()));
}
#[test]
/*#[test]
fn _do_form() {
let env = _env_empty();
assert!(matches!(
@ -324,7 +379,7 @@ mod tests {
Ok(MalType::Int(2))
));
assert!(matches!(env_get(&env.clone(), "a"), Ok(MalType::Int(1))));
}
}*/
#[test]
fn _if_form() {
@ -360,10 +415,9 @@ mod tests {
fn fn_star() {
let env = _env_empty();
assert!(matches!(
fn_star_form(load!("(fn* (a b) 1 2)"), env.clone()),
Ok(MalType::MalFun { eval, params, ast, .. })
if eval == eval_ast
&& matches!((*params).clone(), MalType::List(v)
fn_star_form(load!("(fn* [a b] 1 2)"), env.clone()),
Ok(MalType::MalFun {params, ast, .. })
if matches!((*params).clone(), MalType::Vector(v)
if matches!(&v[0], MalType::Sym(v) if v == "a")
&& matches!(&v[1], MalType::Sym(v) if v == "b")
&& matches!((*ast).clone(), MalType::List(v)
@ -378,6 +432,7 @@ mod tests {
Err(e) if !e.is_recoverable()));
}
/*
#[test]
fn _eval_func() {
let env = _env_empty();
@ -402,9 +457,9 @@ mod tests {
eval_func(load_f!("(or nil 1)", env.clone())),
Ok(MalType::Int(1))
));
}
}*/
#[test]
/*#[test]
fn _apply() {
let env = _env_empty();
assert!(matches!(
@ -412,6 +467,6 @@ mod tests {
Ok(MalType::Int(1))
));
assert!(matches!(env_get(&env, "a"), Ok(MalType::Int(1))));
}
}*/
}
}

View File

@ -8,16 +8,24 @@ mod mal_tests;
mod parse_tools;
mod printer;
mod reader;
mod step4_if_fn_do;
mod step6_file;
mod types;
use core::ns_init;
use parse_tools::{interactive, load_file};
use parse_tools::{interactive, load_file, load_home_file, set_home_path};
fn main() {
// Initialize ns environment
let reply_env = ns_init();
// Set the "MAL_HOME" symbol to the specified directory or the default one
set_home_path(&reply_env);
// load "$MAL_HOME/core.mal" [warn: true] since this has some core functionalities
load_home_file("core.mal", &reply_env, true);
// Load config files ($MAL_HOME/config.mal, or default $HOME/.config/mal/config.mal)
// [warn: false] since this file is optional
load_home_file("config.mal", &reply_env, false);
// load all files passed as arguments
args().collect::<Vec<String>>()[1..].iter().for_each(|f| {
if let Err(e) = load_file(f, &reply_env) {

View File

@ -5,8 +5,12 @@ mod functional {
($file:expr) => {{
use crate::core::ns_init;
use crate::load_file;
use crate::parse_tools::{load_home_file, set_home_path};
let env = ns_init();
set_home_path(&env);
load_home_file("core.mal", &env, false);
assert!(matches!(
load_file(format!("tests/{}.mal", $file).as_str(), &ns_init()),
load_file(format!("tests/{}.mal", $file).as_str(), &env),
Ok(_)
));
}};
@ -14,12 +18,7 @@ mod functional {
#[test]
fn assert_fail() {
use crate::core::ns_init;
use crate::load_file;
assert!(matches!(
load_file("tests/assert_fail.mal", &ns_init()),
Err(_)
))
test!("assert")
}
#[test]

View File

@ -1,100 +1,132 @@
use crate::env::Env;
use crate::reader::Reader;
use crate::step4_if_fn_do::rep;
use crate::types::{MalErr, MalRet, MalType::Nil};
use regex::Regex;
use crate::eval::eval;
use crate::reader::{read_str, Reader};
use crate::step6_file::rep;
use crate::types::{MalErr, MalRet};
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::io::Read;
use std::path::Path;
use std::process::exit;
pub fn load_file(filename: &str, env: &Env) -> MalRet {
let file_desc = File::open(filename);
let file = match file_desc {
Ok(file) => file,
Err(_) => {
println!("Unable to open file: '{}'", filename);
exit(1)
fn eval_str(line: &str, env: &Env) -> MalRet {
eval(&read_str(Reader::new().push(line))?, env.clone())
}
pub fn set_home_path(env: &Env) {
eval_str(
"(if (env \"MAL_HOME\")
(def! MAL_HOME (env \"MAL_HOME\"))
(def! MAL_HOME (str (env \"HOME\") \"/.config/mal\")))",
env,
)
.unwrap();
}
fn get_home_path(env: &Env) -> Result<String, MalErr> {
Ok(eval_str("MAL_HOME", env)?.if_string()?.to_string())
}
pub fn load_home_file(filename: &str, env: &Env, warn: bool) {
let full_filename = get_home_path(env).unwrap_or_else(|_| "".to_string()) + "/" + filename;
if Path::new(&full_filename).exists() {
if let Err(e) = load_file(&full_filename, env) {
eprintln!("; reading \"{}\":", full_filename);
eprintln!("{}", e.message());
}
};
let reader = BufReader::new(file);
let mut last: Result<Vec<String>, MalErr> = Ok(Vec::new());
let comment_line = Regex::new(r"^[\s]*;.*").unwrap();
let parser = Reader::new();
for line in reader.lines() {
match line {
Ok(line) => {
// Read line to compose program inpu
if line.is_empty() || comment_line.is_match(&line) {
last = Ok(Vec::new());
continue;
} else {
parser.push(&line);
}
last = match rep(&parser, env) {
Err(error) if error.is_recoverable() => Err(error),
tmp => {
parser.clear();
Ok(tmp.map_err(|error| {
MalErr::unrecoverable(format!("; Error @ {}", error.message()).as_str())
})?)
}
}
}
Err(err) => {
return Err(MalErr::unrecoverable(
format!("Error reading line: {}", err).as_str(),
))
}
}
}
if let Err(error) = last {
Err(MalErr::unrecoverable(
format!(
"; ERROR parsing: '{}'\n; {}\n; the environment is in an unknown state",
filename,
error.message()
)
.as_str(),
))
} else {
Ok(Nil)
} else if warn {
eprintln!("; WARNING: file \"{}\" does not exist", full_filename);
}
}
use std::io::Write;
use std::process::exit;
pub fn read_file(filename: &str) -> Result<String, MalErr> {
let mut file = File::open(filename).map_err(|_| {
MalErr::unrecoverable(format!("Failed to open file '{}'", filename).as_str())
})?;
let mut content = String::new();
file.read_to_string(&mut content).map_err(|_| {
MalErr::unrecoverable(format!("Failed to read content of '{}'", filename).as_str())
})?;
Ok(content)
}
pub fn load_file(filename: &str, env: &Env) -> MalRet {
eval_str(
format!(
"(eval (read-string (str \"(do \" (slurp \"{}\") \"\nnil)\")))",
filename
)
.as_str(),
env,
)
} // WTF this is becoming ever less like rust and more like lisp, did I really completely skip the file reading?
extern crate rustyline;
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;
pub fn interactive(env: Env) {
const HISTORY: &str = ".mal-history";
let home = get_home_path(&env).unwrap();
let history = home + "/" + HISTORY;
// Using "Editor" instead of the standard I/O because I hate myself but not this much
// TODO: remove unwrap and switch to a better error handling
let mut rl = DefaultEditor::new().unwrap();
if rl.load_history(&history).is_err() {
eprintln!("; Failed to load history");
}
let mut num = 0;
let parser = Reader::new();
loop {
parser.clear();
loop {
print!("user> ");
// Flush the prompt to appear before command
let _ = io::stdout().flush();
// // Old reader
// print!("user> ");
// // Flush the prompt to appear before command
// let _ = io::stdout().flush();
// Read line to compose program input
let mut line = String::new();
io::stdin().read_line(&mut line).unwrap();
// // Read line to compose program input
// let mut line = String::new();
// io::stdin().read_line(&mut line).unwrap();
let line = rl.readline("user> ");
parser.push(&line);
match line {
Ok(line) => {
// TODO: should handle this in a different way
rl.add_history_entry(&line).unwrap_or_default();
rl.save_history(&history)
.unwrap_or_else(|e| eprintln!("; WARNING: saving history: {}", e));
// Perform rep on whole available input
match rep(&parser, &env) {
Ok(output) => output.iter().for_each(|el| println!("[{}]> {}", num, el)),
Err(error) => {
if error.is_recoverable() {
// && line != "\n" {
continue;
parser.push(&line);
// Perform rep on whole available input
match rep(&parser, &env) {
Ok(output) => output.iter().for_each(|el| eprintln!("[{}]> {}", num, el)),
Err(error) => {
if error.is_recoverable() {
// && line != "\n" {
continue;
}
eprintln!("; [{}]> Error @ {}", num, error.message());
}
}
println!("; [{}]> Error @ {}", num, error.message());
num += 1;
break;
}
Err(ReadlineError::Interrupted) => {
parser.clear();
continue;
}
Err(ReadlineError::Eof) => exit(0),
Err(err) => {
eprint!("; Error reading lnie: {:?}", err);
break;
}
}
num += 1;
break;
}
}
}

View File

@ -13,7 +13,7 @@ fn key_str(val: &str) -> MalType {
pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
match ast {
M::Nil => "nil".to_string(),
M::Nil => "NIL".to_string(),
M::Sym(sym) => sym.to_string(),
M::Key(sym) => sym[2..].to_string(),
M::Int(val) => val.to_string(),
@ -67,4 +67,5 @@ pub fn print_malfun(sym: &str, params: Rc<MalType>, ast: Rc<MalType>) {
.unwrap_or(&[])
.iter()
.for_each(|el| println!("; {}", pr_str(el, true)));
println!();
}

View File

@ -1,4 +1,5 @@
use std::cell::{Cell, RefCell};
use std::rc::Rc;
// Specyfy components in "types"
use crate::types::*;
@ -25,10 +26,11 @@ impl Reader {
}
}
pub fn push(&self, input: &str) {
pub fn push(&self, input: &str) -> &Self {
self.ptr.set(0);
// reset the state of the parser and push the additional strings
self.tokens.borrow_mut().append(&mut tokenize(input));
self
}
pub fn clear(&self) {
@ -95,14 +97,18 @@ impl Reader {
"nil" => Ok(Nil),
tk => {
if Regex::new(r"^-?[0-9]+$").unwrap().is_match(tk) {
Ok(Int(tk.parse::<isize>().unwrap()))
return Ok(Int(tk.parse::<isize>().unwrap()));
} else if tk.starts_with('\"') {
Ok(Str(unescape_str(tk)))
if tk.len() > 1 && tk.ends_with('\"') {
return Ok(Str(unescape_str(tk)));
}
return Err(MalErr::unrecoverable(
"End of line reached without closing string",
));
} else if tk.starts_with(':') {
Ok(Key(format!("ʞ{}", tk)))
} else {
Ok(Sym(tk.to_string()))
return Ok(Key(format!("ʞ{}", tk)));
}
Ok(Sym(tk.to_string()))
}
}
}
@ -119,6 +125,14 @@ impl Reader {
"(" => self.read_list(")"),
"[" => self.read_list("]"),
"{" => self.read_list("}"),
// Ugly quote transformation for quote expansion
"'" => {
self.next()?;
Ok(List(Rc::new(vec![
MalType::Sym("quote".to_string()),
self.read_form()?,
])))
}
_ => self.read_atom(),
}
}
@ -128,7 +142,11 @@ impl Reader {
/// Create anew Reader with the tokens
/// Call read_from with the reader instance
pub fn read_str(reader: &Reader) -> MalRet {
reader.read_form()
let mut ret = Nil;
while !reader.ended() {
ret = reader.read_form()?;
}
Ok(ret)
}
/// Read a string and return a list of tokens in it (following regex in README)

41
src/step5_tco.rs Normal file
View File

@ -0,0 +1,41 @@
// Structure the main functions of the interpreter
//
// For now just act as an echo, note that each function should not modify the
// input, thus this can be referenced by the previous step without the need
// to allocate more memory
use crate::env::Env;
use crate::eval::eval;
use crate::printer::pr_str;
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: &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| MalErr::new(format!("EVAL: {}", err.message()), err.severity()))
}
#[allow(non_snake_case)]
/// Print out the result of the evaluation
fn PRINT(output: MalType) -> String {
pr_str(&output, true)
}
pub fn rep(reader: &Reader, env: &Env) -> Result<Vec<String>, MalErr> {
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);
}
}
}

39
src/step6_file.rs Normal file
View File

@ -0,0 +1,39 @@
// Structure the main functions of the interpreter
//
// For now just act as an echo, note that each function should not modify the
// input, thus this can be referenced by the previous step without the need
// to allocate more memory
use crate::env::Env;
use crate::eval::eval;
use crate::printer::pr_str;
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: &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| MalErr::new(format!("EVAL: {}", err.message()), err.severity()))
}
#[allow(non_snake_case)]
/// Print out the result of the evaluation
fn PRINT(output: MalType) -> String {
pr_str(&output, true)
}
pub fn rep(reader: &Reader, env: &Env) -> Result<Vec<String>, MalErr> {
let mut ret_str = Vec::new();
while !reader.ended() {
let ast = READ(reader)?;
let out = EVAL(ast, env.clone())?;
ret_str.push(PRINT(out));
}
Ok(ret_str)
}

View File

@ -1,15 +1,15 @@
use crate::env::{car_cdr, Env};
use crate::env::{bin_unpack, Env};
use std::{collections::HashMap, rc::Rc};
// All Mal types should inherit from this
#[derive(Clone, Debug)]
#[derive(Clone)]
pub enum MalType {
List(MalArgs),
Vector(MalArgs),
Map(MalMap),
Fun(fn(&[MalType]) -> MalRet, &'static str), // Used for base functions, implemented using the underlying language (rust)
MalFun {
eval: fn(ast: &MalType, env: Env) -> MalRet,
// eval: fn(ast: &MalType, env: Env) -> MalRet,
params: Rc<MalType>,
ast: Rc<MalType>,
env: Env,
@ -42,6 +42,15 @@ impl MalType {
}
}
pub fn if_vec(&self) -> Result<&[MalType], MalErr> {
match self {
Self::Vector(list) => Ok(list),
_ => Err(MalErr::unrecoverable(
format!("{:?} is not a vector", prt(self)).as_str(),
)),
}
}
pub fn if_symbol(&self) -> Result<&str, MalErr> {
match self {
Self::Sym(sym) => Ok(sym),
@ -50,63 +59,54 @@ impl MalType {
)),
}
}
pub fn if_string(&self) -> Result<&str, MalErr> {
match self {
Self::Str(sym) => Ok(sym),
_ => Err(MalErr::unrecoverable(
format!("{:?} is not a string", prt(self)).as_str(),
)),
}
}
}
use crate::types::MalType as M;
fn mal_eq(a: &MalType, b: &[MalType]) -> MalRet {
Ok(M::Bool(match a {
M::Nil => b.iter().all(|el| matches!(el, M::Nil)),
M::Bool(a) => b.iter().all(|el| matches!(el, M::Bool(b) if a == b)),
M::Int(a) => b.iter().all(|el| matches!(el, M::Int(b) if a == b)),
M::Key(a) => b.iter().all(|el| matches!(el, M::Key(b) if a == b)),
M::Str(a) => b.iter().all(|el| matches!(el, M::Str(b) if a == b)),
M::List(a) => b.iter().all(|el| {
matches!(el, M::List(b)
if a.len() == b.len()
&& a.iter().zip(b.iter()).all(
|(a, b)| matches!(mal_eq(a, &[b.clone()]),
Ok(M::Bool(true)))))
}),
M::Vector(a) => b.iter().all(|el| {
matches!(el, M::Vector(b)
if a.len() == b.len()
&& a.iter().zip(b.iter()).all(
|(a, b)| matches!(mal_eq(a, &[b.clone()]),
Ok(M::Bool(true)))))
}),
_ => {
return Err(MalErr::unrecoverable(
"Comparison not implemented for 'Map', 'Fun', 'MalFun' and 'Sym'",
))
// That's a quite chonky function
fn mal_eq(args: (&MalType, &MalType)) -> bool {
match (args.0, args.1) {
(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)) | (M::Sym(a), M::Sym(b)) => a == b,
(M::List(a), M::List(b)) | (M::Vector(a), M::Vector(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(mal_eq)
}
}))
_ => false,
}
}
pub fn mal_comp(args: &[MalType]) -> MalRet {
match args.len() {
0 => Ok(M::Bool(true)),
_ => {
let (car, cdr) = car_cdr(args)?;
mal_eq(car, cdr)
}
if args.len() != 2 {
return Err(MalErr::unrecoverable("Expected 2 arguments"));
}
Ok(M::Bool(mal_eq(bin_unpack(args)?)))
}
pub fn mal_assert(args: &[MalType]) -> MalRet {
if args.iter().any(|i| matches!(i, M::Nil | M::Bool(false))) {
Err(MalErr::unrecoverable("Assertion failed"))
} else {
Ok(M::Nil)
return Err(MalErr::unrecoverable("Assertion failed"));
}
Ok(M::Nil)
}
#[derive(PartialEq, Clone, Copy)]
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Severity {
Recoverable,
Unrecoverable,
}
#[derive(Debug)]
pub struct MalErr {
message: String,
severity: Severity,
@ -129,6 +129,11 @@ impl MalErr {
self.severity == Severity::Recoverable
}
pub fn severe(mut self) -> Self {
self.severity = Severity::Unrecoverable;
self
}
pub fn recoverable(message: &str) -> Self {
Self::new(message.to_owned(), Severity::Recoverable)
}

View File

@ -1,51 +1,43 @@
; +
(assert (= (+) 0))
(assert (= (+ 1) 1))
(assert (= (+ 1 -4) -3))
(assert (= (+ 1 1) 2))
(assert (= (+ 1 2 3 4) 10))
; -
(assert (= (-) 0))
(assert (= (- 1) -1))
(assert (= (- 2 1) 1))
(assert (= (- 1 2) -1))
(assert (= (- 10 1 2 3) 4))
; *
(assert (= (*) 1))
(assert (= (* 2) 2))
(assert (= (* 2 3) 6))
(assert (= (* -2 3) -6))
(assert (= (* -2 -3) 6))
(assert (= (* 10 1 2 3) 60))
; /
(assert (= (/) 1))
(assert (= (/ 1) 1))
(assert (= (/ 2) 0))
(assert (= (/ 3 2) 1))
(assert (= (/ 128 2 4) 16))
(assert (= (/ 2 3) 0))
(assert (= (/ -10 2) -5))
(assert (= (/ -10 -2) 5))
(assert (not (ok? (/ 12 0))))
; mod
(assert (= (mod 10 4) 2))
(assert (= (mod 4 10) 4))
; >
(assert (not (>)))
(assert (> 1))
(assert (> 3 2 1))
(assert (not (> 3 1 2)))
(assert (> 3 2))
(assert (not (> 1 2)))
(assert (not (> 1 1)))
; <
(assert (not (<)))
(assert (< 1))
(assert (< 1 2 3))
(assert (not (< 1 3 2)))
(assert (< 1 3))
(assert (not (< 3 2)))
(assert (not (< 1 1)))
; >=
(assert (not (>=)))
(assert (>= 1))
(assert (>= 3 2 1))
(assert (not (>= 3 1 2)))
(assert (>= 3 1))
(assert (not (>= 1 2)))
(assert (>= 1 1))
; <=
(assert (not (<=)))
(assert (<= 1))
(assert (<= 1 2 3))
(assert (not (<= 1 3 2)))
(assert (<= 1 3))
(assert (not (<= 3 2)))
(assert (<= 1 1))

6
tests/assert.mal Normal file
View File

@ -0,0 +1,6 @@
(assert true)
(assert (ok? 1))
(assert-eq nil (ok? (1)))
(assert-eq true (ok? 1))
(ok? (assert true))
(not (ok? (assert nil)))

View File

@ -1 +0,0 @@
(assert nil)

View File

@ -6,53 +6,48 @@
; 1 different
; compare with foreign type
; empty
(assert (=)) ; nothing to compare with
; nil
(assert (= nil))
(assert (= nil nil nil))
(assert (= nil nil))
(assert (not (= nil true)))
(assert (not (= nil 1)))
; bool
(assert (= true))
(assert (= true true true))
(assert (not (= true false true)))
(assert (= true true))
(assert (not (= false true)))
(assert (not (= true 1)))
; int
(assert (= 1))
(assert (= 1 1 1))
(assert (not (= 1 2 1)))
(assert (= 1 1))
(assert (not (= 1 2)))
(assert (not (= 1 nil)))
; key
(assert (= :a))
(assert (= :a :a :a))
(assert (not (= :a :b :a)))
(assert (= :a :a))
(assert (not (= :a :b)))
(assert (not (= :a "a")))
; sym
(assert (= 'a 'a))
(assert (not (= 'a 'b)))
(assert (not (= 'a "a")))
; string
(assert (= "a"))
(assert (= "a" "a" "a"))
(assert (not (= "a" "b" "a")))
(assert (= "a" "a"))
(assert (not (= "a" "b")))
(assert (not (= "a" :a)))
; add comparison for same elements with different length
; list
(assert (= (list 1 1 1)))
(assert (= (list 1 1 1) (list 1 1 1) (list 1 1 1)))
(assert (not (= (list 1 1 1) (list 1 2 1) (list 1 1 1))))
(assert (not (= (list 1) (list 1 1))))
(assert (not (= (list 1 1) (list 1))))
(assert (not (= () (list 1))))
(assert (not (= (list 1) ())))
(assert (not (= (list 1 1 1) [1 1 1])))
(assert (= '(1 1 1) '(1 1 1)))
(assert (not (= '(1 2 1) '(1 1 1))))
(assert (not (= '(1) '(1 1))))
(assert (not (= '(1 1) '(1))))
(assert (not (= () '(1))))
(assert (not (= '(1) ())))
(assert (not (= '(1 1 1) [1 1 1])))
; vector
(assert (= [1 1 1]))
(assert (= [1 1 1] [1 1 1] [1 1 1]))
(assert (not (= [1 1 1] [1 2 1] [1 1 1])))
(assert (not (= [1 1 1] (list 1 1 1))))
(assert (= [1 1 1] [1 1 1]))
(assert (not (= [1 2 1] [1 1 1])))
(assert (not (= [1 1 1] '(1 1 1))))

View File

@ -1,10 +1,9 @@
(def! n-fib (fn* (n)
(if (<= n 0) 0 ; 0 base case
(if (= n 1) 1 ; 1 base case
(+ (n-fib (- n 1)) (n-fib (- n 2))))))) ; recursive
(def! n-fib (fn* [n]
(if (< n 2) n ; base
(+ (n-fib (- n 1)) (n-fib (- n 2)))))) ; recursive
(def! assert-fib (fn* (n expected) ; check fibonacci result
(if (= (n-fib n) expected) nil
(def! assert-fib (fn* [n expected] ; check fibonacci result
(if (not (= (n-fib n) expected))
(do (prn (list
"Expected"
expected

View File

@ -9,23 +9,19 @@
(assert (not (not true)))
; or
(assert (not (or false)))
(assert (or 1))
(assert (or 1 nil false))
(assert (or 1 1 1))
(assert (or nil 1 false))
(assert (not (or nil false)))
(assert (not (or false nil)))
(assert (not (or nil nil)))
(assert (or 1 nil))
(assert (or nil 1))
(assert (or 1 1))
; and
(assert (and 1))
(assert (not (and nil)))
(assert (and 1 1 1))
(assert (not (and 1 nil false)))
(assert (not (and nil false)))
(assert (not (and nil nil)))
(assert (not (and 1 nil)))
(assert (not (and 1 nil)))
(assert (and 1 1))
; xor
(assert (not (xor nil false nil)))
(assert (xor nil 1 nil false nil))
(assert (not (xor nil 1 false 1)))
(assert (not (xor 1 nil 1 1)))
(assert (not (xor nil nil)))
(assert (xor nil 1))
(assert (xor 1 nil))
(assert (not (xor 1 1)))