diff --git a/impls/rust/Cargo.lock b/impls/rust/Cargo.lock deleted file mode 100644 index 38dc2a4549..0000000000 --- a/impls/rust/Cargo.lock +++ /dev/null @@ -1,432 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "clipboard-win" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" -dependencies = [ - "error-code", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[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.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.174" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[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.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rust2" -version = "0.1.0" -dependencies = [ - "fnv", - "itertools", - "lazy_static", - "regex", - "rustyline", -] - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[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.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[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 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/impls/rust/Cargo.toml b/impls/rust/Cargo.toml index 40831b0533..ff79075001 100644 --- a/impls/rust/Cargo.toml +++ b/impls/rust/Cargo.toml @@ -4,11 +4,10 @@ version = "0.1.0" authors = ["root"] [dependencies] -rustyline = "13.0.0" -lazy_static = "1.4.0" +rustyline = "14.0" regex = "1.7" -itertools = "0.10" +itertools = "0.13" fnv = "1.0.6" diff --git a/impls/rust/Dockerfile b/impls/rust/Dockerfile index 1720e2854f..9719af7b5d 100644 --- a/impls/rust/Dockerfile +++ b/impls/rust/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:24.04 +FROM ubuntu:25.04 MAINTAINER Joel Martin ########################################################## @@ -22,7 +22,8 @@ WORKDIR /mal RUN apt-get -y install cargo \ librust-fnv-dev \ librust-itertools-dev \ - librust-lazy-static-dev \ + librust-regex-dev \ + librust-rustyline-dev \ rust-clippy rustfmt ENV CARGO_HOME /mal diff --git a/impls/rust/Makefile b/impls/rust/Makefile index 8702c68623..8dab30f396 100644 --- a/impls/rust/Makefile +++ b/impls/rust/Makefile @@ -19,6 +19,7 @@ all: $(STEPS) $(STEPS): $(EXEC_DIR)/%: %.rs cargo build --release --bin $* +$(STEPS): readline.rs $(STEP1-2) $(STEP3) $(UPPER_STEPS): types.rs reader.rs printer.rs $(STEP3) $(UPPER_STEPS): env.rs $(UPPER_STEPS): core.rs @@ -27,7 +28,8 @@ lint: rustfmt *.rs cargo clippy -.PHONY: clean +.PHONY: clean lint all clean: - cargo clean + rm -fr target/ + rm -f .mal-history *~ Cargo.lock diff --git a/impls/rust/core.rs b/impls/rust/core.rs index 34dd628cff..4891035cbd 100644 --- a/impls/rust/core.rs +++ b/impls/rust/core.rs @@ -1,18 +1,18 @@ use std::fs::File; use std::io::Read; use std::rc::Rc; -use std::sync::Mutex; use std::time::{SystemTime, UNIX_EPOCH}; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - use crate::printer::pr_seq; use crate::reader::read_str; -use crate::types::MalErr::ErrMalVal; -use crate::types::MalVal::{Atom, Bool, Func, Hash, Int, List, MalFunc, Nil, Str, Sym, Vector}; -use crate::types::{MalArgs, MalRet, MalVal, _assoc, _dissoc, atom, error, func, hash_map}; +use crate::types::MalVal::{ + Atom, Bool, Func, Hash, Int, Kwd, List, MalFunc, Nil, Str, Sym, Vector, +}; +use crate::types::{ + list, FuncStruct, MalArgs, MalRet, MalVal, _assoc, error, func, hash_map, unwrap_map_key, + vector, wrap_map_key, +}; +use readline; macro_rules! fn_t_int_int { ($ret:ident, $fn:expr) => {{ @@ -51,32 +51,10 @@ fn symbol(a: MalArgs) -> MalRet { } } -fn readline(a: MalArgs) -> MalRet { - lazy_static! { - static ref RL: Mutex> - = Mutex::new(Editor::<(), rustyline::history::DefaultHistory>::new().unwrap()); - } - //let mut rl = Editor::<()>::new(); - - match a[0] { - Str(ref p) => { - //match rl.readline(p) { - match RL.lock().unwrap().readline(p) { - Ok(mut line) => { - // Remove any trailing \n or \r\n - if line.ends_with('\n') { - line.pop(); - if line.ends_with('\r') { - line.pop(); - } - } - Ok(Str(line)) - } - Err(ReadlineError::Eof) => Ok(Nil), - Err(e) => error(&format!("{:?}", e)), - } - } - _ => error("readline: prompt is not Str"), +fn readline(p: &str) -> MalRet { + match readline::readline(p) { + Some(s) => Ok(Str(s)), + None => Ok(Nil), } } @@ -99,9 +77,9 @@ fn time_ms(_a: MalArgs) -> MalRet { } fn get(a: MalArgs) -> MalRet { - match (a[0].clone(), a[1].clone()) { - (Nil, _) => Ok(Nil), - (Hash(ref hm, _), Str(ref s)) => match hm.get(s) { + match a[0] { + Nil => Ok(Nil), + Hash(ref hm, _) => match hm.get(&wrap_map_key(&a[1])?) { Some(mv) => Ok(mv.clone()), None => Ok(Nil), }, @@ -118,45 +96,52 @@ fn assoc(a: MalArgs) -> MalRet { fn dissoc(a: MalArgs) -> MalRet { match a[0] { - Hash(ref hm, _) => _dissoc((**hm).clone(), a[1..].to_vec()), + Hash(ref hm, _) => { + let mut new_hm = (**hm).clone(); + for k in a[1..].iter() { + let _ = new_hm.remove(&wrap_map_key(k)?); + } + Ok(Hash(Rc::new(new_hm), Rc::new(Nil))) + } _ => error("dissoc on non-Hash Map"), } } fn contains_q(a: MalArgs) -> MalRet { - match (a[0].clone(), a[1].clone()) { - (Hash(ref hm, _), Str(ref s)) => Ok(Bool(hm.contains_key(s))), + match a[0] { + Hash(ref hm, _) => Ok(Bool(hm.contains_key(&wrap_map_key(&a[1])?))), _ => error("illegal get args"), } } fn keys(a: MalArgs) -> MalRet { match a[0] { - Hash(ref hm, _) => Ok(list!(hm.keys().map(|k| { Str(k.to_string()) }).collect())), + Hash(ref hm, _) => Ok(list(hm.keys().map(|k| unwrap_map_key(k)).collect())), _ => error("keys requires Hash Map"), } } fn vals(a: MalArgs) -> MalRet { match a[0] { - Hash(ref hm, _) => Ok(list!(hm.values().cloned().collect())), - _ => error("keys requires Hash Map"), + Hash(ref hm, _) => Ok(list(hm.values().cloned().collect())), + _ => error("vals requires Hash Map"), } } fn vec(a: MalArgs) -> MalRet { match a[0] { - List(ref v, _) | Vector(ref v, _) => Ok(vector!(v.to_vec())), + List(ref v, _) => Ok(Vector(v.clone(), Rc::new(Nil))), + Vector(_, _) => Ok(a[0].clone()), _ => error("non-seq passed to vec"), } } fn cons(a: MalArgs) -> MalRet { - match a[1].clone() { + match &a[1] { List(v, _) | Vector(v, _) => { let mut new_v = vec![a[0].clone()]; - new_v.extend_from_slice(&v); - Ok(list!(new_v.to_vec())) + new_v.extend_from_slice(v); + Ok(list(new_v)) } _ => error("cons expects seq as second arg"), } @@ -170,40 +155,31 @@ fn concat(a: MalArgs) -> MalRet { _ => return error("non-seq passed to concat"), } } - Ok(list!(new_v.to_vec())) + Ok(list(new_v)) } fn nth(a: MalArgs) -> MalRet { - match (a[0].clone(), a[1].clone()) { - (List(seq, _), Int(idx)) | (Vector(seq, _), Int(idx)) => { - if seq.len() <= idx as usize { - return error("nth: index out of range"); - } - Ok(seq[idx as usize].clone()) - } + match (&a[0], &a[1]) { + (List(seq, _) | Vector(seq, _), Int(idx)) => match seq.get(*idx as usize) { + Some(result) => Ok(result.clone()), + None => error("nth: index out of range"), + }, _ => error("invalid args to nth"), } } fn first(a: MalArgs) -> MalRet { - match a[0].clone() { - List(ref seq, _) | Vector(ref seq, _) if seq.len() == 0 => Ok(Nil), - List(ref seq, _) | Vector(ref seq, _) => Ok(seq[0].clone()), - Nil => Ok(Nil), + match a[0] { + List(ref seq, _) | Vector(ref seq, _) if seq.len() > 0 => Ok(seq[0].clone()), + List(_, _) | Vector(_, _) | Nil => Ok(Nil), _ => error("invalid args to first"), } } fn rest(a: MalArgs) -> MalRet { - match a[0].clone() { - List(ref seq, _) | Vector(ref seq, _) => { - if seq.len() > 1 { - Ok(list!(seq[1..].to_vec())) - } else { - Ok(list![]) - } - } - Nil => Ok(list![]), + match a[0] { + List(ref seq, _) | Vector(ref seq, _) if seq.len() > 1 => Ok(list(seq[1..].to_vec())), + List(_, _) | Vector(_, _) | Nil => Ok(list!()), _ => error("invalid args to first"), } } @@ -227,7 +203,7 @@ fn map(a: MalArgs) -> MalRet { for mv in v.iter() { res.push(a[0].apply(vec![mv.clone()])?) } - Ok(list!(res)) + Ok(list(res)) } _ => error("map called with non-seq"), } @@ -236,57 +212,132 @@ fn map(a: MalArgs) -> MalRet { fn conj(a: MalArgs) -> MalRet { match a[0] { List(ref v, _) => { - let sl = a[1..] - .iter() - .rev() - .cloned() - .collect::>(); - Ok(list!([&sl[..], v].concat())) + let sl = a[1..].iter().rev().cloned().collect::>(); + Ok(list([&sl[..], v].concat())) } - Vector(ref v, _) => Ok(vector!([v, &a[1..]].concat())), + Vector(ref v, _) => Ok(vector([v, &a[1..]].concat())), _ => error("conj: called with non-seq"), } } fn seq(a: MalArgs) -> MalRet { match a[0] { - List(ref v, _) | Vector(ref v, _) if v.len() == 0 => Ok(Nil), - List(ref v, _) | Vector(ref v, _) => Ok(list!(v.to_vec())), - Str(ref s) if s.is_empty() => Ok(Nil), - Str(ref s) if !a[0].keyword_q() => { - Ok(list!(s.chars().map(|c| { Str(c.to_string()) }).collect())) - } - Nil => Ok(Nil), + ref l @ List(ref v, _) if v.len() > 0 => Ok(l.clone()), + Vector(ref v, _) if v.len() > 0 => Ok(list(v.to_vec())), + Str(ref s) if !s.is_empty() => Ok(list(s.chars().map(|c| Str(c.to_string())).collect())), + List(_, _) | Vector(_, _) | Str(_) | Nil => Ok(Nil), _ => error("seq: called with non-seq"), } } +fn keyword(a: MalArgs) -> MalRet { + match a[0] { + Kwd(_) => Ok(a[0].clone()), + Str(ref s) => Ok(Kwd(String::from(s))), + _ => error("invalid type for keyword"), + } +} + +pub fn empty_q(a: MalArgs) -> MalRet { + match a[0] { + List(ref l, _) | Vector(ref l, _) => Ok(Bool(l.len() == 0)), + Nil => Ok(Bool(true)), + _ => error("invalid type for empty?"), + } +} + +pub fn count(a: MalArgs) -> MalRet { + match a[0] { + List(ref l, _) | Vector(ref l, _) => Ok(Int(l.len() as i64)), + Nil => Ok(Int(0)), + _ => error("invalid type for count"), + } +} + +pub fn atom(a: MalArgs) -> MalRet { + Ok(Atom(Rc::new(std::cell::RefCell::new(a[0].clone())))) +} + +pub fn deref(a: MalArgs) -> MalRet { + match a[0] { + Atom(ref a) => Ok(a.borrow().clone()), + _ => error("attempt to deref a non-Atom"), + } +} + +pub fn reset_bang(a: MalArgs) -> MalRet { + match a[0] { + Atom(ref atm) => { + *atm.borrow_mut() = a[1].clone(); + Ok(a[1].clone()) + } + _ => error("attempt to reset! a non-Atom"), + } +} + +pub fn swap_bang(a: MalArgs) -> MalRet { + match a[0] { + Atom(ref atm) => { + let mut fargs = a[2..].to_vec(); + fargs.insert(0, atm.borrow().clone()); + let result = a[1].apply(fargs)?; + *atm.borrow_mut() = result.clone(); + Ok(result) + } + _ => error("attempt to swap! a non-Atom"), + } +} + +pub fn get_meta(a: MalArgs) -> MalRet { + match a[0] { + List(_, ref meta) | Vector(_, ref meta) | Hash(_, ref meta) => Ok((**meta).clone()), + Func(_, ref meta) => Ok((**meta).clone()), + MalFunc(FuncStruct { ref meta, .. }) => Ok((**meta).clone()), + _ => error("meta not supported by type"), + } +} + +pub fn with_meta(a: MalArgs) -> MalRet { + let m = Rc::new(a[1].clone()); + match a[0] { + List(ref l, _) => Ok(List(l.clone(), m)), + Vector(ref l, _) => Ok(Vector(l.clone(), m)), + Hash(ref l, _) => Ok(Hash(l.clone(), m)), + Func(ref l, _) => Ok(Func(*l, m)), + MalFunc(ref f @ FuncStruct { .. }) => Ok(MalFunc(FuncStruct { + meta: m, + ..f.clone() + })), + _ => error("with-meta not supported by type"), + } +} + pub fn ns() -> Vec<(&'static str, MalVal)> { vec![ ("=", func(|a| Ok(Bool(a[0] == a[1])))), - ("throw", func(|a| Err(ErrMalVal(a[0].clone())))), + ("throw", func(|a| Err(a[0].clone()))), ("nil?", func(fn_is_type!(Nil))), ("true?", func(fn_is_type!(Bool(true)))), ("false?", func(fn_is_type!(Bool(false)))), ("symbol", func(symbol)), ("symbol?", func(fn_is_type!(Sym(_)))), - ( - "string?", - func(fn_is_type!(Str(ref s) if !s.starts_with('\u{29e}'))), - ), - ("keyword", func(|a| a[0].keyword())), - ( - "keyword?", - func(fn_is_type!(Str(ref s) if s.starts_with('\u{29e}'))), - ), + ("string?", func(fn_is_type!(Str(_)))), + ("keyword", func(keyword)), + ("keyword?", func(fn_is_type!(Kwd(_)))), ("number?", func(fn_is_type!(Int(_)))), ( "fn?", - func(fn_is_type!(MalFunc{is_macro,..} if !is_macro,Func(_,_))), + func(fn_is_type!( + MalFunc(FuncStruct { + is_macro: false, + .. + }), + Func(_, _) + )), ), ( "macro?", - func(fn_is_type!(MalFunc{is_macro,..} if is_macro)), + func(fn_is_type!(MalFunc(FuncStruct { is_macro: true, .. }))), ), ("pr-str", func(|a| Ok(Str(pr_seq(&a, true, "", "", " "))))), ("str", func(|a| Ok(Str(pr_seq(&a, false, "", "", ""))))), @@ -305,7 +356,7 @@ pub fn ns() -> Vec<(&'static str, MalVal)> { }), ), ("read-string", func(fn_str!(read_str))), - ("readline", func(readline)), + ("readline", func(fn_str!(readline))), ("slurp", func(fn_str!(slurp))), ("<", func(fn_t_int_int!(Bool, |i, j| { i < j }))), ("<=", func(fn_t_int_int!(Bool, |i, j| { i <= j }))), @@ -317,9 +368,9 @@ pub fn ns() -> Vec<(&'static str, MalVal)> { ("/", func(fn_t_int_int!(Int, |i, j| { i / j }))), ("time-ms", func(time_ms)), ("sequential?", func(fn_is_type!(List(_, _), Vector(_, _)))), - ("list", func(|a| Ok(list!(a.to_vec())))), + ("list", func(|a| Ok(list(a)))), ("list?", func(fn_is_type!(List(_, _)))), - ("vector", func(|a| Ok(vector!(a.to_vec())))), + ("vector", func(|a| Ok(vector(a)))), ("vector?", func(fn_is_type!(Vector(_, _)))), ("hash-map", func(hash_map)), ("map?", func(fn_is_type!(Hash(_, _)))), @@ -332,21 +383,21 @@ pub fn ns() -> Vec<(&'static str, MalVal)> { ("vec", func(vec)), ("cons", func(cons)), ("concat", func(concat)), - ("empty?", func(|a| a[0].empty_q())), + ("empty?", func(empty_q)), ("nth", func(nth)), ("first", func(first)), ("rest", func(rest)), - ("count", func(|a| a[0].count())), + ("count", func(count)), ("apply", func(apply)), ("map", func(map)), ("conj", func(conj)), ("seq", func(seq)), - ("meta", func(|a| a[0].get_meta())), - ("with-meta", func(|a| a[0].clone().with_meta(&a[1]))), - ("atom", func(|a| Ok(atom(&a[0])))), + ("meta", func(get_meta)), + ("with-meta", func(with_meta)), + ("atom", func(atom)), ("atom?", func(fn_is_type!(Atom(_)))), - ("deref", func(|a| a[0].deref())), - ("reset!", func(|a| a[0].reset_bang(&a[1]))), - ("swap!", func(|a| a[0].swap_bang(&a[1..].to_vec()))), + ("deref", func(deref)), + ("reset!", func(reset_bang)), + ("swap!", func(swap_bang)), ] } diff --git a/impls/rust/env.rs b/impls/rust/env.rs index e8f0ccc56b..2fad179021 100644 --- a/impls/rust/env.rs +++ b/impls/rust/env.rs @@ -3,13 +3,12 @@ use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; -use crate::types::MalErr::ErrString; -use crate::types::MalVal::{List, Nil, Sym, Vector}; -use crate::types::{error, MalErr, MalRet, MalVal}; +use crate::types::MalVal::{List, Sym, Vector}; +use crate::types::{error, list, MalRet, MalVal}; pub struct EnvStruct { data: RefCell>, - pub outer: Option, + outer: Option, } pub type Env = Rc; @@ -25,14 +24,14 @@ pub fn env_new(outer: Option) -> Env { } // TODO: mbinds and exprs as & types -pub fn env_bind(outer: Option, mbinds: &MalVal, exprs: Vec) -> Result { - let env = env_new(outer); +pub fn env_bind(outer: Env, mbinds: &MalVal, exprs: Vec) -> Result { + let env = env_new(Some(outer)); match mbinds { List(binds, _) | Vector(binds, _) => { for (i, b) in binds.iter().enumerate() { match b { Sym(s) if s == "&" => { - env_set(&env, &binds[i + 1], list!(exprs[i..].to_vec()))?; + env_set(&env, &binds[i + 1], list(exprs[i..].to_vec()))?; break; } _ => { @@ -42,7 +41,7 @@ pub fn env_bind(outer: Option, mbinds: &MalVal, exprs: Vec) -> Resu } Ok(env) } - _ => Err(ErrString("env_bind binds not List/Vector".to_string())), + _ => error("env_bind binds not List/Vector"), } } @@ -59,14 +58,6 @@ pub fn env_get(env: &Env, key: &str) -> Option { } } -pub fn env_find_repl(env: &Env) -> Env { - let mut mut_env = env; - while let Some(outer) = &mut_env.outer { - mut_env = outer; - } - mut_env.clone() -} - pub fn env_set(env: &Env, key: &MalVal, val: MalVal) -> MalRet { match key { Sym(s) => { diff --git a/impls/rust/printer.rs b/impls/rust/printer.rs index d24ea58558..59d5c695fa 100644 --- a/impls/rust/printer.rs +++ b/impls/rust/printer.rs @@ -1,5 +1,7 @@ -use crate::types::MalVal; -use crate::types::MalVal::{Atom, Bool, Func, Hash, Int, List, MalFunc, Nil, Str, Sym, Vector}; +use crate::types::MalVal::{ + Atom, Bool, Func, Hash, Int, Kwd, List, MalFunc, Nil, Str, Sym, Vector, +}; +use crate::types::{unwrap_map_key, FuncStruct, MalVal}; fn escape_str(s: &str) -> String { s.chars() @@ -21,10 +23,9 @@ impl MalVal { Bool(false) => String::from("false"), Int(i) => format!("{}", i), //Float(f) => format!("{}", f), + Kwd(s) => format!(":{}", s), Str(s) => { - if let Some(keyword) = s.strip_prefix('\u{29e}') { - format!(":{}", keyword) - } else if print_readably { + if print_readably { format!("\"{}\"", escape_str(s)) } else { s.clone() @@ -36,26 +37,20 @@ impl MalVal { Hash(hm, _) => { let l: Vec = hm .iter() - .flat_map(|(k, v)| vec![Str(k.to_string()), v.clone()]) + .flat_map(|(k, v)| vec![unwrap_map_key(k), v.clone()]) .collect(); pr_seq(&l, print_readably, "{", "}", " ") } Func(_, _) => String::from("#"), - MalFunc { + MalFunc(FuncStruct { ast: a, params: p, .. - } => format!("(fn* {} {})", p.pr_str(true), a.pr_str(true)), + }) => format!("(fn* {} {})", p.pr_str(true), a.pr_str(true)), Atom(a) => format!("(atom {})", a.borrow().pr_str(true)), } } } -pub fn pr_seq( - seq: &[MalVal], - print_readably: bool, - start: &str, - end: &str, - join: &str -) -> String { +pub fn pr_seq(seq: &[MalVal], print_readably: bool, start: &str, end: &str, join: &str) -> String { let strs: Vec = seq.iter().map(|x| x.pr_str(print_readably)).collect(); format!("{}{}{}", start, strs.join(join), end) } diff --git a/impls/rust/reader.rs b/impls/rust/reader.rs index 5a82529a1a..929558e8aa 100644 --- a/impls/rust/reader.rs +++ b/impls/rust/reader.rs @@ -1,9 +1,8 @@ use regex::{Captures, Regex}; use std::rc::Rc; -use crate::types::MalErr::ErrString; -use crate::types::MalVal::{Bool, Int, List, Nil, Str, Sym, Vector}; -use crate::types::{error, hash_map, MalErr, MalRet, MalVal}; +use crate::types::MalVal::{Bool, Int, Kwd, List, Nil, Str, Sym}; +use crate::types::{error, hash_map, list, vector, MalRet, MalVal}; #[derive(Debug, Clone)] struct Reader { @@ -12,70 +11,69 @@ struct Reader { } impl Reader { - fn next(&mut self) -> Result { + fn next(&mut self) -> Result { self.pos += 1; Ok(self .tokens .get(self.pos - 1) - .ok_or_else(|| ErrString("underflow".to_string()))? + .ok_or_else(|| Str("underflow".to_string()))? .to_string()) } - fn peek(&self) -> Result { + fn peek(&self) -> Result { Ok(self .tokens .get(self.pos) - .ok_or_else(|| ErrString("underflow".to_string()))? + .ok_or_else(|| Str("underflow".to_string()))? .to_string()) } } -fn tokenize(str: &str) -> Vec { - lazy_static! { - static ref RE: Regex = Regex::new( - r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]+)"### - ) - .unwrap(); - } +thread_local! { + static TOKENIZE_RE: Regex = Regex::new( + r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]+)"### + ).unwrap(); + static UNESCAPE_RE: Regex = Regex::new(r#"\\(.)"#).unwrap(); + static INT_RE: Regex = Regex::new(r"^-?[0-9]+$").unwrap(); + static STR_RE: Regex = Regex::new(r#""(?:\\.|[^\\"])*""#).unwrap(); +} - let mut res = vec![]; - for cap in RE.captures_iter(str) { - if cap[1].starts_with(';') { - continue; +fn tokenize(str: &str) -> Vec { + TOKENIZE_RE.with(|re| { + let mut res = vec![]; + for cap in re.captures_iter(str) { + if cap[1].starts_with(';') { + continue; + } + res.push(String::from(&cap[1])); } - res.push(String::from(&cap[1])); - } - res + res + }) } fn unescape_str(s: &str) -> String { - lazy_static! { - static ref RE: Regex = Regex::new(r#"\\(.)"#).unwrap(); - } - RE.replace_all(s, |caps: &Captures| { - if &caps[1] == "n" { "\n" } else { &caps[1] }.to_string() + UNESCAPE_RE.with(|re| { + re.replace_all(s, |caps: &Captures| { + if &caps[1] == "n" { "\n" } else { &caps[1] }.to_string() + }) + .to_string() }) - .to_string() } fn read_atom(rdr: &mut Reader) -> MalRet { - lazy_static! { - static ref INT_RE: Regex = Regex::new(r"^-?[0-9]+$").unwrap(); - static ref STR_RE: Regex = Regex::new(r#""(?:\\.|[^\\"])*""#).unwrap(); - } let token = rdr.next()?; match &token[..] { "nil" => Ok(Nil), "false" => Ok(Bool(false)), "true" => Ok(Bool(true)), _ => { - if INT_RE.is_match(&token) { + if INT_RE.with(|re| re.is_match(&token)) { Ok(Int(token.parse().unwrap())) - } else if STR_RE.is_match(&token) { + } else if STR_RE.with(|re| re.is_match(&token)) { Ok(Str(unescape_str(&token[1..token.len() - 1]))) } else if token.starts_with('\"') { error("expected '\"', got EOF") } else if let Some(keyword) = token.strip_prefix(':') { - Ok(Str(format!("\u{29e}{}", keyword))) + Ok(Kwd(String::from(keyword))) } else { Ok(Sym(token.to_string())) } @@ -83,7 +81,7 @@ fn read_atom(rdr: &mut Reader) -> MalRet { } } -fn read_seq(rdr: &mut Reader, end: &str) -> MalRet { +fn read_seq(rdr: &mut Reader, end: &str) -> Result, MalVal> { let mut seq: Vec = vec![]; rdr.next()?; loop { @@ -94,15 +92,10 @@ fn read_seq(rdr: &mut Reader, end: &str) -> MalRet { if token == end { break; } - seq.push(read_form(rdr)?) + seq.push(read_form(rdr)?); } let _ = rdr.next(); - match end { - ")" => Ok(list!(seq)), - "]" => Ok(vector!(seq)), - "}" => hash_map(seq), - _ => error("read_seq unknown end value"), - } + Ok(seq) } fn read_form(rdr: &mut Reader) -> MalRet { @@ -110,35 +103,35 @@ fn read_form(rdr: &mut Reader) -> MalRet { match &token[..] { "'" => { let _ = rdr.next(); - Ok(list![Sym("quote".to_string()), read_form(rdr)?]) + Ok(list!(Sym("quote".to_string()), read_form(rdr)?)) } "`" => { let _ = rdr.next(); - Ok(list![Sym("quasiquote".to_string()), read_form(rdr)?]) + Ok(list!(Sym("quasiquote".to_string()), read_form(rdr)?)) } "~" => { let _ = rdr.next(); - Ok(list![Sym("unquote".to_string()), read_form(rdr)?]) + Ok(list!(Sym("unquote".to_string()), read_form(rdr)?)) } "~@" => { let _ = rdr.next(); - Ok(list![Sym("splice-unquote".to_string()), read_form(rdr)?]) + Ok(list!(Sym("splice-unquote".to_string()), read_form(rdr)?)) } "^" => { let _ = rdr.next(); let meta = read_form(rdr)?; - Ok(list![Sym("with-meta".to_string()), read_form(rdr)?, meta]) + Ok(list!(Sym("with-meta".to_string()), read_form(rdr)?, meta)) } "@" => { let _ = rdr.next(); - Ok(list![Sym("deref".to_string()), read_form(rdr)?]) + Ok(list!(Sym("deref".to_string()), read_form(rdr)?)) } ")" => error("unexpected ')'"), - "(" => read_seq(rdr, ")"), + "(" => Ok(list(read_seq(rdr, ")")?)), "]" => error("unexpected ']'"), - "[" => read_seq(rdr, "]"), + "[" => Ok(vector(read_seq(rdr, "]")?)), "}" => error("unexpected '}'"), - "{" => read_seq(rdr, "}"), + "{" => hash_map(read_seq(rdr, "}")?.to_vec()), _ => read_atom(rdr), } } @@ -149,8 +142,5 @@ pub fn read_str(str: &str) -> MalRet { if tokens.is_empty() { return error("no input"); } - read_form(&mut Reader { - pos: 0, - tokens - }) + read_form(&mut Reader { pos: 0, tokens }) } diff --git a/impls/rust/readline.rs b/impls/rust/readline.rs new file mode 100644 index 0000000000..fab6f693bc --- /dev/null +++ b/impls/rust/readline.rs @@ -0,0 +1,43 @@ +extern crate rustyline; + +// A global variable makes more sense than passing the readline editor +// as an argument to *every* core function just for readline. + +struct S { + e: rustyline::Editor<(), rustyline::history::DefaultHistory>, +} + +impl Drop for S { + fn drop(&mut self) { + self.e.save_history(".mal-history").unwrap() + } +} + +thread_local! { + static ED : std::cell::RefCell = { + let mut e = rustyline::Editor::new().unwrap(); + if e.load_history(".mal-history").is_err() { + println!("No previous history."); + } + std::cell::RefCell::new(S{e}) + } +} + +pub fn readline(prompt: &str) -> Option { + ED.with_borrow_mut(|s| { + let r = s.e.readline(prompt); + if let Err(rustyline::error::ReadlineError::Eof) = r { + None + } else { + let mut line = r.unwrap(); + // Remove any trailing \n or \r\n + while line.ends_with('\n') || line.ends_with('\r') { + line.pop(); + } + if !line.is_empty() { + let _ = s.e.add_history_entry(&line); + } + Some(line.to_string()) + } + }) +} diff --git a/impls/rust/run b/impls/rust/run index 06764851ce..d67524c747 100755 --- a/impls/rust/run +++ b/impls/rust/run @@ -1,2 +1,2 @@ -#!/bin/bash +#!/bin/sh exec $(dirname $0)/target/release/${STEP:-stepA_mal} "${@}" diff --git a/impls/rust/step0_repl.rs b/impls/rust/step0_repl.rs index b3311812b0..9fb22be941 100644 --- a/impls/rust/step0_repl.rs +++ b/impls/rust/step0_repl.rs @@ -1,31 +1,15 @@ -extern crate rustyline; +#![allow(non_snake_case)] -use rustyline::error::ReadlineError; -use rustyline::Editor; +mod readline; fn main() { // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - println!("{}", line); - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; - } + // main repl loop + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + println!("{}", line); } } + println!(); } diff --git a/impls/rust/step1_read_print.rs b/impls/rust/step1_read_print.rs index 8aa9eaa38f..7a8445623f 100644 --- a/impls/rust/step1_read_print.rs +++ b/impls/rust/step1_read_print.rs @@ -1,51 +1,51 @@ -#[macro_use] -extern crate lazy_static; +#![allow(non_snake_case)] + extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] #[allow(dead_code)] mod types; -use crate::types::format_error; -mod printer; -mod reader; -// TODO: figure out a way to avoid including env +use crate::types::{MalRet, MalVal}; #[allow(dead_code)] mod env; +mod printer; +mod reader; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str) +} + +// eval +fn eval(ast: MalVal) -> MalRet { + Ok(ast) +} + +// print +fn print(ast: MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str) -> Result { + let ast = read(str)?; + let exp = eval(ast)?; + Ok(print(exp)) +} fn main() { // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match reader::read_str(&line) { - Ok(mv) => { - println!("{}", mv.pr_str(true)); - } - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + // main repl loop + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); } diff --git a/impls/rust/step2_eval.rs b/impls/rust/step2_eval.rs index 3f9556e1a5..fd42af8011 100644 --- a/impls/rust/step2_eval.rs +++ b/impls/rust/step2_eval.rs @@ -1,31 +1,35 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] #[allow(dead_code)] mod types; -use crate::types::MalErr::ErrString; -use crate::types::MalVal::{Hash, Int, List, Nil, Sym, Vector}; -use crate::types::{error, format_error, func, MalArgs, MalErr, MalRet, MalVal}; -mod printer; -mod reader; -// TODO: figure out a way to avoid including env +use crate::types::MalVal::{Func, Hash, Int, List, Nil, Sym, Vector}; +use crate::types::{error, func, vector, MalArgs, MalRet, MalVal}; #[allow(dead_code)] mod env; +mod printer; +mod reader; pub type Env = FnvHashMap; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -35,16 +39,16 @@ fn read(str: &str) -> MalRet { fn eval(ast: &MalVal, env: &Env) -> MalRet { // println!("EVAL: {}", print(&ast)); match ast { - Sym(sym) => Ok(env - .get(sym) - .ok_or_else(|| ErrString(format!("'{}' not found", sym)))? - .clone()), + Sym(s) => match env.get(s) { + Some(r) => Ok(r.clone()), + None => error(&format!("'{}' not found", s)), + }, Vector(v, _) => { let mut lst: MalArgs = vec![]; for a in v.iter() { lst.push(eval(a, env)?); } - Ok(vector!(lst)) + Ok(vector(lst)) } Hash(hm, _) => { let mut new_hm: FnvHashMap = FnvHashMap::default(); @@ -74,7 +78,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -89,10 +93,6 @@ fn int_op(op: fn(i64, i64) -> i64, a: MalArgs) -> MalRet { fn main() { // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } let mut repl_env = Env::default(); repl_env.insert("+".to_string(), func(|a: MalArgs| int_op(|i, j| i + j, a))); @@ -100,25 +100,14 @@ fn main() { repl_env.insert("*".to_string(), func(|a: MalArgs| int_op(|i, j| i * j, a))); repl_env.insert("/".to_string(), func(|a: MalArgs| int_op(|i, j| i / j, a))); - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + // main repl loop + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, &repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); } diff --git a/impls/rust/step3_env.rs b/impls/rust/step3_env.rs index 528af48e49..d22a55debd 100644 --- a/impls/rust/step3_env.rs +++ b/impls/rust/step3_env.rs @@ -1,28 +1,35 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] #[allow(dead_code)] mod types; -use crate::types::MalVal::{Bool, Hash, Int, List, Nil, Sym, Vector}; -use crate::types::{error, format_error, func, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::MalVal::{Bool, Func, Hash, Int, List, Nil, Sym, Vector}; +use crate::types::{error, func, vector, MalArgs, MalRet, MalVal}; +#[allow(dead_code)] mod env; mod printer; mod reader; use crate::env::{env_get, env_new, env_set, env_sets, Env}; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -37,14 +44,14 @@ fn eval(ast: &MalVal, env: &Env) -> MalRet { match ast { Sym(s) => match env_get(env, s) { Some(r) => Ok(r), - None => error (&format!("'{}' not found", s)), - } + None => error(&format!("'{}' not found", s)), + }, Vector(v, _) => { let mut lst: MalArgs = vec![]; for a in v.iter() { lst.push(eval(a, env)?); } - Ok(vector!(lst)) + Ok(vector(lst)) } Hash(hm, _) => { let mut new_hm: FnvHashMap = FnvHashMap::default(); @@ -59,9 +66,7 @@ fn eval(ast: &MalVal, env: &Env) -> MalRet { } let a0 = &l[0]; match a0 { - Sym(a0sym) if a0sym == "def!" => { - env_set(env, &l[1], eval(&l[2], env)?) - } + Sym(a0sym) if a0sym == "def!" => env_set(env, &l[1], eval(&l[2], env)?), Sym(a0sym) if a0sym == "let*" => { let let_env = &env_new(Some(env.clone())); let (a1, a2) = (&l[1], &l[2]); @@ -85,7 +90,7 @@ fn eval(ast: &MalVal, env: &Env) -> MalRet { args.push(eval(&l[i], env)?); } f.apply(args) - }, + } } } _ => Ok(ast.clone()), @@ -97,7 +102,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -112,10 +117,6 @@ fn int_op(op: fn(i64, i64) -> i64, a: MalArgs) -> MalRet { fn main() { // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } let repl_env = env_new(None); env_sets(&repl_env, "+", func(|a: MalArgs| int_op(|i, j| i + j, a))); @@ -123,25 +124,14 @@ fn main() { env_sets(&repl_env, "*", func(|a: MalArgs| int_op(|i, j| i * j, a))); env_sets(&repl_env, "/", func(|a: MalArgs| int_op(|i, j| i / j, a))); - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + // main repl loop + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, &repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); } diff --git a/impls/rust/step4_if_fn_do.rs b/impls/rust/step4_if_fn_do.rs index 1adfede538..051b20152b 100644 --- a/impls/rust/step4_if_fn_do.rs +++ b/impls/rust/step4_if_fn_do.rs @@ -1,29 +1,44 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] mod types; -use crate::types::MalVal::{Bool, Hash, List, MalFunc, Nil, Sym, Vector}; -use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Sym, Vector}; +use crate::types::{error, vector, FuncStruct, MalArgs, MalRet, MalVal}; mod env; mod printer; mod reader; -use crate::env::{env_get, env_new, env_set, env_sets, Env}; +use crate::env::{env_bind, env_get, env_new, env_set, env_sets, Env}; #[macro_use] mod core; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + MalFunc(FuncStruct { + ref ast, + ref env, + ref params, + .. + }) => { + let fn_env = &env_bind(env.clone(), params, args)?; + eval(ast, fn_env) + } + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -38,14 +53,14 @@ fn eval(ast: &MalVal, env: &Env) -> MalRet { match ast { Sym(s) => match env_get(env, s) { Some(r) => Ok(r), - None => error (&format!("'{}' not found", s)), - } + None => error(&format!("'{}' not found", s)), + }, Vector(v, _) => { let mut lst: MalArgs = vec![]; for a in v.iter() { lst.push(eval(a, env)?); } - Ok(vector!(lst)) + Ok(vector(lst)) } Hash(hm, _) => { let mut new_hm: FnvHashMap = FnvHashMap::default(); @@ -60,9 +75,7 @@ fn eval(ast: &MalVal, env: &Env) -> MalRet { } let a0 = &l[0]; match a0 { - Sym(a0sym) if a0sym == "def!" => { - env_set(env, &l[1], eval(&l[2], env)?) - } + Sym(a0sym) if a0sym == "def!" => env_set(env, &l[1], eval(&l[2], env)?), Sym(a0sym) if a0sym == "let*" => { let let_env = &env_new(Some(env.clone())); let (a1, a2) = (&l[1], &l[2]); @@ -96,14 +109,13 @@ fn eval(ast: &MalVal, env: &Env) -> MalRet { } Sym(a0sym) if a0sym == "fn*" => { let (a1, a2) = (l[1].clone(), l[2].clone()); - Ok(MalFunc { - eval, + Ok(MalFunc(FuncStruct { ast: Rc::new(a2), env: env.clone(), params: Rc::new(a1), is_macro: false, meta: Rc::new(Nil), - }) + })) } _ => { let f = eval(a0, env)?; @@ -112,7 +124,7 @@ fn eval(ast: &MalVal, env: &Env) -> MalRet { args.push(eval(&l[i], env)?); } f.apply(args) - }, + } } } _ => Ok(ast.clone()), @@ -124,7 +136,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -141,10 +153,6 @@ fn re(str: &str, env: &Env) { fn main() { // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } // core.rs: defined using rust let repl_env = env_new(None); @@ -156,25 +164,13 @@ fn main() { re("(def! not (fn* (a) (if a false true)))", &repl_env); // main repl loop - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, &repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); } diff --git a/impls/rust/step5_tco.rs b/impls/rust/step5_tco.rs index f8b2735290..f266af4ab2 100644 --- a/impls/rust/step5_tco.rs +++ b/impls/rust/step5_tco.rs @@ -1,22 +1,19 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] mod types; use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Sym, Vector}; -use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::{error, vector, FuncStruct, MalArgs, MalRet, MalVal}; mod env; mod printer; mod reader; @@ -24,6 +21,24 @@ use crate::env::{env_bind, env_get, env_new, env_set, env_sets, Env}; #[macro_use] mod core; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + MalFunc(FuncStruct { + ref ast, + ref env, + ref params, + .. + }) => { + let fn_env = &env_bind(env.clone(), params, args)?; + eval(ast, fn_env) + } + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -44,25 +59,25 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { _ => println!("EVAL: {}", print(ast)), } match ast { - Sym(s) => match env_get(env, s) { - Some(r) => return Ok(r), - None => return error(&format!("'{}' not found", s)), - } - Vector(v, _) => { - let mut lst: MalArgs = vec![]; - for a in v.iter() { - lst.push(eval(a, env)?); + Sym(s) => match env_get(env, s) { + Some(r) => return Ok(r), + None => return error(&format!("'{}' not found", s)), + }, + Vector(v, _) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { + lst.push(eval(a, env)?); + } + return Ok(vector(lst)); } - return Ok(vector!(lst)); - } - Hash(hm, _) => { - let mut new_hm: FnvHashMap = FnvHashMap::default(); - for (k, v) in hm.iter() { - new_hm.insert(k.to_string(), eval(v, env)?); + Hash(hm, _) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k, v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v, env)?); + } + return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); } - return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); - } - List(l, _) => { + List(l, _) => { if l.is_empty() { return Ok(ast.clone()); } @@ -117,49 +132,45 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { } Sym(a0sym) if a0sym == "fn*" => { let (a1, a2) = (l[1].clone(), l[2].clone()); - return Ok(MalFunc { - eval, + return Ok(MalFunc(FuncStruct { ast: Rc::new(a2), env: env.clone(), params: Rc::new(a1), is_macro: false, meta: Rc::new(Nil), - }) + })); } - _ => match eval(a0, env) { - Ok(f @ Func(_, _)) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - return f.apply(args); - } - Ok(MalFunc { - ast: mast, - env: menv, - params: mparams, - .. - }) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - live_env = env_bind(Some(menv.clone()), &mparams, args.to_vec())?; - env = &live_env; - live_ast = (*mast).clone(); - ast = &live_ast; - continue 'tco; - } - Ok(_) => return error("attempt to call non-function"), - e @ Err(_) => return e, + _ => match eval(a0, env)? { + f @ Func(_, _) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + return f.apply(args); + } + MalFunc(FuncStruct { + ast: mast, + env: menv, + params: mparams, + .. + }) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + live_env = env_bind(menv.clone(), &mparams, args)?; + env = &live_env; + live_ast = (*mast).clone(); + ast = &live_ast; + continue 'tco; + } + _ => return error("attempt to call non-function"), }, } - } - _ => return Ok(ast.clone()), + } + _ => return Ok(ast.clone()), }; - } // end 'tco loop - } // print @@ -167,7 +178,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -184,10 +195,6 @@ fn re(str: &str, env: &Env) { fn main() { // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } // core.rs: defined using rust let repl_env = env_new(None); @@ -199,25 +206,13 @@ fn main() { re("(def! not (fn* (a) (if a false true)))", &repl_env); // main repl loop - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, &repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); } diff --git a/impls/rust/step6_file.rs b/impls/rust/step6_file.rs index 1447e46f14..99b76188e3 100644 --- a/impls/rust/step6_file.rs +++ b/impls/rust/step6_file.rs @@ -1,29 +1,44 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] mod types; use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Str, Sym, Vector}; -use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::{error, list, vector, FuncStruct, MalArgs, MalRet, MalVal}; mod env; mod printer; mod reader; -use crate::env::{env_bind, env_find_repl, env_get, env_new, env_set, env_sets, Env}; +use crate::env::{env_bind, env_get, env_new, env_set, env_sets, Env}; #[macro_use] mod core; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + MalFunc(FuncStruct { + ref ast, + ref env, + ref params, + .. + }) => { + let fn_env = &env_bind(env.clone(), params, args)?; + eval(ast, fn_env) + } + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -44,25 +59,25 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { _ => println!("EVAL: {}", print(ast)), } match ast { - Sym(s) => match env_get(env, s) { - Some(r) => return Ok(r), - None => return error(&format!("'{}' not found", s)), - } - Vector(v, _) => { - let mut lst: MalArgs = vec![]; - for a in v.iter() { - lst.push(eval(a, env)?); + Sym(s) => match env_get(env, s) { + Some(r) => return Ok(r), + None => return error(&format!("'{}' not found", s)), + }, + Vector(v, _) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { + lst.push(eval(a, env)?); + } + return Ok(vector(lst)); } - return Ok(vector!(lst)); - } - Hash(hm, _) => { - let mut new_hm: FnvHashMap = FnvHashMap::default(); - for (k, v) in hm.iter() { - new_hm.insert(k.to_string(), eval(v, env)?); + Hash(hm, _) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k, v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v, env)?); + } + return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); } - return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); - } - List(l, _) => { + List(l, _) => { if l.is_empty() { return Ok(ast.clone()); } @@ -117,58 +132,45 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { } Sym(a0sym) if a0sym == "fn*" => { let (a1, a2) = (l[1].clone(), l[2].clone()); - return Ok(MalFunc { - eval, + return Ok(MalFunc(FuncStruct { ast: Rc::new(a2), env: env.clone(), params: Rc::new(a1), is_macro: false, meta: Rc::new(Nil), - }) - } - Sym(a0sym) if a0sym == "eval" => { - // Hard to implement without global variables. - // Normal argument evaluation. - live_ast = eval(&l[1], env)?; - ast = &live_ast; - live_env = env_find_repl(env); - env = &live_env; - continue 'tco; + })); } - _ => match eval(a0, env) { - Ok(f @ Func(_, _)) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - return f.apply(args); - } - Ok(MalFunc { - ast: mast, - env: menv, - params: mparams, - .. - }) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - live_env = env_bind(Some(menv.clone()), &mparams, args.to_vec())?; - env = &live_env; - live_ast = (*mast).clone(); - ast = &live_ast; - continue 'tco; - } - Ok(_) => return error("attempt to call non-function"), - e @ Err(_) => return e, + _ => match eval(a0, env)? { + f @ Func(_, _) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + return f.apply(args); + } + MalFunc(FuncStruct { + ast: mast, + env: menv, + params: mparams, + .. + }) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + live_env = env_bind(menv.clone(), &mparams, args)?; + env = &live_env; + live_ast = (*mast).clone(); + ast = &live_ast; + continue 'tco; + } + _ => return error("attempt to call non-function"), }, } - } - _ => return Ok(ast.clone()), + } + _ => return Ok(ast.clone()), }; - } // end 'tco loop - } // print @@ -176,7 +178,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -191,56 +193,50 @@ fn re(str: &str, env: &Env) { panic!("error during startup"); } -fn main() { - let mut args = std::env::args(); - let arg1 = args.nth(1); - - // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } +thread_local! { + static REPL_ENV: Env = env_new(None); +} - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns() { - env_sets(&repl_env, k, v); - } - env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); - - // core.mal: defined using the language itself - re("(def! not (fn* (a) (if a false true)))", &repl_env); - re( - "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", - &repl_env, - ); - - if let Some(f) = arg1 { - // Invoked with arguments - re(&format!("(load-file \"{}\")", f), &repl_env); - std::process::exit(0); - } +fn main() { + REPL_ENV.with(|repl_env| { + let mut args = std::env::args(); + let arg1 = args.nth(1); + + // `()` can be used when no completer is required + + // core.rs: defined using rust + env_sets( + repl_env, + "eval", + types::func(|a| REPL_ENV.with(|e| eval(&a[0], e))), + ); + for (k, v) in core::ns() { + env_sets(repl_env, k, v); + } + env_sets(repl_env, "*ARGV*", list(args.map(Str).collect())); + + // core.mal: defined using the language itself + re("(def! not (fn* (a) (if a false true)))", repl_env); + re( + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + repl_env, + ); + + if let Some(f) = arg1 { + // Invoked with arguments + re(&format!("(load-file \"{}\")", f), repl_env); + std::process::exit(0); + } - // main repl loop - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } + // main repl loop + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; - } } - } + println!(); + }) } diff --git a/impls/rust/step7_quote.rs b/impls/rust/step7_quote.rs index 2c598065a5..93d1bc6938 100644 --- a/impls/rust/step7_quote.rs +++ b/impls/rust/step7_quote.rs @@ -1,29 +1,44 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] mod types; use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Str, Sym, Vector}; -use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::{error, list, vector, FuncStruct, MalArgs, MalRet, MalVal}; mod env; mod printer; mod reader; -use crate::env::{env_bind, env_find_repl, env_get, env_new, env_set, env_sets, Env}; +use crate::env::{env_bind, env_get, env_new, env_set, env_sets, Env}; #[macro_use] mod core; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + MalFunc(FuncStruct { + ref ast, + ref env, + ref params, + .. + }) => { + let fn_env = &env_bind(env.clone(), params, args)?; + eval(ast, fn_env) + } + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -32,19 +47,19 @@ fn read(str: &str) -> MalRet { // eval fn qq_iter(elts: &MalArgs) -> MalVal { - let mut acc = list![]; + let mut acc = list!(); for elt in elts.iter().rev() { if let List(v, _) = elt { if v.len() == 2 { if let Sym(ref s) = v[0] { if s == "splice-unquote" { - acc = list![Sym("concat".to_string()), v[1].clone(), acc]; + acc = list!(Sym("concat".to_string()), v[1].clone(), acc); continue; } } } } - acc = list![Sym("cons".to_string()), quasiquote(elt), acc]; + acc = list!(Sym("cons".to_string()), quasiquote(elt), acc); } acc } @@ -60,9 +75,9 @@ fn quasiquote(ast: &MalVal) -> MalVal { } } qq_iter(v) - }, - Vector(v, _) => list![Sym("vec".to_string()), qq_iter(v)], - Hash(_, _) | Sym(_)=> list![Sym("quote".to_string()), ast.clone()], + } + Vector(v, _) => list!(Sym("vec".to_string()), qq_iter(v)), + Hash(_, _) | Sym(_) => list!(Sym("quote".to_string()), ast.clone()), _ => ast.clone(), } } @@ -81,25 +96,25 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { _ => println!("EVAL: {}", print(ast)), } match ast { - Sym(s) => match env_get(env, s) { - Some(r) => return Ok(r), - None => return error(&format!("'{}' not found", s)), - } - Vector(v, _) => { - let mut lst: MalArgs = vec![]; - for a in v.iter() { - lst.push(eval(a, env)?); + Sym(s) => match env_get(env, s) { + Some(r) => return Ok(r), + None => return error(&format!("'{}' not found", s)), + }, + Vector(v, _) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { + lst.push(eval(a, env)?); + } + return Ok(vector(lst)); } - return Ok(vector!(lst)); - } - Hash(hm, _) => { - let mut new_hm: FnvHashMap = FnvHashMap::default(); - for (k, v) in hm.iter() { - new_hm.insert(k.to_string(), eval(v, env)?); + Hash(hm, _) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k, v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v, env)?); + } + return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); } - return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); - } - List(l, _) => { + List(l, _) => { if l.is_empty() { return Ok(ast.clone()); } @@ -160,58 +175,45 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { } Sym(a0sym) if a0sym == "fn*" => { let (a1, a2) = (l[1].clone(), l[2].clone()); - return Ok(MalFunc { - eval, + return Ok(MalFunc(FuncStruct { ast: Rc::new(a2), env: env.clone(), params: Rc::new(a1), is_macro: false, meta: Rc::new(Nil), - }) + })); } - Sym(a0sym) if a0sym == "eval" => { - // Hard to implement without global variables. - // Normal argument evaluation. - live_ast = eval(&l[1], env)?; - ast = &live_ast; - live_env = env_find_repl(env); - env = &live_env; - continue 'tco; - } - _ => match eval(a0, env) { - Ok(f @ Func(_, _)) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - return f.apply(args); - } - Ok(MalFunc { - ast: mast, - env: menv, - params: mparams, - .. - }) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - live_env = env_bind(Some(menv.clone()), &mparams, args.to_vec())?; - env = &live_env; - live_ast = (*mast).clone(); - ast = &live_ast; - continue 'tco; - } - Ok(_) => return error("attempt to call non-function"), - e @ Err(_) => return e, + _ => match eval(a0, env)? { + f @ Func(_, _) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + return f.apply(args); + } + MalFunc(FuncStruct { + ast: mast, + env: menv, + params: mparams, + .. + }) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + live_env = env_bind(menv.clone(), &mparams, args)?; + env = &live_env; + live_ast = (*mast).clone(); + ast = &live_ast; + continue 'tco; + } + _ => return error("attempt to call non-function"), }, } - } - _ => return Ok(ast.clone()), + } + _ => return Ok(ast.clone()), }; - } // end 'tco loop - } // print @@ -219,7 +221,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -234,56 +236,50 @@ fn re(str: &str, env: &Env) { panic!("error during startup"); } +thread_local! { + static REPL_ENV: Env = env_new(None); +} + fn main() { - let mut args = std::env::args(); - let arg1 = args.nth(1); + REPL_ENV.with(|repl_env| { + let mut args = std::env::args(); + let arg1 = args.nth(1); - // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } + // `()` can be used when no completer is required - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns() { - env_sets(&repl_env, k, v); - } - env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + // core.rs: defined using rust + env_sets( + repl_env, + "eval", + types::func(|a| REPL_ENV.with(|e| eval(&a[0], e))), + ); + for (k, v) in core::ns() { + env_sets(repl_env, k, v); + } + env_sets(repl_env, "*ARGV*", list(args.map(Str).collect())); - // core.mal: defined using the language itself - re("(def! not (fn* (a) (if a false true)))", &repl_env); - re( - "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", - &repl_env, - ); + // core.mal: defined using the language itself + re("(def! not (fn* (a) (if a false true)))", repl_env); + re( + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + repl_env, + ); - if let Some(f) = arg1 { - // Invoked with arguments - re(&format!("(load-file \"{}\")", f), &repl_env); - std::process::exit(0); - } + if let Some(f) = arg1 { + // Invoked with arguments + re(&format!("(load-file \"{}\")", f), repl_env); + std::process::exit(0); + } - // main repl loop - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } + // main repl loop + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; - } } - } + println!(); + }) } diff --git a/impls/rust/step8_macros.rs b/impls/rust/step8_macros.rs index 96ff6f6e9e..0e53b9a4a2 100644 --- a/impls/rust/step8_macros.rs +++ b/impls/rust/step8_macros.rs @@ -1,29 +1,44 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] mod types; use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Str, Sym, Vector}; -use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::{error, list, vector, FuncStruct, MalArgs, MalRet, MalVal}; mod env; mod printer; mod reader; -use crate::env::{env_bind, env_find_repl, env_get, env_new, env_set, env_sets, Env}; +use crate::env::{env_bind, env_get, env_new, env_set, env_sets, Env}; #[macro_use] mod core; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + MalFunc(FuncStruct { + ref ast, + ref env, + ref params, + .. + }) => { + let fn_env = &env_bind(env.clone(), params, args)?; + eval(ast, fn_env) + } + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -32,19 +47,19 @@ fn read(str: &str) -> MalRet { // eval fn qq_iter(elts: &MalArgs) -> MalVal { - let mut acc = list![]; + let mut acc = list!(); for elt in elts.iter().rev() { if let List(v, _) = elt { if v.len() == 2 { if let Sym(ref s) = v[0] { if s == "splice-unquote" { - acc = list![Sym("concat".to_string()), v[1].clone(), acc]; + acc = list!(Sym("concat".to_string()), v[1].clone(), acc); continue; } } } } - acc = list![Sym("cons".to_string()), quasiquote(elt), acc]; + acc = list!(Sym("cons".to_string()), quasiquote(elt), acc); } acc } @@ -60,9 +75,9 @@ fn quasiquote(ast: &MalVal) -> MalVal { } } qq_iter(v) - }, - Vector(v, _) => list![Sym("vec".to_string()), qq_iter(v)], - Hash(_, _) | Sym(_)=> list![Sym("quote".to_string()), ast.clone()], + } + Vector(v, _) => list!(Sym("vec".to_string()), qq_iter(v)), + Hash(_, _) | Sym(_) => list!(Sym("quote".to_string()), ast.clone()), _ => ast.clone(), } } @@ -81,25 +96,25 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { _ => println!("EVAL: {}", print(ast)), } match ast { - Sym(s) => match env_get(env, s) { - Some(r) => return Ok(r), - None => return error(&format!("'{}' not found", s)), - } - Vector(v, _) => { - let mut lst: MalArgs = vec![]; - for a in v.iter() { - lst.push(eval(a, env)?); + Sym(s) => match env_get(env, s) { + Some(r) => return Ok(r), + None => return error(&format!("'{}' not found", s)), + }, + Vector(v, _) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { + lst.push(eval(a, env)?); + } + return Ok(vector(lst)); } - return Ok(vector!(lst)); - } - Hash(hm, _) => { - let mut new_hm: FnvHashMap = FnvHashMap::default(); - for (k, v) in hm.iter() { - new_hm.insert(k.to_string(), eval(v, env)?); + Hash(hm, _) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k, v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v, env)?); + } + return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); } - return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); - } - List(l, _) => { + List(l, _) => { if l.is_empty() { return Ok(ast.clone()); } @@ -137,24 +152,16 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { let (a1, a2) = (&l[1], &l[2]); let r = eval(a2, env)?; match r { - MalFunc { - eval, - ast, - env, - params, - .. - } => return env_set( - &env, - a1, - MalFunc { - eval, - ast, - env: env.clone(), - params, - is_macro: true, - meta: Rc::new(Nil), - }, - ), + MalFunc(f) => { + return env_set( + env, + a1, + MalFunc(FuncStruct { + is_macro: true, + ..f.clone() + }), + ) + } _ => return error("set_macro on non-function"), } } @@ -185,64 +192,51 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { } Sym(a0sym) if a0sym == "fn*" => { let (a1, a2) = (l[1].clone(), l[2].clone()); - return Ok(MalFunc { - eval, + return Ok(MalFunc(FuncStruct { ast: Rc::new(a2), env: env.clone(), params: Rc::new(a1), is_macro: false, meta: Rc::new(Nil), - }) - } - Sym(a0sym) if a0sym == "eval" => { - // Hard to implement without global variables. - // Normal argument evaluation. - live_ast = eval(&l[1], env)?; - ast = &live_ast; - live_env = env_find_repl(env); - env = &live_env; - continue 'tco; + })); } - _ => match eval(a0, env) { - Ok(f @ MalFunc { is_macro: true, .. }) => { - let new_ast = f.apply(l[1..].to_vec())?; - live_ast = new_ast; - ast = &live_ast; - continue 'tco; - } - Ok(f @ Func(_, _)) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - return f.apply(args); - } - Ok(MalFunc { - ast: mast, - env: menv, - params: mparams, - .. - }) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - live_env = env_bind(Some(menv.clone()), &mparams, args.to_vec())?; - env = &live_env; - live_ast = (*mast).clone(); - ast = &live_ast; - continue 'tco; - } - Ok(_) => return error("attempt to call non-function"), - e @ Err(_) => return e, + _ => match eval(a0, env)? { + f @ MalFunc(FuncStruct { is_macro: true, .. }) => { + let new_ast = f.apply(l[1..].to_vec())?; + live_ast = new_ast; + ast = &live_ast; + continue 'tco; + } + f @ Func(_, _) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + return f.apply(args); + } + MalFunc(FuncStruct { + ast: mast, + env: menv, + params: mparams, + .. + }) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + live_env = env_bind(menv.clone(), &mparams, args)?; + env = &live_env; + live_ast = (*mast).clone(); + ast = &live_ast; + continue 'tco; + } + _ => return error("attempt to call non-function"), }, } - } - _ => return Ok(ast.clone()), + } + _ => return Ok(ast.clone()), }; - } // end 'tco loop - } // print @@ -250,7 +244,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -265,58 +259,48 @@ fn re(str: &str, env: &Env) { panic!("error during startup"); } +thread_local! { + static REPL_ENV: Env = env_new(None); +} + fn main() { + REPL_ENV.with(|repl_env| { let mut args = std::env::args(); let arg1 = args.nth(1); // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } // core.rs: defined using rust - let repl_env = env_new(None); + env_sets(repl_env, "eval", types::func(|a| REPL_ENV.with(|e| eval(&a[0], e)))); for (k, v) in core::ns() { - env_sets(&repl_env, k, v); + env_sets(repl_env, k, v); } - env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + env_sets(repl_env, "*ARGV*", list(args.map(Str).collect())); // core.mal: defined using the language itself - re("(def! not (fn* (a) (if a false true)))", &repl_env); + re("(def! not (fn* (a) (if a false true)))", repl_env); re( "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", - &repl_env, + repl_env, ); re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", - &repl_env); + repl_env); if let Some(f) = arg1 { // Invoked with arguments - re(&format!("(load-file \"{}\")", f), &repl_env); + re(&format!("(load-file \"{}\")", f), repl_env); std::process::exit(0); } // main repl loop - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); + }) } diff --git a/impls/rust/step9_try.rs b/impls/rust/step9_try.rs index 7b56dd877e..6bb04041f9 100644 --- a/impls/rust/step9_try.rs +++ b/impls/rust/step9_try.rs @@ -1,30 +1,44 @@ +#![allow(non_snake_case)] + use std::rc::Rc; //use std::collections::HashMap; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] mod types; -use crate::types::MalErr::{ErrMalVal, ErrString}; use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Str, Sym, Vector}; -use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::{error, list, vector, FuncStruct, MalArgs, MalRet, MalVal}; mod env; mod printer; mod reader; -use crate::env::{env_bind, env_find_repl, env_get, env_new, env_set, env_sets, Env}; +use crate::env::{env_bind, env_get, env_new, env_set, env_sets, Env}; #[macro_use] mod core; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + MalFunc(FuncStruct { + ref ast, + ref env, + ref params, + .. + }) => { + let fn_env = &env_bind(env.clone(), params, args)?; + eval(ast, fn_env) + } + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -33,19 +47,19 @@ fn read(str: &str) -> MalRet { // eval fn qq_iter(elts: &MalArgs) -> MalVal { - let mut acc = list![]; + let mut acc = list!(); for elt in elts.iter().rev() { if let List(v, _) = elt { if v.len() == 2 { if let Sym(ref s) = v[0] { if s == "splice-unquote" { - acc = list![Sym("concat".to_string()), v[1].clone(), acc]; + acc = list!(Sym("concat".to_string()), v[1].clone(), acc); continue; } } } } - acc = list![Sym("cons".to_string()), quasiquote(elt), acc]; + acc = list!(Sym("cons".to_string()), quasiquote(elt), acc); } acc } @@ -61,9 +75,9 @@ fn quasiquote(ast: &MalVal) -> MalVal { } } qq_iter(v) - }, - Vector(v, _) => list![Sym("vec".to_string()), qq_iter(v)], - Hash(_, _) | Sym(_)=> list![Sym("quote".to_string()), ast.clone()], + } + Vector(v, _) => list!(Sym("vec".to_string()), qq_iter(v)), + Hash(_, _) | Sym(_) => list!(Sym("quote".to_string()), ast.clone()), _ => ast.clone(), } } @@ -82,25 +96,25 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { _ => println!("EVAL: {}", print(ast)), } match ast { - Sym(s) => match env_get(env, s) { - Some(r) => return Ok(r), - None => return error(&format!("'{}' not found", s)), - } - Vector(v, _) => { - let mut lst: MalArgs = vec![]; - for a in v.iter() { - lst.push(eval(a, env)?); + Sym(s) => match env_get(env, s) { + Some(r) => return Ok(r), + None => return error(&format!("'{}' not found", s)), + }, + Vector(v, _) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { + lst.push(eval(a, env)?); + } + return Ok(vector(lst)); } - return Ok(vector!(lst)); - } - Hash(hm, _) => { - let mut new_hm: FnvHashMap = FnvHashMap::default(); - for (k, v) in hm.iter() { - new_hm.insert(k.to_string(), eval(v, env)?); + Hash(hm, _) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k, v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v, env)?); + } + return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); } - return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); - } - List(l, _) => { + List(l, _) => { if l.is_empty() { return Ok(ast.clone()); } @@ -138,24 +152,16 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { let (a1, a2) = (&l[1], &l[2]); let r = eval(a2, env)?; match r { - MalFunc { - eval, - ast, - env, - params, - .. - } => return env_set( - &env, - a1, - MalFunc { - eval, - ast, - env: env.clone(), - params, - is_macro: true, - meta: Rc::new(Nil), - }, - ), + MalFunc(f) => { + return env_set( + env, + a1, + MalFunc(FuncStruct { + is_macro: true, + ..f.clone() + }), + ) + } _ => return error("set_macro on non-function"), } } @@ -166,12 +172,7 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { continue 'tco; } match eval(&l[1], env) { - Err(e) => { - let exc = match e { - ErrMalVal(mv) => mv.clone(), - ErrString(s) => Str(s.to_string()), - }; - match &l[2] { + Err(exc) => match &l[2] { List(c, _) => { live_env = env_new(Some(env.clone())); env = &live_env; @@ -181,11 +182,10 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { continue 'tco; } _ => return error("invalid catch block"), - } + }, + res => return res, } - res => return res, - } - }, + } Sym(a0sym) if a0sym == "do" => { for i in 1..l.len() - 1 { let _ = eval(&l[i], env)?; @@ -213,64 +213,51 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { } Sym(a0sym) if a0sym == "fn*" => { let (a1, a2) = (l[1].clone(), l[2].clone()); - return Ok(MalFunc { - eval, + return Ok(MalFunc(FuncStruct { ast: Rc::new(a2), env: env.clone(), params: Rc::new(a1), is_macro: false, meta: Rc::new(Nil), - }) + })); } - Sym(a0sym) if a0sym == "eval" => { - // Hard to implement without global variables. - // Normal argument evaluation. - live_ast = eval(&l[1], env)?; - ast = &live_ast; - live_env = env_find_repl(env); - env = &live_env; - continue 'tco; - } - _ => match eval(a0, env) { - Ok(f @ MalFunc { is_macro: true, .. }) => { - let new_ast = f.apply(l[1..].to_vec())?; - live_ast = new_ast; - ast = &live_ast; - continue 'tco; - } - Ok(f @ Func(_, _)) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - return f.apply(args); - } - Ok(MalFunc { - ast: mast, - env: menv, - params: mparams, - .. - }) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - live_env = env_bind(Some(menv.clone()), &mparams, args.to_vec())?; - env = &live_env; - live_ast = (*mast).clone(); - ast = &live_ast; - continue 'tco; - } - Ok(_) => return error("attempt to call non-function"), - e @ Err(_) => return e, + _ => match eval(a0, env)? { + f @ MalFunc(FuncStruct { is_macro: true, .. }) => { + let new_ast = f.apply(l[1..].to_vec())?; + live_ast = new_ast; + ast = &live_ast; + continue 'tco; + } + f @ Func(_, _) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + return f.apply(args); + } + MalFunc(FuncStruct { + ast: mast, + env: menv, + params: mparams, + .. + }) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + live_env = env_bind(menv.clone(), &mparams, args)?; + env = &live_env; + live_ast = (*mast).clone(); + ast = &live_ast; + continue 'tco; + } + _ => return error("attempt to call non-function"), }, } - } - _ => return Ok(ast.clone()), + } + _ => return Ok(ast.clone()), }; - } // end 'tco loop - } // print @@ -278,7 +265,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -293,58 +280,48 @@ fn re(str: &str, env: &Env) { panic!("error during startup"); } +thread_local! { + static REPL_ENV: Env = env_new(None); +} + fn main() { + REPL_ENV.with(|repl_env| { let mut args = std::env::args(); let arg1 = args.nth(1); // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } // core.rs: defined using rust - let repl_env = env_new(None); + env_sets(repl_env, "eval", types::func(|a| REPL_ENV.with(|e| eval(&a[0], e)))); for (k, v) in core::ns() { - env_sets(&repl_env, k, v); + env_sets(repl_env, k, v); } - env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + env_sets(repl_env, "*ARGV*", list(args.map(Str).collect())); // core.mal: defined using the language itself - re("(def! not (fn* (a) (if a false true)))", &repl_env); + re("(def! not (fn* (a) (if a false true)))", repl_env); re( "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", - &repl_env, + repl_env, ); re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", - &repl_env); + repl_env); if let Some(f) = arg1 { // Invoked with arguments - re(&format!("(load-file \"{}\")", f), &repl_env); + re(&format!("(load-file \"{}\")", f), repl_env); std::process::exit(0); } // main repl loop - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); + }) } diff --git a/impls/rust/stepA_mal.rs b/impls/rust/stepA_mal.rs index ce52da7432..f43af0d027 100644 --- a/impls/rust/stepA_mal.rs +++ b/impls/rust/stepA_mal.rs @@ -5,28 +5,40 @@ use std::rc::Rc; use fnv::FnvHashMap; use itertools::Itertools; -#[macro_use] -extern crate lazy_static; extern crate fnv; extern crate itertools; extern crate regex; -extern crate rustyline; -use rustyline::error::ReadlineError; -use rustyline::Editor; - +mod readline; #[macro_use] mod types; -use crate::types::MalErr::{ErrMalVal, ErrString}; use crate::types::MalVal::{Bool, Func, Hash, List, MalFunc, Nil, Str, Sym, Vector}; -use crate::types::{error, format_error, MalArgs, MalErr, MalRet, MalVal}; +use crate::types::{error, list, vector, FuncStruct, MalArgs, MalRet, MalVal}; mod env; mod printer; mod reader; -use crate::env::{env_bind, env_find_repl, env_get, env_new, env_set, env_sets, Env}; +use crate::env::{env_bind, env_get, env_new, env_set, env_sets, Env}; #[macro_use] mod core; +impl MalVal { + pub fn apply(&self, args: MalArgs) -> MalRet { + match self { + Func(f, _) => f(args), + MalFunc(FuncStruct { + ref ast, + ref env, + ref params, + .. + }) => { + let fn_env = &env_bind(env.clone(), params, args)?; + eval(ast, fn_env) + } + _ => error("attempt to call non-function"), + } + } +} + // read fn read(str: &str) -> MalRet { reader::read_str(str) @@ -35,19 +47,19 @@ fn read(str: &str) -> MalRet { // eval fn qq_iter(elts: &MalArgs) -> MalVal { - let mut acc = list![]; + let mut acc = list!(); for elt in elts.iter().rev() { if let List(v, _) = elt { if v.len() == 2 { if let Sym(ref s) = v[0] { if s == "splice-unquote" { - acc = list![Sym("concat".to_string()), v[1].clone(), acc]; + acc = list!(Sym("concat".to_string()), v[1].clone(), acc); continue; } } } } - acc = list![Sym("cons".to_string()), quasiquote(elt), acc]; + acc = list!(Sym("cons".to_string()), quasiquote(elt), acc); } acc } @@ -63,9 +75,9 @@ fn quasiquote(ast: &MalVal) -> MalVal { } } qq_iter(v) - }, - Vector(v, _) => list![Sym("vec".to_string()), qq_iter(v)], - Hash(_, _) | Sym(_)=> list![Sym("quote".to_string()), ast.clone()], + } + Vector(v, _) => list!(Sym("vec".to_string()), qq_iter(v)), + Hash(_, _) | Sym(_) => list!(Sym("quote".to_string()), ast.clone()), _ => ast.clone(), } } @@ -84,25 +96,25 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { _ => println!("EVAL: {}", print(ast)), } match ast { - Sym(s) => match env_get(env, s) { - Some(r) => return Ok(r), - None => return error(&format!("'{}' not found", s)), - } - Vector(v, _) => { - let mut lst: MalArgs = vec![]; - for a in v.iter() { - lst.push(eval(a, env)?); + Sym(s) => match env_get(env, s) { + Some(r) => return Ok(r), + None => return error(&format!("'{}' not found", s)), + }, + Vector(v, _) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { + lst.push(eval(a, env)?); + } + return Ok(vector(lst)); } - return Ok(vector!(lst)); - } - Hash(hm, _) => { - let mut new_hm: FnvHashMap = FnvHashMap::default(); - for (k, v) in hm.iter() { - new_hm.insert(k.to_string(), eval(v, env)?); + Hash(hm, _) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k, v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v, env)?); + } + return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); } - return Ok(Hash(Rc::new(new_hm), Rc::new(Nil))); - } - List(l, _) => { + List(l, _) => { if l.is_empty() { return Ok(ast.clone()); } @@ -140,24 +152,16 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { let (a1, a2) = (&l[1], &l[2]); let r = eval(a2, env)?; match r { - MalFunc { - eval, - ast, - env, - params, - .. - } => return env_set( - &env, - a1, - MalFunc { - eval, - ast, - env: env.clone(), - params, - is_macro: true, - meta: Rc::new(Nil), - }, - ), + MalFunc(f) => { + return env_set( + env, + a1, + MalFunc(FuncStruct { + is_macro: true, + ..f.clone() + }), + ) + } _ => return error("set_macro on non-function"), } } @@ -168,12 +172,7 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { continue 'tco; } match eval(&l[1], env) { - Err(e) => { - let exc = match e { - ErrMalVal(mv) => mv.clone(), - ErrString(s) => Str(s.to_string()), - }; - match &l[2] { + Err(exc) => match &l[2] { List(c, _) => { live_env = env_new(Some(env.clone())); env = &live_env; @@ -183,11 +182,10 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { continue 'tco; } _ => return error("invalid catch block"), - } - } - res => return res, + }, + res => return res, } - }, + } Sym(a0sym) if a0sym == "do" => { for i in 1..l.len() - 1 { let _ = eval(&l[i], env)?; @@ -215,64 +213,51 @@ fn eval(orig_ast: &MalVal, orig_env: &Env) -> MalRet { } Sym(a0sym) if a0sym == "fn*" => { let (a1, a2) = (l[1].clone(), l[2].clone()); - return Ok(MalFunc { - eval, + return Ok(MalFunc(FuncStruct { ast: Rc::new(a2), env: env.clone(), params: Rc::new(a1), is_macro: false, meta: Rc::new(Nil), - }) - } - Sym(a0sym) if a0sym == "eval" => { - // Hard to implement without global variables. - // Normal argument evaluation. - live_ast = eval(&l[1], env)?; - ast = &live_ast; - live_env = env_find_repl(env); - env = &live_env; - continue 'tco; + })); } - _ => match eval(a0, env) { - Ok(f @ MalFunc { is_macro: true, .. }) => { - let new_ast = f.apply(l[1..].to_vec())?; - live_ast = new_ast; - ast = &live_ast; - continue 'tco; - } - Ok(f @ Func(_, _)) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - return f.apply(args); - } - Ok(MalFunc { - ast: mast, - env: menv, - params: mparams, - .. - }) => { - let mut args: MalArgs = vec![]; - for i in 1..l.len() { - args.push(eval(&l[i], env)?); - } - live_env = env_bind(Some(menv.clone()), &mparams, args.to_vec())?; - env = &live_env; - live_ast = (*mast).clone(); - ast = &live_ast; - continue 'tco; - } - Ok(_) => return error("attempt to call non-function"), - e @ Err(_) => return e, + _ => match eval(a0, env)? { + f @ MalFunc(FuncStruct { is_macro: true, .. }) => { + let new_ast = f.apply(l[1..].to_vec())?; + live_ast = new_ast; + ast = &live_ast; + continue 'tco; + } + f @ Func(_, _) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + return f.apply(args); + } + MalFunc(FuncStruct { + ast: mast, + env: menv, + params: mparams, + .. + }) => { + let mut args: MalArgs = vec![]; + for i in 1..l.len() { + args.push(eval(&l[i], env)?); + } + live_env = env_bind(menv.clone(), &mparams, args)?; + env = &live_env; + live_ast = (*mast).clone(); + ast = &live_ast; + continue 'tco; + } + _ => return error("attempt to call non-function"), }, } - } - _ => return Ok(ast.clone()), + } + _ => return Ok(ast.clone()), }; - } // end 'tco loop - } // print @@ -280,7 +265,7 @@ fn print(ast: &MalVal) -> String { ast.pr_str(true) } -fn rep(str: &str, env: &Env) -> Result { +fn rep(str: &str, env: &Env) -> Result { let ast = read(str)?; let exp = eval(&ast, env)?; Ok(print(&exp)) @@ -295,60 +280,50 @@ fn re(str: &str, env: &Env) { panic!("error during startup"); } +thread_local! { + static REPL_ENV: Env = env_new(None); +} + fn main() { + REPL_ENV.with(|repl_env| { let mut args = std::env::args(); let arg1 = args.nth(1); // `()` can be used when no completer is required - let mut rl = Editor::<(), rustyline::history::DefaultHistory>::new().unwrap(); - if rl.load_history(".mal-history").is_err() { - eprintln!("No previous history."); - } // core.rs: defined using rust - let repl_env = env_new(None); + env_sets(repl_env, "eval", types::func(|a| REPL_ENV.with(|e| eval(&a[0], e)))); for (k, v) in core::ns() { - env_sets(&repl_env, k, v); + env_sets(repl_env, k, v); } - env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + env_sets(repl_env, "*ARGV*", list(args.map(Str).collect())); // core.mal: defined using the language itself - re("(def! *host-language* \"rust\")", &repl_env); - re("(def! not (fn* (a) (if a false true)))", &repl_env); + re("(def! *host-language* \"rust\")", repl_env); + re("(def! not (fn* (a) (if a false true)))", repl_env); re( "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", - &repl_env, + repl_env, ); re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", - &repl_env); + repl_env); if let Some(f) = arg1 { // Invoked with arguments - re(&format!("(load-file \"{}\")", f), &repl_env); + re(&format!("(load-file \"{}\")", f), repl_env); std::process::exit(0); } // main repl loop - re("(println (str \"Mal [\" *host-language* \"]\"))", &repl_env); - loop { - let readline = rl.readline("user> "); - match readline { - Ok(line) => { - let _ = rl.add_history_entry(&line); - rl.save_history(".mal-history").unwrap(); - if !line.is_empty() { - match rep(&line, &repl_env) { - Ok(out) => println!("{}", out), - Err(e) => println!("Error: {}", format_error(e)), - } - } - } - Err(ReadlineError::Interrupted) => continue, - Err(ReadlineError::Eof) => break, - Err(err) => { - println!("Error: {:?}", err); - break; + re("(println (str \"Mal [\" *host-language* \"]\"))", repl_env); + while let Some(ref line) = readline::readline("user> ") { + if !line.is_empty() { + match rep(line, repl_env) { + Ok(ref out) => println!("{}", out), + Err(ref e) => println!("Error: {}", e.pr_str(true)), } } } + println!(); + }) } diff --git a/impls/rust/types.rs b/impls/rust/types.rs index b68c2280ae..875de1d991 100644 --- a/impls/rust/types.rs +++ b/impls/rust/types.rs @@ -4,9 +4,12 @@ use std::rc::Rc; use fnv::FnvHashMap; use itertools::Itertools; -use crate::env::{env_bind, Env}; -use crate::types::MalErr::{ErrMalVal, ErrString}; -use crate::types::MalVal::{Atom, Bool, Func, Hash, Int, List, MalFunc, Nil, Str, Sym, Vector}; +use crate::env::Env; +use crate::types::MalVal::{Bool, Func, Hash, Int, Kwd, List, MalFunc, Nil, Str, Sym, Vector}; + +// Function closures and atoms may create cyclic dependencies, so +// reference counting should be replaced at least for these two kinds +// of references. #[derive(Clone)] pub enum MalVal { @@ -16,166 +19,48 @@ pub enum MalVal { //Float(f64), Str(String), Sym(String), + Kwd(String), List(Rc>, Rc), Vector(Rc>, Rc), Hash(Rc>, Rc), Func(fn(MalArgs) -> MalRet, Rc), - MalFunc { - eval: fn(ast: &MalVal, env: &Env) -> MalRet, - ast: Rc, - env: Env, - params: Rc, - is_macro: bool, - meta: Rc, - }, + MalFunc(FuncStruct), Atom(Rc>), } -pub enum MalErr { - ErrString(String), - ErrMalVal(MalVal), +#[derive(Clone)] +pub struct FuncStruct { + pub ast: Rc, + pub env: Env, + pub params: Rc, + pub is_macro: bool, + pub meta: Rc, } pub type MalArgs = Vec; -pub type MalRet = Result; +pub type MalRet = Result; // type utility macros macro_rules! list { - ($seq:expr) => {{ - List(Rc::new($seq),Rc::new(Nil)) - }}; [$($args:expr),*] => {{ let v: Vec = vec![$($args),*]; List(Rc::new(v),Rc::new(Nil)) }} } -macro_rules! vector { - ($seq:expr) => {{ - Vector(Rc::new($seq),Rc::new(Nil)) - }}; - [$($args:expr),*] => {{ - let v: Vec = vec![$($args),*]; - Vector(Rc::new(v),Rc::new(Nil)) - }} -} - // type utility functions -pub fn error(s: &str) -> MalRet { - Err(ErrString(s.to_string())) +pub fn error(s: &str) -> Result { + Err(Str(s.to_string())) } -pub fn format_error(e: MalErr) -> String { - match e { - ErrString(s) => s, - ErrMalVal(mv) => mv.pr_str(true), - } +pub fn list(seq: MalArgs) -> MalVal { + List(Rc::new(seq), Rc::new(Nil)) } -pub fn atom(mv: &MalVal) -> MalVal { - Atom(Rc::new(RefCell::new(mv.clone()))) -} - -impl MalVal { - pub fn keyword(&self) -> MalRet { - match self { - Str(s) if s.starts_with('\u{29e}') => Ok(Str(s.to_string())), - Str(s) => Ok(Str(format!("\u{29e}{}", s))), - _ => error("invalid type for keyword"), - } - } - - pub fn empty_q(&self) -> MalRet { - match self { - List(l, _) | Vector(l, _) => Ok(Bool(l.len() == 0)), - Nil => Ok(Bool(true)), - _ => error("invalid type for empty?"), - } - } - - pub fn count(&self) -> MalRet { - match self { - List(l, _) | Vector(l, _) => Ok(Int(l.len() as i64)), - Nil => Ok(Int(0)), - _ => error("invalid type for count"), - } - } - - pub fn apply(&self, args: MalArgs) -> MalRet { - match self { - Func(f, _) => f(args), - MalFunc { - eval, - ref ast, - env, - ref params, - .. - } => { - let fn_env = &env_bind(Some(env.clone()), params, args)?; - eval(ast, fn_env) - } - _ => error("attempt to call non-function"), - } - } - - pub fn keyword_q(&self) -> bool { - matches!(self, Str(s) if s.starts_with('\u{29e}')) - } - - pub fn deref(&self) -> MalRet { - match self { - Atom(a) => Ok(a.borrow().clone()), - _ => error("attempt to deref a non-Atom"), - } - } - - pub fn reset_bang(&self, new: &MalVal) -> MalRet { - match self { - Atom(a) => { - *a.borrow_mut() = new.clone(); - Ok(new.clone()) - } - _ => error("attempt to reset! a non-Atom"), - } - } - - pub fn swap_bang(&self, args: &MalArgs) -> MalRet { - match self { - Atom(a) => { - let f = &args[0]; - let mut fargs = args[1..].to_vec(); - fargs.insert(0, a.borrow().clone()); - *a.borrow_mut() = f.apply(fargs)?; - Ok(a.borrow().clone()) - } - _ => error("attempt to swap! a non-Atom"), - } - } - - pub fn get_meta(&self) -> MalRet { - match self { - List(_, meta) | Vector(_, meta) | Hash(_, meta) => Ok((**meta).clone()), - Func(_, meta) => Ok((**meta).clone()), - MalFunc { meta, .. } => Ok((**meta).clone()), - _ => error("meta not supported by type"), - } - } - - pub fn with_meta(&mut self, new_meta: &MalVal) -> MalRet { - match self { - List(_, ref mut meta) - | Vector(_, ref mut meta) - | Hash(_, ref mut meta) - | Func(_, ref mut meta) - | MalFunc { ref mut meta, .. } => { - *meta = Rc::new(new_meta.clone()); - } - _ => return error("with-meta not supported by type"), - }; - Ok(self.clone()) - } +pub fn vector(seq: MalArgs) -> MalVal { + Vector(Rc::new(seq), Rc::new(Nil)) } impl PartialEq for MalVal { @@ -186,6 +71,7 @@ impl PartialEq for MalVal { (Int(ref a), Int(ref b)) => a == b, (Str(ref a), Str(ref b)) => a == b, (Sym(ref a), Sym(ref b)) => a == b, + (Kwd(ref a), Kwd(ref b)) => a == b, (List(ref a, _), List(ref b, _)) | (Vector(ref a, _), Vector(ref b, _)) | (List(ref a, _), Vector(ref b, _)) @@ -206,26 +92,24 @@ pub fn _assoc(mut hm: FnvHashMap, kvs: MalArgs) -> MalRet { return error("odd number of elements"); } for (k, v) in kvs.iter().tuples() { - match k { - Str(s) => { - hm.insert(s.to_string(), v.clone()); - } - _ => return error("key is not string"), - } + hm.insert(wrap_map_key(k)?, v.clone()); } Ok(Hash(Rc::new(hm), Rc::new(Nil))) } -pub fn _dissoc(mut hm: FnvHashMap, ks: MalArgs) -> MalRet { - for k in ks { - match k { - Str(ref s) => { - let _ = hm.remove(s); - } - _ => return error("key is not string"), - } +pub fn wrap_map_key(k: &MalVal) -> Result { + match k { + Str(s) => Ok(String::from(s)), + Kwd(s) => Ok(format!("\u{29e}{}", s)), + _ => error("key is not string"), + } +} + +pub fn unwrap_map_key(s: &str) -> MalVal { + match s.strip_prefix('\u{29e}') { + Some(keyword) => Kwd(String::from(keyword)), + _ => Str(String::from(s)), } - Ok(Hash(Rc::new(hm), Rc::new(Nil))) } pub fn hash_map(kvs: MalArgs) -> MalRet {