mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
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:
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@ src/#*
|
||||
|
||||
# emacs save
|
||||
src/*~
|
||||
|
||||
# history file
|
||||
.mal-history
|
||||
289
Cargo.lock
generated
289
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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
83
core/core.mal
Normal 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)))
|
||||
52
src/core.rs
52
src/core.rs
@ -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")
|
||||
)
|
||||
}
|
||||
|
||||
109
src/env.rs
109
src/env.rs
@ -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),
|
||||
}
|
||||
|
||||
207
src/eval.rs
207
src/eval.rs
@ -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))));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@ -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) {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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!();
|
||||
}
|
||||
|
||||
@ -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
41
src/step5_tco.rs
Normal 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
39
src/step6_file.rs
Normal 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)
|
||||
}
|
||||
83
src/types.rs
83
src/types.rs
@ -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)
|
||||
}
|
||||
|
||||
@ -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
6
tests/assert.mal
Normal 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)))
|
||||
@ -1 +0,0 @@
|
||||
(assert nil)
|
||||
@ -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))))
|
||||
@ -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
|
||||
|
||||
@ -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)))
|
||||
Reference in New Issue
Block a user