Compare commits

...

12 Commits

109 changed files with 15313 additions and 1069 deletions
+50 -214
View File
@@ -148,9 +148,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.11"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
@@ -410,9 +410,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.41"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
@@ -429,9 +429,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.41"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstyle",
"clap_lex",
@@ -439,9 +439,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.41"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
@@ -451,9 +451,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.5"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "cobs"
@@ -461,7 +461,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
@@ -644,31 +644,6 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.11.0",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crypt"
version = "0.1.0"
@@ -1057,15 +1032,6 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "error-chain"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
dependencies = [
"version_check",
]
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -1349,17 +1315,6 @@ dependencies = [
"digest",
]
[[package]]
name = "hostname"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [
"libc",
"match_cfg",
"winapi",
]
[[package]]
name = "http"
version = "1.3.1"
@@ -1656,7 +1611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
dependencies = [
"hashbrown 0.16.1",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
@@ -1803,10 +1758,14 @@ dependencies = [
]
[[package]]
name = "match_cfg"
name = "lysp"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
dependencies = [
"clap",
"cross",
"nom 8.0.0",
"thiserror 1.0.69",
]
[[package]]
name = "md2txt"
@@ -1849,18 +1808,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
[[package]]
name = "ndk"
version = "0.9.0"
@@ -1922,6 +1869,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "ntpc"
version = "0.1.0"
@@ -2010,15 +1966,6 @@ dependencies = [
"syn",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "objc-sys"
version = "0.3.5"
@@ -2291,36 +2238,13 @@ dependencies = [
"sha2",
]
[[package]]
name = "parking_lot"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.13",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "pci-ids"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d88ae3281b415d856e9c2ddbcdd5961e71c1a3e90138512c04d720241853a6af"
dependencies = [
"nom",
"nom 7.1.3",
"phf",
"phf_codegen",
"proc-macro2",
@@ -2581,9 +2505,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.95"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -2610,9 +2534,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.40"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
@@ -2682,7 +2606,7 @@ dependencies = [
"kasuari",
"lru",
"strum",
"thiserror 2.0.12",
"thiserror 2.0.18",
"unicode-segmentation",
"unicode-truncate",
"unicode-width 0.2.2",
@@ -2745,10 +2669,11 @@ name = "red"
version = "0.1.0"
dependencies = [
"cross",
"crossterm",
"libc",
"libterm",
"syslog",
"log",
"logsink",
"lysp",
"regex",
"thiserror 1.0.69",
"unicode-width 0.1.14",
]
@@ -2773,9 +2698,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.1"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
@@ -2785,9 +2710,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
@@ -3088,7 +3013,7 @@ dependencies = [
"libc",
"log",
"logsink",
"nom",
"nom 7.1.3",
"runtime",
"stuff",
"thiserror 1.0.69",
@@ -3100,36 +3025,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "2.2.0"
@@ -3326,9 +3221,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -3346,19 +3241,6 @@ dependencies = [
"syn",
]
[[package]]
name = "syslog"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc7e95b5b795122fafe6519e27629b5ab4232c73ebb2428f568e82b1a457ad3"
dependencies = [
"error-chain",
"hostname",
"libc",
"log",
"time",
]
[[package]]
name = "sysutils"
version = "0.1.0"
@@ -3426,11 +3308,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.18",
]
[[package]]
@@ -3446,9 +3328,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
@@ -3462,14 +3344,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
@@ -3478,16 +3355,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tiny-skia"
version = "0.11.4"
@@ -3942,22 +3809,6 @@ dependencies = [
"rustls-pki-types",
]
[[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-util"
version = "0.1.9"
@@ -3967,12 +3818,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[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.45.0"
@@ -3982,15 +3827,6 @@ dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
+3
View File
@@ -12,6 +12,7 @@ members = [
"lib/libpsf",
"lib/libterm",
"lib/logsink",
"lib/lysp",
"lib/pixie",
"lib/runtime",
"lib/stuff",
@@ -47,6 +48,7 @@ ratatui = { version = "0.30.0", features = ["all-widgets", "macros"], default-fe
toml = "0.8.20"
url = "2.5.0"
http = "1.1.0"
nom = "8.0.0"
# Cryptography
rustls = { version = "0.23.29", default-features = false, features = ["std", "logging", "tls12", "custom-provider"] }
@@ -89,6 +91,7 @@ cryptic.path = "lib/cryptic"
hclient.path = "lib/hclient"
pixie.path = "lib/pixie"
stuff.path = "lib/stuff"
lysp.path = "lib/lysp"
[workspace.lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }
+1 -1
View File
@@ -45,7 +45,7 @@ extern "C" fn sigint_proxy(_: c_int) {
pub fn set_sigint_handler(handler: fn()) {
*SIGINT_HANDLER.lock().unwrap() = handler;
unsafe { libc::signal(libc::SIGINT, sigint_proxy as usize) };
unsafe { libc::signal(libc::SIGINT, (sigint_proxy as *const ()).addr()) };
}
pub fn send_kill(pid: u32) -> io::Result<()> {
+17 -6
View File
@@ -9,14 +9,18 @@ pub struct RawMode {
}
impl RawMode {
/// # Safety
///
/// Modifies terminal state, if not correctly paired with a [RawMode::leave()] call,
/// will ruin the terminal's behavior and appearance.
pub unsafe fn enter<F: AsRawFd>(stdin: &F) -> Result<Self, io::Error> {
let mut old = MaybeUninit::uninit();
if libc::tcgetattr(stdin.as_raw_fd(), old.as_mut_ptr()) != 0 {
if unsafe { libc::tcgetattr(stdin.as_raw_fd(), old.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let old = old.assume_init();
let old = unsafe { old.assume_init() };
let mut new = old;
new.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ISIG | libc::ICANON | libc::IEXTEN);
// new.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
@@ -32,23 +36,30 @@ impl RawMode {
new.c_cflag &= !(libc::PARENB | libc::CSIZE);
new.c_cflag |= libc::CS8;
if libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &new) != 0 {
if unsafe { libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &new) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(Self { saved_termios: old })
}
/// # Safety
///
/// Modifies terminal state, if called multiple times or after modifying the terminal state
/// will ruin the terminal's behavior and appearance.
pub unsafe fn leave<F: AsRawFd>(&self, stdin: &F) {
libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &self.saved_termios);
unsafe { libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &self.saved_termios) };
}
}
/// # Safety
///
/// Does not check whether stdout is a terminal, so marked as semantically unsafe
pub unsafe fn terminal_size(stdout: &Stdout) -> io::Result<(usize, usize)> {
let mut size: MaybeUninit<libc::winsize> = MaybeUninit::uninit();
if libc::ioctl(stdout.as_raw_fd(), libc::TIOCGWINSZ, size.as_mut_ptr()) != 0 {
if unsafe { libc::ioctl(stdout.as_raw_fd(), libc::TIOCGWINSZ, size.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let size = size.assume_init();
let size = unsafe { size.assume_init() };
Ok((size.ws_col as _, size.ws_row as _))
}
+1
View File
@@ -0,0 +1 @@
/target
+300
View File
@@ -0,0 +1,300 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [
"libc",
]
[[package]]
name = "lysp"
version = "0.1.0"
dependencies = [
"clap",
"dirs",
"nom",
"thiserror",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "lysp"
version = "0.1.0"
edition = "2024"
[dependencies]
thiserror.workspace = true
clap.workspace = true
nom.workspace = true
cross.workspace = true
[lints]
workspace = true
@@ -0,0 +1,94 @@
(defun err? (v) (= 'err (car v)))
(defun no-arguments () 1234)
(defun only-required (a b c) (+ a b c))
(defun only-optional (&optional a b c) (list a b c))
(defun only-rest (&rest r) (length r))
(defun required-and-optional (a b &optional c d) (list a b c d))
(defun required-and-rest (a b &rest r) (list a b r))
(defun optional-and-rest (&optional a b &rest r) (list a b r))
(defun required-and-optional-and-rest (a b &optional c d &rest r) (list a b c d r))
; no-arguments
(assert (= '(ok 1234) (eval '(no-arguments))))
(assert (err? (eval '(no-arguments 1))))
; only-required
(assert (= '(ok 6) (eval '(only-required 1 2 3))))
(assert (err? (eval '(only-required 1 2))))
; only-optional
(assert
(=
'(ok (nil nil nil))
(eval '(only-optional))
)
)
(assert
(=
'(ok (1 nil nil))
(eval '(only-optional 1))
)
)
(assert
(=
'(ok (1 2 nil))
(eval '(only-optional 1 2))
)
)
(assert
(=
'(ok (1 2 3))
(eval '(only-optional 1 2 3))
)
)
(assert (err? (eval '(only-optional 1 2 3 4))))
; only-rest
(assert (= 0 (only-rest)))
(assert (= 1 (only-rest 1)))
(assert (= 2 (only-rest 1 2)))
(assert (= 3 (only-rest 1 2 3)))
; required-and-optional
(assert (err? (eval '(required-and-optional))))
(assert (err? (eval '(required-and-optional 1))))
(assert
(=
'(1 2 nil nil)
(required-and-optional 1 2)
)
)
(assert
(=
'(1 2 3 nil)
(required-and-optional 1 2 3)
)
)
(assert
(=
'(1 2 3 4)
(required-and-optional 1 2 3 4)
)
)
(assert (err? (eval '(required-and-optional 1 2 3 4 5))))
; required-and-rest
(assert (err? (eval '(required-and-rest))))
(assert (err? (eval '(required-and-rest 1))))
(assert (= '(1 2 nil) (required-and-rest 1 2)))
(assert (= '(1 2 (3)) (required-and-rest 1 2 3)))
(assert (= '(1 2 (3 4 5)) (required-and-rest 1 2 3 4 5)))
; optional-and-rest
(assert (= '(nil nil nil) (optional-and-rest)))
(assert (= '(1 nil nil) (optional-and-rest 1)))
(assert (= '(1 2 nil) (optional-and-rest 1 2)))
(assert (= '(1 2 (3)) (optional-and-rest 1 2 3)))
(assert (= '(1 2 (3 4 5)) (optional-and-rest 1 2 3 4 5)))
; required-and-optional-and-rest
(assert (err? (eval '(required-and-optional-and-rest))))
(assert (err? (eval '(required-and-optional-and-rest 1))))
(assert (= '(1 2 nil nil nil) (required-and-optional-and-rest 1 2)))
(assert (= '(1 2 3 nil nil) (required-and-optional-and-rest 1 2 3)))
(assert (= '(1 2 3 4 nil) (required-and-optional-and-rest 1 2 3 4)))
(assert (= '(1 2 3 4 (5)) (required-and-optional-and-rest 1 2 3 4 5)))
(assert (= '(1 2 3 4 (5 6 7 8)) (required-and-optional-and-rest 1 2 3 4 5 6 7 8)))
+11
View File
@@ -0,0 +1,11 @@
(assert (= (->string 1234) "1234"))
(assert (= (->string '(1 2)) "(1 2)"))
(assert (= (->string #t) "#T"))
(assert (= 1234 (string->number "1234")))
(assert (= -1234 (string->number "-1234")))
(assert (= 0x1234 (string->number "1234" 16)))
(assert (= 0o1234 (string->number "1234" 8)))
(assert (= (/ 1 2) (string->number "0.5")))
(assert (= (/ -1 2) (string->number "-0.5")))
(assert (= (/ -1 2) (string->number "-5e-1")))
+12
View File
@@ -0,0 +1,12 @@
(print "Argument count:" (length *args*))
(print "Arguments as a list:" *args*)
(print "Iterated:")
(let
(index 0 current *args*)
(while current
(print index ":" (car current))
(setq index (+ index 1))
(setq current (cdr current))
)
)
+16
View File
@@ -0,0 +1,16 @@
; empty env
(setq custom-env (env/create nil))
(setq
custom-env-expr
'(progn
(defun my-function () 1234)
)
)
(defun my-function () 4321)
(assert (= 'ok (car (eval custom-env custom-env-expr))))
(assert (= 4321 (my-function)))
(assert (= '(ok 1234) (eval custom-env '(my-function))))
(assert (= 'err (car (eval custom-env '(print 1234))))) ;; print is not defined in custom-env
(env/load-prelude custom-env)
(assert (= '(ok nil) (eval custom-env '(print 1234))))
@@ -0,0 +1,24 @@
(defun factorial (x)
(if (= x 0)
1
(* (factorial (- x 1)) x)
)
)
(defun loop-factorial (x)
(let (i 0 acc 1)
(while (< i x)
(setq i (+ i 1))
(setq acc (* acc i))
)
acc
)
)
;; TODO for loops?
(let (i 0)
(while (<= i 15)
(print i "\t" (factorial i) "\t" (loop-factorial i))
(setq i (+ i 1))
)
)
+52
View File
@@ -0,0 +1,52 @@
(setq h (hash/new
'(key . value)
'(1 . 2)
'("string" . 3)
'(3 . 4)))
(assert (= 'value (hash/get h 'key)))
(assert (= 2 (hash/get h 1)))
(assert (= 100 (hash/get h 0 100)))
(assert (= NIL (hash/get h 0)))
(hash/map! (lambda (_ v) v) h)
(assert (= 'value (hash/get h 'key)))
(assert (= 2 (hash/get h 1)))
(assert (= 100 (hash/get h 0 100)))
(assert (= NIL (hash/get h 0)))
(hash/remove! h 'key)
(assert (= NIL (hash/get h 'key)))
(hash/map! (lambda (_ v) (+ v 100)) h)
(assert (= 102 (hash/get h 1)))
(assert (= 103 (hash/get h "string")))
(assert (= 100 (hash/get h 0 100)))
(assert (= NIL (hash/get h 0)))
(setq hl (hash->list h))
(assert (= '(3 . 104) (find (lambda (a) (= (car a) 3)) hl)))
(assert (= '("string" . 103) (find (lambda (a) (= (car a) "string")) hl)))
(assert (= '(1 . 102) (find (lambda (a) (= (car a) 1)) hl)))
(setq sum-1 0)
(hash/for-each (lambda (_ v) (setq sum-1 (+ sum-1 v))) h)
(setq sum-2 (hash/fold 0 (lambda (a _ v) (+ a v)) h))
(assert (= sum-1 sum-2 309))
(hash/filter! (lambda (k _) (/= k "string")) h)
;; Hash equality
(assert (= h (hash/new '(1 . 102) '(3 . 104))))
(assert (=
(hash/new '(1 . 2) '(2 . 3) '("string" . "value"))
(list->hash '((2 . 3) ("string" . "value") (1 . 2)))
))
;; hash->list->hash idempotence
(setq h (hash/new))
(let (i 0)
(while (< i 10000)
(hash/put! h (+ "key" i) (+ "value" i))
(setq i (+ i 1))
))
(assert (= h (list->hash (hash->list h))))
+1
View File
@@ -0,0 +1 @@
(import "io.lysp")
+21
View File
@@ -0,0 +1,21 @@
(print "FS API:")
(runtime-debug (print (fs/exists? (car *args*))))
(runtime-debug (print (fs/file? (car *args*))))
(runtime-debug (print (fs/directory? (car *args*))))
(runtime-debug (print (fs/read-to-string (car *args*))))
(runtime-debug (print (fs/home-directory)))
(print "Stream API:")
(print (+ (car *args*) ":"))
(setq handle (stream/open (car *args*)))
(loop
(let (data (stream/read handle))
(if data
(stream/write stream/stdout data)
(break)
)
)
)
(stream/close handle)
+13
View File
@@ -0,0 +1,13 @@
(setq xs '(1 2 3 4 5))
(setq ys '((1 2) (3 4) (5 6)))
(assert (= (length xs) 5))
(assert (= '(5 4 3 2 1) (reverse xs)))
(assert (= 15 (apply + xs) (fold + 0 xs)))
(assert (= 120 (apply * xs) (fold * 1 xs)))
(assert (= (length ys) 3))
(assert (= '((5 6) (3 4) (1 2)) (reverse ys)))
(assert (= '((2 1) (4 3) (6 5)) (map reverse ys)))
(assert (= '(2 2 2) (map length ys)))
(assert (= '(1 2 3 4 5 6) (flatmap identity ys)))
+40
View File
@@ -0,0 +1,40 @@
;; loop, broken by (return)
(setq
looped-loop
(let (i 0)
(loop
(if (< i 10)
(setq i (+ i 1))
(break)
)
)
i
)
)
;; while, broken prematurely
(setq
looped-while
(let (i 0)
(while (< i 20)
(setq i (+ i 1))
(if (= i 10) (break))
)
i
)
)
;; while, broken by condition
(setq
looped-while-full
(let (i 0)
(while (< i 10)
(setq i (+ i 1))
)
i
)
)
;; All loops execute the same count of times
(assert (= looped-loop looped-while looped-while-full))
(print "Test succeeded")
+68
View File
@@ -0,0 +1,68 @@
; quoting rules
(setq glob0 123)
(setq glob1 '(2 3 4))
(assert (= (list 1 2 3) '(1 2 3) `(1 2 3) (quote (1 2 3))))
(assert (= '(1 glob0 3) `(1 glob0 3)))
(assert (= '(1 123 3) `(1 ,glob0 3)))
(assert (= '(1 glob1 5) `(1 glob1 5)))
(assert (= '(1 (2 3 4) 5) `(1 ,glob1 5)))
(assert (= '(1 2 3 4 5) `(1 ,@glob1 5)))
(assert (= '(2 3 4 5) `(,@glob1 5)))
(assert (= '(1 2 3 4) `(1 ,@glob1)))
(assert (= '(2 3 4) `(,@glob1)))
(assert (= '((2 3 4)) `(,glob1)))
; Nested
(assert (= '((123 123) (123 123)) `((,glob0 ,glob0) (,glob0 ,glob0))))
(assert (= '(2 3 4 2 3 4) `(,@glob1 ,@glob1)))
(assert (= '((((2 3 4)))) `(((,glob1)))))
; Read value of a constructed symbol
(defun read-indexed (prefix index) (get (symbol (+ (->string prefix) index))))
(assert (= 123 (read-indexed 'glob 0)))
(assert (= '(2 3 4) (read-indexed 'glob 1)))
(assert (= nil (read-indexed 'glob 2)))
; those are prelude, but defined in lysp itself:
(print "The previously printed expression is AFTER this one in the code")
(compile-debug
(when #t
(print "a")
(print "b")
)
)
(runtime-debug
(when #t
(print "a")
(print "b")
)
)
(when 1
(print "a")
(print "b")
)
(unless nil
(print "c")
(print "d")
)
; catch macro
(print "Failing in a catch block...")
(catch
(print a)
e (print "Caught an error:" e)
)
(print "... doesn't fail the execution")
(defmacro nested-0 (x) `(list 2 ,x))
(defmacro nested-1 (x) `(list 1 (nested-0 ,x)))
(assert (= '(1 (2 3)) (nested-1 3)))
;; (explain ...) must work on macros
(assert (explain and))
+72
View File
@@ -0,0 +1,72 @@
; add
(assert (= (+ 1 2 3) 6))
(assert (= (+) 0))
(assert (= (+ 1) 1))
; sub
(assert (= (- 1) -1))
(assert (= (- 1 2) -1))
; mul
(assert (= (*) 1))
(assert (= (* 2) 2))
(assert (= (* 2 3 4) 24))
; div
(assert (= (/ 1 0) #inf))
(assert (= (/ -1 0) -#inf))
(assert (= (/ 6 2) 3))
(assert (= (/ 12 3 2) 2))
(assert (= (/ 6 4) (/ 3 2)))
(assert (= (/ 4) (/ 25 100))) ; reciprocal
; rem
(assert (= (% 6 2) 0))
(assert (= (% 6 4) 2))
;;;; NaN/infinity handling
(assert (≠ #nan #nan))
(assert (= #inf #inf))
(assert (≠ -#inf #inf))
(assert (> #inf 0 -#inf))
;;;; Ordering
(assert (< 1 2 3))
(assert (not (< 1 1 2 3)))
(assert (<= 1 1 2 3 3))
(assert (> 3 2 1))
(assert (not (> 3 3 2 1)))
(assert (>= 3 3 2 1))
(assert (= 1 1 1 1))
(assert (not (= 1 1 2 1)))
(assert (≠ 1 2 3 4))
(assert (not (≠ 1 2 2 3)))
;;;; Logic
(assert (and #t #t 1))
(assert (not (and #t #f 1)))
(assert (not (or nil 0 #f)))
; (and ...) and (or ...) must short-circuit
; (and ...) and (or ...) must not double-evaluate
(setq no-double-eval-and 0)
(setq no-double-eval-or 0)
(and
#t
(progn
(setq no-double-eval-and (+ no-double-eval-and 1))
#t
)
#f
(error "(and ...) must short-circuit after #F"))
(or
#f
(progn
(setq no-double-eval-or (+ no-double-eval-or 1))
#t
)
#t
(error "(or ...) must short-circuit after #T"))
(assert (= no-double-eval-and 1))
(assert (= no-double-eval-or 1))
+34
View File
@@ -0,0 +1,34 @@
(defun cadr (x) (car (cdr x)))
(defun map-ok-err (f-ok f-err result)
(if (= (car result) 'ok)
`(ok ,(f-ok (cadr result)))
`(err ,(f-err (cadr result)))
)
)
(defun map-ok (f-ok result) (map-ok-err f-ok identity result))
(defun map-err (f-err result) (map-ok-err identity f-err result))
(defun repl-print (value)
(print "==>" value)
)
(defun repl-eval-print (expression)
(map-ok-err repl-print repl-eval-error (eval expression))
)
(defun repl-eval-error (error)
(print "Evaluation error:")
(print error)
)
(defun repl-read-error (error)
(print "Parse error:")
(print error)
)
(loop
(let (expression (read))
(if expression NIL (break))
(setq expression (unquote expression))
(map-ok-err repl-eval-print repl-read-error expression)
)
)
+46
View File
@@ -0,0 +1,46 @@
(defun replace-argument (x) (let (x 1) x))
(defun replace-let ()
(let (x 1)
(setq x 2) ;; mutate local x
x
)
)
(defun reassignment-in-a-loop (iterations)
(let (x 0 y 1)
(while (< x iterations)
(setq y (* y 2))
(setq x (+ x 1))
)
y
)
)
(assert (= (replace-argument 2) 1))
(assert (= (replace-argument 3) 1))
(assert (= (replace-let) 2))
(assert (= (reassignment-in-a-loop 4) 16))
;; capture upvalue
(assert
(=
123
(let
(loc0 123)
((lambda () loc0))
)
)
)
;; mutate upvalue
(assert
(=
321
(let
(loc0 123)
((lambda () (setq loc0 321)))
loc0
)
)
)
+56
View File
@@ -0,0 +1,56 @@
;; Conditional
(if #t 1 2)
(cond
[(= 1 2 3) #f]
[(= 1 1 1) #t]
[`otherwise 1234]
)
;; Loops
(while #f
1
2
3)
(loop
1
2
3
(break))
;; Functions
(defun a (x y z w) 1 2 3 x)
(lambda (x y z w) 1 2 3 x)
;; Macros
(defmacro my-quote (x) `(quote ,x))
(print (my-quote (a b c)))
;; vectors
#[1 2 3]
; alternate syntax for lists
[] () nil NIL
;; progn
(progn
1
2
3)
;; setq
(setq glob-value "my-global")
;; let
(let
(x 1 y 2)
x
)
(let* (x 1 y x) y)
;; Quoting
`(ok ,glob-value)
;; Evaluation rules
; (progn ...) must evaluate to the last expression
(assert (= (progn 1 2 3) 3))
+55
View File
@@ -0,0 +1,55 @@
;; open upvalue get
(setq res-1
(let (a 123)
((lambda (b) (+ a b)) 321)
))
;; shared open upvalue get
(setq res-2
(let (a 123)
(let
(
x ((lambda (b) (+ a b) (+ a b)) 321)
y ((lambda (b) (+ a b) (* a b)) 2)
)
(+ x y)
)
)
)
;; closed upvalue get
(setq func-0 (let (a 123) (lambda (b) (+ a b))))
;; open upvalue set
(setq res-3
(let (a 123)
((lambda (b) (setq a b)) 321)
a
)
)
;; shared open upvalue set
(setq res-4
(let (a 123)
(let (
funcs (list
(lambda () (setq a 1))
(lambda () (setq a (+ a 1)))
(lambda () (setq a (+ a 2)))
)
)
(while (not (nil? funcs))
((car funcs))
(setq funcs (cdr funcs))
)
a
)
)
)
;; closed upvalue set
(setq func-1 (let (a 123) (lambda (b) (setq a (+ a b)) a)))
(assert (= 444 res-1))
(assert (= 690 res-2))
(assert (= 444 (func-0 321)))
(assert (= 321 res-3))
(assert (= 4 res-4))
(assert (= 444 (func-1 321)))
+656
View File
@@ -0,0 +1,656 @@
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
path::Path,
rc::Rc,
};
use crate::{
compile::CompileOptions,
vm::{
instruction::BranchOffset,
value::{BytecodeFunction, StringValue},
},
};
use crate::{
compile::{
error::CompileError,
function::FunctionSignature,
instruction::Emitted,
syntax::{Expression, FunctionBody},
},
vm::{
Value,
instruction::{ConstantId, ImmediateInteger, Instruction, LocalId},
value::{BooleanValue, IdentifierValue, NumberValue},
},
};
#[derive(Clone)]
pub struct FunctionBlock {
parent: Option<usize>,
identifier: Option<IdentifierValue>,
docstring: Option<StringValue>,
signature: FunctionSignature,
script_path: Option<Rc<Path>>,
// Data
pub(crate) constants: Vec<Value>,
locals: Vec<Local>,
upvalues: Vec<UpvalueDef>,
scope_depth: usize,
// Code
pub(crate) instructions: Vec<Emitted>,
labels: Vec<usize>,
loop_stack: Vec<LoopStackEntry>,
}
#[derive(Clone)]
struct Local {
name: IdentifierValue,
depth: isize,
is_captured: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UpvalueDef {
pub(crate) index: LocalId,
pub(crate) is_local: bool,
}
#[derive(Clone)]
struct LoopStackEntry {
label_entry: usize,
label_exit: usize,
}
#[derive(Debug, PartialEq)]
pub enum CompileValue {
Nil,
Identifier(IdentifierValue),
Integer(NumberValue),
Boolean(BooleanValue),
String(StringValue),
LocalFunction(usize),
Quote(Rc<Value>),
Value(Value),
Stack,
}
#[derive(Clone)]
pub struct CompileContext {
pub function_blocks: Vec<FunctionBlock>,
pub(crate) current: usize,
pub(crate) options: CompileOptions,
script_path: Option<Rc<Path>>,
}
pub trait Compile {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError>;
}
impl CompileContext {
pub fn new(
options: CompileOptions,
root_name: Option<IdentifierValue>,
script_path: Option<Rc<Path>>,
) -> Self {
Self {
function_blocks: vec![FunctionBlock::root(root_name, script_path.clone())],
current: 0,
options,
script_path,
}
}
pub fn compile_value(
options: CompileOptions,
chunk_name: Option<IdentifierValue>,
value: &Value,
script_path: Option<Rc<Path>>,
) -> Result<Rc<BytecodeFunction>, CompileError> {
let mut cx = Self::new(options, chunk_name, script_path);
let expression = Expression::parse(value).map_err(CompileError::Parse)?;
let value = expression.compile(&mut cx)?;
cx.compile_return_value(value)?;
cx.to_bytecode()
}
pub fn compile_function(
&mut self,
identifier: Option<IdentifierValue>,
docstring: Option<StringValue>,
signature: &FunctionSignature,
body: &FunctionBody,
) -> Result<usize, CompileError> {
// TODO signature
let index = self.push_lambda_context(identifier, docstring, signature)?;
for expression in body.head.iter() {
self.compile_statement(expression)?;
}
let value = body.tail.compile(self)?;
self.compile_return_value(value)?;
self.pop_context();
Ok(index)
}
pub fn compile_return_value(&mut self, value: CompileValue) -> Result<(), CompileError> {
self.push(value)?;
self.emit(Instruction::Return);
Ok(())
}
pub fn push_lambda_context(
&mut self,
identifier: Option<IdentifierValue>,
docstring: Option<StringValue>,
signature: &FunctionSignature,
) -> Result<usize, CompileError> {
if self.options.trace_compile {
eprintln!("COMPILE: push_lambda_context({identifier:?})");
}
let block = FunctionBlock::new(
Some(self.current),
identifier,
docstring,
signature,
self.script_path.clone(),
);
let index = self.function_blocks.len();
self.function_blocks.push(block);
self.current = index;
Ok(index)
}
pub fn pop_context(&mut self) {
if self.options.trace_compile {
let current_name = self.identifier.clone();
eprintln!("COMPILE: pop_lambda_context({current_name:?})");
}
let index = self
.parent
.expect("cannot pop out of root function context");
self.current = index;
}
pub fn compile_statement(&mut self, expression: &Expression) -> Result<(), CompileError> {
let value = expression.compile(self)?;
self.discard(value);
Ok(())
}
pub fn compile_set_global(
&mut self,
identifier: IdentifierValue,
value: CompileValue,
) -> Result<(), CompileError> {
self.push(value)?;
self.get_constant(Value::Identifier(identifier))?;
self.emit(Instruction::SetGlobal);
Ok(())
}
pub fn compile_declare_macro(
&mut self,
identifier: IdentifierValue,
value: CompileValue,
) -> Result<(), CompileError> {
self.push(value)?;
self.get_constant(Value::Identifier(identifier))?;
self.emit(Instruction::DeclareMacro);
Ok(())
}
pub fn compile_assign(
&mut self,
identifier: IdentifierValue,
value: CompileValue,
) -> Result<(), CompileError> {
self.push(value)?;
if let Some(local) = self.resolve_local(identifier.as_ref())? {
self.emit(Instruction::SetLocal);
self.emit(local);
} else if let Some(upvalue) = self.resolve_upvalue(identifier.as_ref())? {
self.emit(Instruction::SetUpvalue);
self.emit(upvalue);
} else {
self.get_constant(Value::Identifier(identifier))?;
self.emit(Instruction::SetGlobal);
}
Ok(())
}
fn get_constant(&mut self, value: Value) -> Result<(), CompileError> {
let index = self.constant(value)?;
self.emit(Instruction::PushConstant);
self.emit(index);
Ok(())
}
pub fn push(&mut self, value: CompileValue) -> Result<(), CompileError> {
match value {
CompileValue::Nil => {
self.emit(Instruction::PushNil);
}
CompileValue::String(value) => {
self.get_constant(Value::String(value))?;
}
CompileValue::Quote(value) => {
self.get_constant(value.as_ref().clone())?;
}
CompileValue::Value(value) => {
self.get_constant(value)?;
}
CompileValue::Integer(value) => {
if let Ok(immediate) = ImmediateInteger::try_from(value) {
self.emit(Instruction::PushInteger);
self.emit(immediate);
} else {
self.get_constant(Value::Number(value))?;
}
}
CompileValue::Boolean(BooleanValue(value)) => match value {
true => self.emit(Instruction::PushTrue),
false => self.emit(Instruction::PushFalse),
},
CompileValue::Identifier(name) => {
if let Some(local) = self.resolve_local(name.as_ref())? {
self.emit(Instruction::GetLocal);
self.emit(local);
} else if let Some(upvalue) = self.resolve_upvalue(name.as_ref())? {
self.emit(Instruction::GetUpvalue);
self.emit(upvalue);
} else {
self.get_constant(Value::Identifier(name))?;
self.emit(Instruction::GetGlobal);
}
}
CompileValue::LocalFunction(index) => {
let function = self.function_blocks[index].to_bytecode()?;
self.get_constant(Value::Function(function.clone()))?;
if !function.upvalues.is_empty() {
self.emit(Instruction::MakeClosure);
}
}
CompileValue::Stack => (),
}
Ok(())
}
pub fn discard(&mut self, value: CompileValue) {
if matches!(value, CompileValue::Stack) {
self.emit(Instruction::Drop);
}
}
pub fn guard_argument(&mut self) {
let depth = self.scope_depth as isize;
self.locals.push(Local {
name: "".into(),
depth,
is_captured: false,
});
}
pub fn unguard_argument(&mut self) {
let guard = self
.locals
.pop()
.expect("unguard_argument() called with an empty locals stack");
let stack_index = self.locals.len();
if guard.name.as_ref() != "" {
panic!(
"unguard_argument(): the local is not a guard: {:?} at stack index {}",
guard.name, stack_index,
);
}
}
pub fn push_scope(&mut self) {
self.scope_depth += 1;
if self.options.trace_compile {
eprintln!("COMPILE: push_scope({})", self.scope_depth);
}
}
pub fn pop_scope(&mut self, out_value: Option<CompileValue>) -> Result<(), CompileError> {
if self.options.trace_compile {
eprintln!("COMPILE: pop_scope({})", self.scope_depth);
}
if self.scope_depth == 0 {
panic!("Cannot pop out of root scope");
}
self.scope_depth -= 1;
let Some(out_value) = out_value else { todo!() };
self.push(out_value)?;
self.emit(Instruction::SetTemp);
for i in (0..self.locals.len()).rev() {
if self.locals[i].depth <= self.scope_depth as isize {
break;
}
if self.locals[i].is_captured {
self.emit(Instruction::CloseUpvalue);
} else {
self.emit(Instruction::Drop);
}
self.locals.pop();
}
self.emit(Instruction::GetTemp);
Ok(())
}
fn resolve_upvalue_inner(
&mut self,
at: Option<usize>,
name: &str,
) -> Result<Option<LocalId>, CompileError> {
let Some(at) = at else {
return Ok(None);
};
let block = &mut self.function_blocks[at];
if let Some(local) = block.resolve_local(name)? {
block.locals[usize::from(local)].is_captured = true;
return self
.add_upvalue(UpvalueDef {
index: local,
is_local: true,
})
.map(Some);
}
let parent = block.parent;
if let Some(upvalue) = self.resolve_upvalue_inner(parent, name)? {
return self
.add_upvalue(UpvalueDef {
index: upvalue,
is_local: false,
})
.map(Some);
}
Ok(None)
}
fn resolve_upvalue(&mut self, name: &str) -> Result<Option<LocalId>, CompileError> {
self.resolve_upvalue_inner(self.parent, name)
}
}
impl Deref for CompileContext {
type Target = FunctionBlock;
fn deref(&self) -> &Self::Target {
&self.function_blocks[self.current]
}
}
impl DerefMut for CompileContext {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.function_blocks[self.current]
}
}
impl FunctionBlock {
fn new(
parent: Option<usize>,
identifier: Option<IdentifierValue>,
docstring: Option<StringValue>,
signature: &FunctionSignature,
script_path: Option<Rc<Path>>,
) -> Self {
let mut block = Self {
parent,
identifier,
docstring,
signature: signature.clone(),
constants: vec![],
locals: vec![],
upvalues: vec![],
scope_depth: 0,
instructions: vec![],
labels: vec![],
loop_stack: vec![],
script_path,
};
for required in signature.required_arguments.iter() {
block
.add_local(required.clone(), Some(-100))
.expect("couldn't add an argument");
}
for optional in signature.optional_arguments.iter() {
block
.add_local(optional.clone(), Some(-100))
.expect("couldn't add an argument");
}
if let Some(rest) = signature.rest_argument.as_ref() {
block
.add_local(rest.clone(), Some(-100))
.expect("couldn't add an argument");
}
block
}
fn root(identifier: Option<IdentifierValue>, script_path: Option<Rc<Path>>) -> Self {
Self::new(
None,
identifier,
None,
&FunctionSignature {
required_arguments: vec![],
optional_arguments: vec![],
rest_argument: None,
},
script_path,
)
}
pub fn enter_loop(&mut self, label_entry: usize, label_exit: usize) {
self.loop_stack.push(LoopStackEntry {
label_entry,
label_exit,
});
}
pub fn leave_loop(&mut self) {
let v = self.loop_stack.pop();
if v.is_none() {
panic!("Compiler error: leave_loop() called outside any enclosing loop");
}
}
pub fn emit_break(&mut self) -> Result<(), CompileError> {
let Some(entry) = self.loop_stack.last() else {
return Err(CompileError::BreakOutsideOfLoop);
};
self.emit(Emitted::Jump(entry.label_exit));
Ok(())
}
pub fn emit_continue(&mut self) -> Result<(), CompileError> {
let Some(entry) = self.loop_stack.last() else {
return Err(CompileError::ContinueOutsideOfLoop);
};
self.emit(Emitted::Jump(entry.label_entry));
Ok(())
}
pub fn to_bytecode(&self) -> Result<Rc<BytecodeFunction>, CompileError> {
if !self.loop_stack.is_empty() {
panic!("Compiler error: loop stack not empty on function completion");
}
let mut instructions = vec![];
let mut resolved_labels = HashMap::new();
let mut patch_branches = HashMap::new();
for &offset in self.labels.iter() {
resolved_labels.insert(offset, None);
}
// Emit all code, with placeholders for branch targets
for (offset, emitted) in self.instructions.iter().enumerate() {
let ip = instructions.len();
// Resolve real label address
if let Some(real) = resolved_labels.get_mut(&offset) {
assert!(real.is_none());
*real = Some(ip);
}
match emitted {
&Emitted::Jump(label) => {
instructions.push(Instruction::Jump.into());
patch_branches.insert(instructions.len(), label);
instructions.push(0xFF);
instructions.push(0xFF);
}
&Emitted::Branch(label) => {
instructions.push(Instruction::Branch.into());
patch_branches.insert(instructions.len(), label);
instructions.push(0xFF);
instructions.push(0xFF);
}
Emitted::LocalId(local) => {
instructions.extend_from_slice(&local.to_bytes());
}
Emitted::ConstantId(index) => {
instructions.extend_from_slice(&index.to_bytes());
}
Emitted::ArgumentCount(count) => {
instructions.extend_from_slice(&count.to_bytes());
}
Emitted::ImmediateInteger(value) => {
instructions.extend_from_slice(&value.to_bytes())
}
&Emitted::Bool(value) => instructions.push(value as u8),
&Emitted::Instruction(instruction) => instructions.push(u8::from(instruction)),
}
}
// Patch branch targets
for (position, label) in patch_branches {
let label_input = self.labels.get(label).copied().unwrap();
let branch_target = resolved_labels.get(&label_input).copied().unwrap().unwrap();
let branch_source = position + 2;
let branch_offset = branch_target
.checked_signed_diff(branch_source)
.and_then(|diff| BranchOffset::try_from(diff).ok())
.ok_or_else(|| CompileError::CannotEmitBranch(branch_source, branch_target))?;
instructions[position..position + 2].copy_from_slice(&branch_offset.to_bytes());
}
Ok(Rc::new(BytecodeFunction {
identifier: self.identifier.clone(),
instructions: instructions.into(),
docstring: self.docstring.clone(),
constants: self.constants.iter().cloned().collect(),
upvalues: self.upvalues.iter().copied().collect(),
required_count: self.signature.required_arguments.len(),
optional_count: self.signature.optional_arguments.len(),
has_rest: self.signature.rest_argument.is_some(),
script: self.script_path.clone(),
}))
}
pub fn new_label(&mut self) -> usize {
let index = self.labels.len();
self.labels.push(usize::MAX);
index
}
pub fn adjust_label(&mut self, label: usize) {
let address = self.instructions.len();
self.labels[label] = address;
}
pub fn emit<I: Into<Emitted>>(&mut self, insn: I) {
let emitted = insn.into();
self.instructions.push(emitted);
}
pub fn constant(&mut self, value: Value) -> Result<ConstantId, CompileError> {
if let Some(index) = self.constants.iter().position(|v| v == &value) {
return Ok(index.try_into().unwrap());
}
let index = ConstantId::try_from(self.constants.len())
.map_err(|_| CompileError::TooManyConstants)?;
self.constants.push(value);
Ok(index)
}
fn add_local(
&mut self,
name: IdentifierValue,
depth: Option<isize>,
) -> Result<LocalId, CompileError> {
let index =
LocalId::try_from(self.locals.len()).map_err(|_| CompileError::TooManyLocals)?;
self.locals.push(Local {
name,
is_captured: false,
depth: depth.unwrap_or(-1),
});
Ok(index)
}
fn add_upvalue(&mut self, upvalue: UpvalueDef) -> Result<LocalId, CompileError> {
if let Some(index) = self.upvalues.iter().position(|uv| upvalue == *uv) {
return Ok(index.try_into().unwrap());
}
// Add new
let index =
LocalId::try_from(self.upvalues.len()).map_err(|_| CompileError::TooManyLocals)?;
self.upvalues.push(upvalue);
Ok(index)
}
// Binding management
pub fn init_local(&mut self, index: LocalId) {
self.locals[usize::from(index)].depth = self.scope_depth as isize;
}
pub fn bind_local(&mut self, name: IdentifierValue) -> Result<LocalId, CompileError> {
if self.scope_depth == 0 {
todo!();
}
let mut i = self.locals.len();
while i > 0 {
i -= 1;
let local = &self.locals[i];
if local.depth != -1 && local.depth < self.scope_depth as isize {
break;
}
if local.name == name {
todo!("Same name, same scope");
}
}
self.add_local(name, None)
}
fn resolve_local(&mut self, name: &str) -> Result<Option<LocalId>, CompileError> {
let Some(index) = self
.locals
.iter()
.rposition(|local| local.name.as_ref() == name)
else {
return Ok(None);
};
let local = &self.locals[index];
if local.depth == -1 {
return Err(CompileError::UndefinedLocalReference(name.into()));
}
Ok(Some(index.try_into().unwrap()))
}
}
#[cfg(test)]
pub fn test_compile(
expression: &Expression,
) -> Result<(CompileContext, CompileValue), CompileError> {
let mut cx = CompileContext::new(Default::default(), None, None);
let value = expression.compile(&mut cx)?;
Ok((cx, value))
}
@@ -0,0 +1,44 @@
use crate::compile::{
block::{Compile, CompileContext, CompileValue},
error::CompileError,
syntax::{LetExpression, SetqExpression},
};
impl Compile for LetExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
cx.push_scope();
let mut binding_indices = vec![];
for binding in self.bindings.iter() {
let index = cx.bind_local(binding.identifier.clone())?;
let value = binding.value.compile(cx)?;
cx.push(value)?;
if self.sequential {
// Mark initialized
cx.init_local(index);
} else {
binding_indices.push(index);
}
}
if !self.sequential {
for index in binding_indices {
// Mark initialized
cx.init_local(index);
}
}
let value = self.body.compile(cx)?;
cx.pop_scope(Some(value))?;
Ok(CompileValue::Stack)
}
}
impl Compile for SetqExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
for assignment in self.pairs.iter() {
let value = assignment.value.compile(cx)?;
cx.compile_assign(assignment.identifier.clone(), value)?;
}
Ok(CompileValue::Nil)
}
}
@@ -0,0 +1,128 @@
use crate::compile::{
block::{Compile, CompileContext, CompileValue},
error::CompileError,
instruction::Emitted,
syntax::{
CondExpression, IfExpression, LoopExpression, PrognExpression, ReturnExpression,
WhileExpression,
},
};
impl Compile for WhileExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let label_entry = cx.new_label();
let label_exit = cx.new_label();
// Loop condition
cx.adjust_label(label_entry);
let condition_value = self.condition.compile(cx)?;
cx.push(condition_value)?;
cx.emit(Emitted::Branch(label_exit));
cx.enter_loop(label_entry, label_exit);
let body_value = self.body.compile(cx)?;
cx.discard(body_value);
cx.leave_loop();
cx.emit(Emitted::Jump(label_entry));
cx.adjust_label(label_exit);
Ok(CompileValue::Nil)
}
}
impl Compile for LoopExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let label_entry = cx.new_label();
let label_exit = cx.new_label();
cx.adjust_label(label_entry);
cx.enter_loop(label_entry, label_exit);
let body_value = self.body.compile(cx)?;
cx.discard(body_value);
cx.leave_loop();
cx.emit(Emitted::Jump(label_entry));
cx.adjust_label(label_exit);
Ok(CompileValue::Nil)
}
}
impl Compile for IfExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let label_false = cx.new_label();
let label_end = cx.new_label();
// Emit condition
let condition_value = self.condition.compile(cx)?;
cx.push(condition_value)?;
cx.emit(Emitted::Branch(label_false));
// True branch
let true_value = self.if_true.compile(cx)?;
cx.push(true_value)?;
cx.emit(Emitted::Jump(label_end));
// False branch
cx.adjust_label(label_false);
let false_value = match self.if_false.as_ref() {
Some(expr) => expr.compile(cx)?,
None => CompileValue::Nil,
};
cx.push(false_value)?;
cx.adjust_label(label_end);
Ok(CompileValue::Stack)
}
}
impl Compile for CondExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let label_end = cx.new_label();
for condition in self.arms.iter() {
let label_otherwise = cx.new_label();
let condition_value = condition.condition.compile(cx)?;
cx.push(condition_value)?;
cx.emit(Emitted::Branch(label_otherwise));
// Condition true
let then_value = condition.then.compile(cx)?;
cx.push(then_value)?;
cx.emit(Emitted::Jump(label_end));
// Condition false
cx.adjust_label(label_otherwise);
}
let otherwise_value = if let Some(otherwise_arm) = self.otherwise_arm.as_ref() {
otherwise_arm.compile(cx)?
} else {
CompileValue::Nil
};
cx.push(otherwise_value)?;
cx.adjust_label(label_end);
Ok(CompileValue::Stack)
}
}
impl Compile for PrognExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
self.body.compile(cx)
}
}
impl Compile for ReturnExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let value = self.expression.compile(cx)?;
cx.compile_return_value(value)?;
Ok(CompileValue::Nil)
}
}
pub(super) fn compile_break(cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
cx.emit_break()?;
Ok(CompileValue::Nil)
}
pub(super) fn compile_continue(cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
cx.emit_continue()?;
Ok(CompileValue::Nil)
}
@@ -0,0 +1,309 @@
use crate::{
compile::{
block::{Compile, CompileContext, CompileValue},
error::CompileError,
syntax::{
CallExpression, DefmacroExpression, DefunExpression, Expression, FunctionBody,
LambdaExpression,
},
},
vm::instruction::{ArgumentCount, Instruction},
};
fn builtin_identifier_callee(identifier: &str) -> Option<Instruction> {
match identifier {
"+" => Some(Instruction::Add),
"-" => Some(Instruction::Sub),
"*" | "·" => Some(Instruction::Mul),
"/" | "÷" => Some(Instruction::Div),
"%" => Some(Instruction::Mod),
"=" => Some(Instruction::Eq),
"/=" | "" => Some(Instruction::Ne),
">" => Some(Instruction::Gt),
"<" => Some(Instruction::Lt),
">=" | "" => Some(Instruction::Ge),
"<=" | "" => Some(Instruction::Le),
"not" => Some(Instruction::Not),
_ => None,
}
}
impl Compile for FunctionBody {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
for expression in self.head.iter() {
cx.compile_statement(expression)?;
}
self.tail.compile(cx)
}
}
impl Compile for CallExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let argument_count = ArgumentCount::try_from(self.arguments.len())
.map_err(|_| CompileError::TooManyArgumentsInCall)?;
let instruction = if let Expression::Identifier(callee) = self.callee.as_ref() {
if let Some(instruction) = builtin_identifier_callee(callee.as_ref()) {
instruction
} else {
Instruction::Call
}
} else {
Instruction::Call
};
if instruction == Instruction::Call {
let callee = self.callee.compile(cx)?;
cx.push(callee)?;
cx.guard_argument();
}
for expression in self.arguments.iter() {
let value = expression.compile(cx)?;
cx.push(value)?;
cx.guard_argument();
}
cx.emit(instruction);
cx.emit(argument_count);
let mut unguard_count = usize::from(argument_count);
if instruction == Instruction::Call {
unguard_count += 1;
}
for _ in 0..unguard_count {
cx.unguard_argument();
}
Ok(CompileValue::Stack)
}
}
impl Compile for LambdaExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let function = cx.compile_function(
Some("lambda".into()),
self.docstring.clone(),
&self.signature,
&self.body,
)?;
Ok(CompileValue::LocalFunction(function))
}
}
impl Compile for DefunExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let function = cx.compile_function(
Some(self.name.clone()),
self.docstring.clone(),
&self.signature,
&self.body,
)?;
cx.compile_set_global(self.name.clone(), CompileValue::LocalFunction(function))?;
Ok(CompileValue::Nil)
}
}
impl Compile for DefmacroExpression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
let function = cx.compile_function(
Some(self.name.clone()),
self.docstring.clone(),
&self.signature,
&self.body,
)?;
cx.compile_declare_macro(self.name.clone(), CompileValue::LocalFunction(function))?;
Ok(CompileValue::Nil)
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use crate::{
compile::{
UpvalueDef,
block::{CompileValue, test_compile},
function::FunctionSignature,
syntax::{
Assignment, CallExpression, DefunExpression, Expression, FunctionBody,
LambdaExpression, LetExpression,
},
},
vm::instruction::{Instruction, LocalId},
};
#[test]
fn test_lambda_capture_compile() {
// (let (x 1)
// ((lambda (y) (+ x y)) 2))
let e = Expression::Let(LetExpression {
sequential: false,
bindings: vec![Assignment {
identifier: "x".into(),
value: Rc::new(Expression::IntegerLiteral(1.into())),
}],
body: FunctionBody {
head: vec![],
tail: Rc::new(Expression::Call(CallExpression {
callee: Rc::new(Expression::Lambda(LambdaExpression {
docstring: None,
signature: FunctionSignature {
required_arguments: vec!["y".into()],
optional_arguments: vec![],
rest_argument: None,
},
body: FunctionBody {
head: vec![],
tail: Rc::new(Expression::Call(CallExpression {
callee: Rc::new(Expression::Identifier("+".into())),
arguments: vec![
Rc::new(Expression::Identifier("x".into())),
Rc::new(Expression::Identifier("y".into())),
],
})),
},
})),
arguments: vec![Rc::new(Expression::IntegerLiteral(2.into()))],
})),
},
});
let (cx, _v) = test_compile(&e).unwrap();
assert_eq!(cx.current, 0);
assert_eq!(cx.function_blocks.len(), 2);
let root_function = cx.function_blocks[0].to_bytecode().unwrap();
let lambda_function = cx.function_blocks[1].to_bytecode().unwrap();
// lambda
assert_eq!(
lambda_function.instructions.as_ref(),
&[
Instruction::GetUpvalue.into(),
0,
Instruction::GetLocal.into(),
0,
Instruction::Add.into(),
2,
Instruction::Return.into(),
]
);
assert_eq!(lambda_function.required_count, 1);
assert_eq!(lambda_function.optional_count, 0);
assert!(!lambda_function.has_rest);
assert_eq!(
lambda_function.upvalues.as_ref(),
&[UpvalueDef {
index: LocalId::from(0),
is_local: true
}]
);
// root
assert_eq!(
root_function.instructions.as_ref(),
&[
// (let ...
// ... (x 1) ...
Instruction::PushInteger.into(),
1,
0,
// ... ((lambda (y) (+ x y)) 2) ...
// (lambda ...)
Instruction::PushConstant.into(),
0,
0,
Instruction::MakeClosure.into(),
// 2
Instruction::PushInteger.into(),
2,
0,
// ((lambda ...) 2)
Instruction::Call.into(),
1,
// (let ... ) exit
Instruction::SetTemp.into(),
Instruction::CloseUpvalue.into(),
Instruction::GetTemp.into(),
]
);
}
#[test]
fn test_lambda_compile() {
let e = Expression::Lambda(LambdaExpression {
docstring: None,
signature: FunctionSignature {
required_arguments: vec!["a".into()],
optional_arguments: vec![],
rest_argument: None,
},
body: FunctionBody {
head: vec![],
tail: Rc::new(Expression::IntegerLiteral(1234.into())),
},
});
let (cx, v) = test_compile(&e).unwrap();
assert_eq!(cx.current, 0);
assert!(cx.function_blocks[0].instructions.is_empty());
let CompileValue::LocalFunction(index) = v else {
panic!("lambda did not compile to a function value")
};
let lambda_function = cx.function_blocks[index].to_bytecode().unwrap();
assert_eq!(
lambda_function.instructions.as_ref(),
&[
Instruction::PushInteger.into(),
210,
4,
Instruction::Return.into(),
]
);
}
#[test]
fn test_compile_defun() {
let e = Expression::Defun(DefunExpression {
name: "my-function".into(),
docstring: None,
signature: FunctionSignature {
required_arguments: vec!["a".into()],
optional_arguments: vec![],
rest_argument: None,
},
body: FunctionBody {
head: vec![],
tail: Rc::new(Expression::Identifier("a".into())),
},
});
let (cx, _v) = test_compile(&e).unwrap();
assert_eq!(cx.current, 0);
assert_eq!(cx.function_blocks.len(), 2);
let root_function = cx.function_blocks[0].to_bytecode().unwrap();
let defun_function = cx.function_blocks[1].to_bytecode().unwrap();
// identifier + function
assert_eq!(root_function.constants.len(), 2);
assert_eq!(
root_function.instructions.as_ref(),
&[
// Identifier
Instruction::PushConstant.into(),
0,
0,
// Function
Instruction::PushConstant.into(),
1,
0,
Instruction::SetGlobal.into(),
]
);
// inner function
assert!(defun_function.constants.is_empty());
assert_eq!(defun_function.required_count, 1);
assert_eq!(defun_function.optional_count, 0);
assert!(!defun_function.has_rest);
assert_eq!(
defun_function.instructions.as_ref(),
&[
// a
Instruction::GetLocal.into(),
0,
Instruction::Return.into(),
]
);
}
}
@@ -0,0 +1,64 @@
use crate::compile::{
block::{Compile, CompileContext, CompileValue},
error::CompileError,
syntax::Expression,
};
mod binding;
mod flow;
mod function;
impl Compile for Expression {
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
match self {
Self::Nil => Ok(CompileValue::Nil),
Self::Break => flow::compile_break(cx),
Self::Continue => flow::compile_continue(cx),
Self::Return(return_) => return_.compile(cx),
Self::Call(call) => call.compile(cx),
Self::If(if_) => if_.compile(cx),
Self::Cond(cond) => cond.compile(cx),
Self::Let(let_) => let_.compile(cx),
Self::Setq(setq) => setq.compile(cx),
Self::While(while_) => while_.compile(cx),
Self::Loop(loop_) => loop_.compile(cx),
Self::Defun(defun) => defun.compile(cx),
Self::Lambda(lambda) => lambda.compile(cx),
Self::Progn(progn) => progn.compile(cx),
Self::Defmacro(defmacro) => defmacro.compile(cx),
Self::Identifier(identifier) => Ok(CompileValue::Identifier(identifier.clone())),
&Self::IntegerLiteral(value) => Ok(CompileValue::Integer(value)),
&Self::BooleanLiteral(value) => Ok(CompileValue::Boolean(value)),
Self::StringLiteral(value) => Ok(CompileValue::String(value.clone())),
Self::Vector(vector) => Ok(CompileValue::Value(vector.clone().into())),
Self::Quote(value) => Ok(CompileValue::Quote(value.clone())),
Self::SyntaxError(_) => unreachable!(),
}
}
}
#[cfg(test)]
mod tests {
use crate::compile::{
block::{CompileValue, test_compile},
syntax::Expression,
};
#[test]
fn test_nil_compile() {
let e = Expression::Nil;
let (cx, v) = test_compile(&e).unwrap();
assert_eq!(cx.current, 0);
assert!(cx.function_blocks[0].instructions.is_empty());
assert_eq!(v, CompileValue::Nil);
}
#[test]
fn test_integer_literal_compile() {
let e = Expression::IntegerLiteral(1.into());
let (cx, v) = test_compile(&e).unwrap();
assert_eq!(cx.current, 0);
assert!(cx.function_blocks[0].instructions.is_empty());
assert_eq!(v, CompileValue::Integer(1.into()));
}
}
+29
View File
@@ -0,0 +1,29 @@
#![coverage(off)]
use crate::{compile::syntax::ParseError, vm::value::IdentifierValue};
#[derive(Debug, PartialEq, thiserror::Error)]
pub enum CompileError {
#[error("parse error: {0:?}")]
Parse(Vec<ParseError>),
#[error("block has too many local variables")]
TooManyLocals,
#[error("module has too many constants")]
TooManyConstants,
#[error("function call has too many arguments")]
TooManyArgumentsInCall,
#[error("(break) used outside of a loop")]
BreakOutsideOfLoop,
#[error("(continue) used outside of a loop")]
ContinueOutsideOfLoop,
#[error("cannot emit branch instruction from {0} to {1}")]
CannotEmitBranch(usize, usize),
#[error("undefined local value reference: {0}")]
UndefinedLocalReference(IdentifierValue),
}
impl From<Vec<ParseError>> for CompileError {
fn from(value: Vec<ParseError>) -> Self {
Self::Parse(value)
}
}
@@ -0,0 +1,52 @@
use crate::vm::value::IdentifierValue;
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionSignature {
pub required_arguments: Vec<IdentifierValue>,
pub optional_arguments: Vec<IdentifierValue>,
pub rest_argument: Option<IdentifierValue>,
}
impl FunctionSignature {
pub const EMPTY: Self = Self {
required_arguments: vec![],
optional_arguments: vec![],
rest_argument: None,
};
pub fn argument(&self, name: &str) -> Option<usize> {
if let Some(index) = self
.required_arguments
.iter()
.position(|a| a.as_ref() == name)
{
Some(index)
} else if let Some(index) = self
.optional_arguments
.iter()
.position(|a| a.as_ref() == name)
{
Some(index + self.required_arguments.len())
} else if self
.rest_argument
.as_ref()
.is_some_and(|a| a.as_ref() == name)
{
Some(self.required_arguments.len() + self.optional_arguments.len())
} else {
None
}
}
pub fn min_arity(&self) -> usize {
self.required_arguments.len()
}
pub fn max_arity(&self) -> usize {
if self.rest_argument.is_none() {
self.required_arguments.len() + self.optional_arguments.len()
} else {
usize::MAX
}
}
}
@@ -0,0 +1,46 @@
use crate::vm::instruction::{ArgumentCount, ConstantId, ImmediateInteger, Instruction, LocalId};
#[derive(Debug, Clone, Copy)]
pub enum Emitted {
Branch(usize),
Jump(usize),
Instruction(Instruction),
LocalId(LocalId),
ConstantId(ConstantId),
ArgumentCount(ArgumentCount),
ImmediateInteger(ImmediateInteger),
Bool(bool),
// Instruction(Instruction),
}
impl From<Instruction> for Emitted {
fn from(value: Instruction) -> Self {
Self::Instruction(value)
}
}
impl From<LocalId> for Emitted {
fn from(value: LocalId) -> Self {
Self::LocalId(value)
}
}
impl From<ConstantId> for Emitted {
fn from(value: ConstantId) -> Self {
Self::ConstantId(value)
}
}
impl From<ArgumentCount> for Emitted {
fn from(value: ArgumentCount) -> Self {
Self::ArgumentCount(value)
}
}
impl From<ImmediateInteger> for Emitted {
fn from(value: ImmediateInteger) -> Self {
Self::ImmediateInteger(value)
}
}
impl From<bool> for Emitted {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
+14
View File
@@ -0,0 +1,14 @@
mod block;
mod codegen;
mod error;
mod function;
mod instruction;
pub mod syntax;
#[derive(Default, Clone)]
pub struct CompileOptions {
pub trace_compile: bool,
}
pub use block::{Compile, CompileContext, UpvalueDef};
pub use error::CompileError;
@@ -0,0 +1,293 @@
use std::rc::Rc;
use crate::{
compile::syntax::{
CollectErrors, Expression, FunctionBody,
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
},
vm::value::{ConsCell, IdentifierValue, Keyword, Value},
};
#[derive(Debug, PartialEq)]
pub struct Assignment {
pub identifier: IdentifierValue,
pub value: Rc<Expression>,
}
#[derive(Debug, PartialEq)]
pub struct SetqExpression {
pub pairs: Vec<Assignment>,
}
#[derive(Debug, PartialEq)]
pub struct LetExpression {
pub sequential: bool,
pub bindings: Vec<Assignment>,
pub body: FunctionBody,
}
impl Assignment {
fn parse_list(value: &Value, input: &Value, after: Keyword) -> Result<Vec<Self>, ParseError> {
let mut iter = value.syntax_iter(ExpectedWhere::InAssignment);
let mut pairs = vec![];
loop {
let [identifier, value] = match iter.next_chunk::<2>() {
Ok(pair) => pair,
Err(rest) => {
if !rest.is_empty() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::try_collect_extraneous(
rest.map(|x| x.cloned()),
)?,
});
}
break;
}
};
let Value::Identifier(identifier) = identifier? else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Identifier,
ExpectedWhere::InAssignment,
),
});
};
let value = Expression::parse_inner(value?);
pairs.push(Self {
identifier: identifier.clone(),
value,
});
}
if pairs.is_empty() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::AtLeastOneBinding,
ExpectedWhere::AfterKeyword(after),
),
});
}
Ok(pairs)
}
}
impl LetExpression {
pub(super) fn parse(
value: &Value,
input: &Value,
keyword: Keyword,
) -> Result<Self, ParseError> {
let Value::Cons(value) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::AfterKeyword(keyword),
),
});
};
let ConsCell(bindings, cdr) = value.as_ref();
let bindings = Assignment::parse_list(bindings, input, keyword)?;
let body = FunctionBody::parse(cdr, input, keyword)?;
let sequential = keyword == Keyword::LetStar;
Ok(Self {
sequential,
bindings,
body,
})
}
}
impl SetqExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let pairs = Assignment::parse_list(value, input, Keyword::Setq)?;
Ok(Self { pairs })
}
}
impl CollectErrors<ParseError> for SetqExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.pairs.iter().fold(false, |acc, v| {
let r = v.collect_errors(errors);
acc | r
})
}
}
impl CollectErrors<ParseError> for LetExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
let r0 = self.bindings.iter().fold(false, |acc, v| {
let r = v.collect_errors(errors);
acc | r
});
let r1 = self.body.collect_errors(errors);
r0 | r1
}
}
impl CollectErrors<ParseError> for Assignment {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.value.collect_errors(errors)
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use crate::{
compile::syntax::{
Assignment, Expression, FunctionBody, LetExpression, SetqExpression,
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
},
vm::value::{ConsCell, Keyword, Value},
};
#[test]
fn test_parse_let() {
let v = Value::list_or_nil([
Value::Keyword(Keyword::Let),
Value::list_or_nil([Value::Identifier("a".into()), Value::Number(1.into())]),
Value::Nil,
]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::Let(LetExpression {
sequential: false,
bindings: vec![Assignment {
identifier: "a".into(),
value: Rc::new(Expression::IntegerLiteral(1.into()))
},],
body: FunctionBody {
head: vec![],
tail: Rc::new(Expression::Nil)
},
})
);
let v = Value::list_or_nil([
Value::Keyword(Keyword::LetStar),
Value::list_or_nil([
Value::Identifier("a".into()),
Value::Number(1.into()),
Value::Identifier("b".into()),
Value::Number(2.into()),
]),
Value::Nil,
]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::Let(LetExpression {
sequential: true,
bindings: vec![
Assignment {
identifier: "a".into(),
value: Rc::new(Expression::IntegerLiteral(1.into()))
},
Assignment {
identifier: "b".into(),
value: Rc::new(Expression::IntegerLiteral(2.into()))
}
],
body: FunctionBody {
head: vec![],
tail: Rc::new(Expression::Nil)
},
})
);
// syntax errors
let v = Value::Cons(Rc::new(ConsCell(
Value::Keyword(Keyword::Let),
Value::Identifier("a".into()),
)));
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
&e[..],
&[ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::AfterKeyword(Keyword::Let)
)
}]
);
}
#[test]
fn test_parse_setq() {
let v = Value::list_or_nil([
Value::Keyword(Keyword::Setq),
Value::Identifier("a".into()),
Value::Identifier("b".into()),
Value::Identifier("c".into()),
Value::Identifier("d".into()),
]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::Setq(SetqExpression {
pairs: vec![
Assignment {
identifier: "a".into(),
value: Rc::new(Expression::Identifier("b".into())),
},
Assignment {
identifier: "c".into(),
value: Rc::new(Expression::Identifier("d".into())),
}
]
})
);
// syntax errors
let v = Value::list_or_nil([Value::Keyword(Keyword::Setq)]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
&e[..],
&[ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::AtLeastOneBinding,
ExpectedWhere::AfterKeyword(Keyword::Setq)
)
}]
);
let v = Value::list_or_nil([
Value::Keyword(Keyword::Setq),
Value::Identifier("a".into()),
Value::Number(1.into()),
Value::Identifier("b".into()),
]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
&e[..],
&[ParseError {
input: v,
error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell(
Value::Identifier("b".into()),
Value::Nil
)))
}]
);
let v = Value::list_or_nil([
Value::Keyword(Keyword::Setq),
Value::Number(1.into()),
Value::Identifier("a".into()),
]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
&e[..],
&[ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::Identifier,
ExpectedWhere::InAssignment
)
}]
);
}
}
@@ -0,0 +1,92 @@
use std::rc::Rc;
use crate::{
compile::syntax::{
CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
},
vm::value::{ConsCell, Value},
};
#[derive(Debug, PartialEq)]
pub struct CallExpression {
pub callee: Rc<Expression>,
pub arguments: Vec<Rc<Expression>>,
}
impl CallExpression {
pub(super) fn parse(cons: &ConsCell, input: &Value) -> Result<Self, ParseError> {
let ConsCell(car, cdr) = cons;
let callee = Expression::parse_inner(car);
let mut arguments = vec![];
let mut list = cdr;
while !list.is_nil() {
let Value::Cons(cons) = list else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InCallExpression,
),
});
};
let ConsCell(car, cdr) = cons.as_ref();
let expression = Expression::parse_inner(car);
arguments.push(expression);
list = cdr;
}
Ok(Self { callee, arguments })
}
}
impl CollectErrors<ParseError> for CallExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
let mut r = self.callee.collect_errors(errors);
for expr in self.arguments.iter() {
r |= expr.collect_errors(errors);
}
r
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use crate::{
compile::syntax::{
CallExpression, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
},
vm::value::Value,
};
#[test]
fn test_parse_call() {
let v = Value::list_or_nil([Value::Identifier("a".into())]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::Call(CallExpression {
callee: Rc::new(Expression::Identifier("a".into())),
arguments: vec![]
})
);
// Syntax errors
let v = Value::Identifier("a".into()).cons(Value::Number(1.into()));
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InCallExpression
),
}]
);
}
}
@@ -0,0 +1,490 @@
use std::rc::Rc;
use crate::{
compile::syntax::{
CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
},
vm::value::{ConsCell, Keyword, Value},
};
#[derive(Debug, PartialEq)]
pub struct IfExpression {
pub condition: Rc<Expression>,
pub if_true: Rc<Expression>,
pub if_false: Option<Rc<Expression>>,
}
#[derive(Debug, PartialEq)]
pub struct CondExpressionArm {
pub condition: Rc<Expression>,
pub then: Rc<Expression>,
}
#[derive(Debug, PartialEq)]
pub struct CondExpression {
pub arms: Vec<CondExpressionArm>,
pub otherwise_arm: Option<Rc<Expression>>,
}
impl IfExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let Value::Cons(value) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Condition,
ExpectedWhere::AfterKeyword(Keyword::If),
),
});
};
let ConsCell(condition, cdr) = value.as_ref();
let condition = Expression::parse_inner(condition);
let Value::Cons(cdr) = cdr else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::AfterCondition,
),
});
};
let ConsCell(if_true, cdr) = cdr.as_ref();
let if_true = Expression::parse_inner(if_true);
let if_false = if cdr.is_nil() {
None
} else {
let Value::Cons(cdr) = cdr else { todo!() };
let ConsCell(if_false, cdr) = cdr.as_ref();
if !cdr.is_nil() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::extraneous(cdr),
});
}
Some(Expression::parse_inner(if_false))
};
Ok(Self {
condition,
if_true,
if_false,
})
}
}
impl CondExpressionArm {
fn parse(cons: &ConsCell, input: &Value) -> Result<Self, ParseError> {
let ConsCell(condition, cdr) = cons;
let condition = Expression::parse_inner(condition);
let Value::Cons(cons) = cdr else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::InConditionArm,
),
});
};
let ConsCell(then, cdr) = cons.as_ref();
if !cdr.is_nil() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::extraneous(cdr),
});
}
let then = Expression::parse_inner(then);
Ok(Self { condition, then })
}
}
impl CondExpression {
pub(super) fn parse(mut value: &Value, input: &Value) -> Result<Self, ParseError> {
let mut arms = vec![];
let mut otherwise_arm = None;
while !value.is_nil() {
let Value::Cons(cons) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ConditionList,
ExpectedWhere::AfterKeyword(Keyword::Cond),
),
});
};
let ConsCell(arm, cdr) = cons.as_ref();
// Arm must be a list
let Value::Cons(arm_cons) = arm else {
return Err(ParseError {
input: arm.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Condition,
ExpectedWhere::InConditionList,
),
});
};
let ConsCell(arm_condition, arm_condition_cdr) = arm_cons.as_ref();
match arm_condition {
Value::Keyword(Keyword::Otherwise) => {
if otherwise_arm.is_some() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::extraneous(arm),
});
}
let Value::Cons(otherwise_cons) = arm_condition_cdr else {
return Err(ParseError {
input: arm.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InConditionList,
),
});
};
let ConsCell(arm_expr, arm_cdr) = otherwise_cons.as_ref();
if !arm_cdr.is_nil() {
return Err(ParseError {
input: arm.clone(),
error: ParseErrorKind::extraneous(arm_cdr),
});
}
let arm = Expression::parse_inner(arm_expr);
otherwise_arm = Some(arm);
}
_ => {
if otherwise_arm.is_some() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::extraneous(arm),
});
}
let arm = CondExpressionArm::parse(arm_cons, arm)?;
arms.push(arm);
}
}
value = cdr;
}
if arms.is_empty() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ConditionList,
ExpectedWhere::AfterKeyword(Keyword::Cond),
),
});
}
Ok(Self {
arms,
otherwise_arm,
})
}
}
impl CollectErrors<ParseError> for IfExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
let a = self.condition.collect_errors(errors);
let b = self.if_true.collect_errors(errors);
let c = self
.if_false
.as_ref()
.map(|x| x.collect_errors(errors))
.unwrap_or_default();
a | b | c
}
}
impl CollectErrors<ParseError> for CondExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
let mut r = false;
for arm in self.arms.iter() {
r |= arm.condition.collect_errors(errors);
r |= arm.then.collect_errors(errors);
}
if let Some(otherwise_arm) = self.otherwise_arm.as_ref() {
r |= otherwise_arm.collect_errors(errors);
}
r
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use crate::{
compile::syntax::{
CondExpression, CondExpressionArm, ExpectedWhat, ExpectedWhere, Expression,
IfExpression, ParseError, ParseErrorKind,
},
vm::value::{ConsCell, Keyword, Value},
};
#[test]
fn test_parse_cond() {
// without &otherwise branch
let b0 = Value::list_or_nil([Value::Boolean(true.into()), Value::Identifier("a".into())]);
let b1 = Value::list_or_nil([Value::Boolean(false.into()), Value::Identifier("b".into())]);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::Cond(CondExpression {
arms: vec![
CondExpressionArm {
condition: Rc::new(Expression::BooleanLiteral(true.into())),
then: Rc::new(Expression::Identifier("a".into()))
},
CondExpressionArm {
condition: Rc::new(Expression::BooleanLiteral(false.into())),
then: Rc::new(Expression::Identifier("b".into()))
}
],
otherwise_arm: None
})
);
// with &otherwise branch
let b0 = Value::list_or_nil([Value::Boolean(true.into()), Value::Identifier("a".into())]);
let b1 = Value::list_or_nil([
Value::Keyword(Keyword::Otherwise),
Value::Identifier("b".into()),
]);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::Cond(CondExpression {
arms: vec![CondExpressionArm {
condition: Rc::new(Expression::BooleanLiteral(true.into())),
then: Rc::new(Expression::Identifier("a".into()))
},],
otherwise_arm: Some(Rc::new(Expression::Identifier("b".into())))
})
);
// syntax error
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond)]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::ConditionList,
ExpectedWhere::AfterKeyword(Keyword::Cond)
)
}]
);
let v = Value::Keyword(Keyword::Cond).cons(Value::Identifier("a".into()));
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::ConditionList,
ExpectedWhere::AfterKeyword(Keyword::Cond)
)
}]
);
let b0 = Value::list_or_nil([Value::Boolean(false.into())]);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: b0,
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::InConditionArm
)
}]
);
let b0 = Value::Boolean(false.into());
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: b0,
error: ParseErrorKind::Expected(
ExpectedWhat::Condition,
ExpectedWhere::InConditionList
)
}]
);
let b0 = Value::Keyword(Keyword::Otherwise);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: b0,
error: ParseErrorKind::Expected(
ExpectedWhat::Condition,
ExpectedWhere::InConditionList
)
}]
);
let b0 = Value::Keyword(Keyword::Otherwise).cons(Value::Number(1.into()));
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: b0,
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InConditionList
)
}]
);
let b0 = Value::Boolean(false.into()).cons(Value::Boolean(false.into()));
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: b0,
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::InConditionArm
)
}]
);
let b0 = Value::list_or_nil([
Value::Boolean(false.into()),
Value::Number(1.into()),
Value::Number(2.into()),
]);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: b0,
error: ParseErrorKind::extraneous(&Value::list_or_nil([Value::Number(2.into())]))
}]
);
let b0 = Value::list_or_nil([
Value::Keyword(Keyword::Otherwise),
Value::Number(1.into()),
Value::Number(2.into()),
]);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: b0,
error: ParseErrorKind::extraneous(&Value::list_or_nil([Value::Number(2.into())]))
}]
);
let b0 = Value::list_or_nil([Value::Keyword(Keyword::Otherwise), Value::Number(1.into())]);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone(), b0.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::extraneous(&b0)
}]
);
let b0 = Value::list_or_nil([Value::Keyword(Keyword::Otherwise), Value::Number(1.into())]);
let b1 = Value::list_or_nil([Value::Boolean(false.into()), Value::Number(1.into())]);
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1.clone()]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::extraneous(&b1)
}]
);
}
#[test]
fn test_parse_if() {
// without false branch
let v = Value::list_or_nil([
Value::Keyword(Keyword::If),
Value::Boolean(true.into()),
Value::Number(1.into()),
]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::If(IfExpression {
condition: Rc::new(Expression::BooleanLiteral(true.into())),
if_true: Rc::new(Expression::IntegerLiteral(1.into())),
if_false: None
})
);
// with false branch
let v = Value::list_or_nil([
Value::Keyword(Keyword::If),
Value::Boolean(false.into()),
Value::Number(1.into()),
Value::Number(2.into()),
]);
let e = Expression::parse(&v).unwrap();
assert_eq!(
e.as_ref(),
&Expression::If(IfExpression {
condition: Rc::new(Expression::BooleanLiteral(false.into())),
if_true: Rc::new(Expression::IntegerLiteral(1.into())),
if_false: Some(Rc::new(Expression::IntegerLiteral(2.into())))
})
);
// invalid syntax
let v = Value::list_or_nil([Value::Keyword(Keyword::If)]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::Condition,
ExpectedWhere::AfterKeyword(Keyword::If)
)
}]
);
let v = Value::list_or_nil([Value::Keyword(Keyword::If), Value::Boolean(true.into())]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::AfterCondition
)
}]
);
let v = Value::list_or_nil([
Value::Keyword(Keyword::If),
Value::Boolean(true.into()),
Value::Number(1.into()),
Value::Number(2.into()),
Value::Number(3.into()),
]);
let e = Expression::parse(&v).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: v,
error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell::end(
Value::Number(3.into())
)))
}]
);
}
}
@@ -0,0 +1,111 @@
#![coverage(off)]
use std::{error::Error as StdError, fmt, rc::Rc};
use crate::vm::value::{ConsCell, Keyword, Value};
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub input: Value,
pub error: ParseErrorKind,
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ParseErrorKind {
#[error("Not an expression")]
NotAnExpression,
#[error("Expected {0} {1}")]
Expected(ExpectedWhat, ExpectedWhere),
#[error("Extraneous expression: {0}")]
ExtraneousExpression(Value),
#[error("Extraneous expression(s): {0}")]
ExtraneousExpressions(Rc<ConsCell>),
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ExpectedWhat {
#[error("an expression")]
Expression,
#[error("an argument list")]
ArgumentList,
#[error("a condition")]
Condition,
#[error("a condition list")]
ConditionList,
#[error("at least one argument")]
AtLeastOneArgument,
#[error("exactly one argument")]
ExactlyOneArgument,
#[error("an argument, &optional or &rest keywords")]
ArgumentSpec,
#[error("a proper list")]
ProperList,
#[error("an identifier")]
Identifier,
#[error("at least one binding pair")]
AtLeastOneBinding,
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ExpectedWhere {
#[error("after a condition")]
AfterCondition,
#[error("after the \"{0}\" keyword")]
AfterKeyword(Keyword),
#[error("after the argument list in a {0}")]
AfterArgumentList(Keyword),
#[error("in the argument list")]
InArgumentList,
#[error("in the call expression")]
InCallExpression,
#[error("in the condition list")]
InConditionList,
#[error("in the condition arm")]
InConditionArm,
#[error("in the function definition")]
InFunctionDefinition,
#[error("in assignment")]
InAssignment,
}
pub(super) trait CollectErrors<E> {
fn collect_errors(&self, errors: &mut Vec<E>) -> bool;
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.error, f)
}
}
impl StdError for ParseError {
fn description(&self) -> &str {
"Parse error"
}
}
impl ParseErrorKind {
pub fn extraneous(value: &Value) -> Self {
match value {
Value::Nil => unreachable!(),
Value::Cons(cons) => Self::ExtraneousExpressions(cons.clone()),
_ => Self::ExtraneousExpression(value.clone()),
}
}
pub fn collect_extraneous<I: IntoIterator<Item = Value>>(iter: I) -> Self
where
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
{
Self::extraneous(&Value::list_or_nil(iter))
}
pub fn try_collect_extraneous<E, I: IntoIterator<Item = Result<Value, E>>>(
iter: I,
) -> Result<Self, E>
where
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
{
Ok(Self::extraneous(&Value::try_list_or_nil(iter)?))
}
}
@@ -0,0 +1,607 @@
use std::rc::Rc;
use crate::{
compile::{
function::FunctionSignature,
syntax::{
CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
maybe_docstring,
},
},
vm::value::{ConsCell, IdentifierValue, Keyword, StringValue, Value},
};
#[derive(Debug, PartialEq)]
pub struct FunctionBody {
pub head: Vec<Rc<Expression>>,
pub tail: Rc<Expression>,
}
#[derive(Debug, PartialEq)]
pub struct LambdaExpression {
pub docstring: Option<StringValue>,
pub signature: FunctionSignature,
pub body: FunctionBody,
}
#[derive(Debug, PartialEq)]
pub struct DefunExpression {
pub name: IdentifierValue,
pub docstring: Option<StringValue>,
pub signature: FunctionSignature,
pub body: FunctionBody,
}
#[derive(Debug, PartialEq)]
pub struct ReturnExpression {
pub expression: Rc<Expression>,
}
#[derive(Debug, PartialEq)]
pub struct PrognExpression {
pub body: FunctionBody,
}
impl FunctionSignature {
pub(super) fn parse(mut value: &Value, input: &Value) -> Result<Self, ParseError> {
enum Mode {
Required,
Optional,
Rest,
}
let mut required_arguments = vec![];
let mut optional_arguments = vec![];
let mut rest_argument = None;
let mut mode = Mode::Required;
while !value.is_nil() {
let Value::Cons(cons) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ArgumentSpec,
ExpectedWhere::InArgumentList,
),
});
};
let ConsCell(car, cdr) = cons.as_ref();
match (&mode, car) {
(Mode::Required, Value::Identifier(arg)) => {
required_arguments.push(arg.clone());
}
(Mode::Optional, Value::Identifier(arg)) => {
optional_arguments.push(arg.clone());
}
(Mode::Rest, Value::Identifier(arg)) => {
if rest_argument.is_some() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ExactlyOneArgument,
ExpectedWhere::AfterKeyword(Keyword::Rest),
),
});
}
rest_argument = Some(arg.clone());
}
(Mode::Required, Value::Keyword(Keyword::Optional)) => {
mode = Mode::Optional;
}
(Mode::Required, Value::Keyword(Keyword::Rest)) => {
mode = Mode::Rest;
}
(Mode::Optional, Value::Keyword(Keyword::Rest)) => {
if optional_arguments.is_empty() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::AtLeastOneArgument,
ExpectedWhere::AfterKeyword(Keyword::Optional),
),
});
}
mode = Mode::Rest;
}
_ => {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ArgumentSpec,
ExpectedWhere::InArgumentList,
),
});
}
}
value = cdr;
}
match mode {
Mode::Required => (),
Mode::Optional => {
if optional_arguments.is_empty() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::AtLeastOneArgument,
ExpectedWhere::AfterKeyword(Keyword::Optional),
),
});
}
}
Mode::Rest => {
if rest_argument.is_none() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ExactlyOneArgument,
ExpectedWhere::AfterKeyword(Keyword::Rest),
),
});
}
}
}
Ok(Self {
required_arguments,
optional_arguments,
rest_argument,
})
}
}
impl FunctionBody {
pub(super) fn parse(
mut value: &Value,
input: &Value,
inside: Keyword,
) -> Result<Self, ParseError> {
let mut expressions = vec![];
while !value.is_nil() {
let Value::Cons(cons) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::AfterArgumentList(inside),
),
});
};
let ConsCell(car, cdr) = cons.as_ref();
let expression = Expression::parse_inner(car);
expressions.push(expression);
value = cdr;
}
let Some(tail) = expressions.pop() else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::AfterArgumentList(inside),
),
});
};
Ok(Self {
head: expressions,
tail,
})
}
}
impl PrognExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let body = FunctionBody::parse(value, input, Keyword::Progn)?;
Ok(Self { body })
}
}
impl LambdaExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let Value::Cons(value) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ArgumentList,
ExpectedWhere::AfterKeyword(Keyword::Lambda),
),
});
};
let ConsCell(car, cdr) = value.as_ref();
let signature = FunctionSignature::parse(car, input)?;
let (cdr, docstring) = maybe_docstring(cdr);
let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?;
Ok(Self {
docstring,
signature,
body,
})
}
}
impl DefunExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let Value::Cons(value) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InFunctionDefinition,
),
});
};
let ConsCell(identifier, cdr) = value.as_ref();
let Value::Identifier(identifier) = identifier else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Identifier,
ExpectedWhere::AfterKeyword(Keyword::Defun),
),
});
};
let Value::Cons(cdr) = cdr else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InFunctionDefinition,
),
});
};
let ConsCell(car, cdr) = cdr.as_ref();
let signature = FunctionSignature::parse(car, input)?;
let (cdr, docstring) = maybe_docstring(cdr);
let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?;
Ok(Self {
name: identifier.clone(),
docstring,
signature,
body,
})
}
}
impl ReturnExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
match value {
Value::Cons(cons) => {
let ConsCell(car, cdr) = cons.as_ref();
if !cdr.is_nil() {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::extraneous(cdr),
});
}
let expression = Expression::parse_inner(car);
Ok(Self { expression })
}
Value::Nil => Ok(Self {
expression: Rc::new(Expression::Nil),
}),
_ => Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::AfterKeyword(Keyword::Return),
),
}),
}
}
}
impl CollectErrors<ParseError> for FunctionBody {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
let mut r = false;
for expr in self.head.iter() {
r |= expr.collect_errors(errors);
}
r |= self.tail.collect_errors(errors);
r
}
}
impl CollectErrors<ParseError> for LambdaExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.body.collect_errors(errors)
}
}
impl CollectErrors<ParseError> for DefunExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.body.collect_errors(errors)
}
}
impl CollectErrors<ParseError> for PrognExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.body.collect_errors(errors)
}
}
impl CollectErrors<ParseError> for ReturnExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.expression.collect_errors(errors)
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use crate::{
compile::{
function::FunctionSignature,
syntax::{
CallExpression, ExpectedWhat, ExpectedWhere, Expression, FunctionBody,
LambdaExpression, ParseError, ParseErrorKind,
},
},
vm::value::{Keyword, Value},
};
#[test]
fn test_parse_lambda() {
let args = Value::list_or_nil([
Value::Identifier("a".into()),
Value::Keyword(Keyword::Optional),
Value::Identifier("b".into()),
Value::Keyword(Keyword::Rest),
Value::Identifier("c".into()),
]);
let body = Value::list_or_nil([
Value::Identifier("+".into()),
Value::Identifier("a".into()),
Value::Number(1.into()),
]);
let lambda = Value::Keyword(Keyword::Lambda).cons(args.cons(body.cons(Value::Nil)));
let expr = Expression::parse(&lambda).unwrap();
assert_eq!(
expr.as_ref(),
&Expression::Lambda(LambdaExpression {
docstring: None,
signature: FunctionSignature {
required_arguments: vec!["a".into()],
optional_arguments: vec!["b".into()],
rest_argument: Some("c".into())
},
body: FunctionBody {
head: vec![],
tail: Expression::Call(CallExpression {
callee: Expression::Identifier("+".into()).into(),
arguments: vec![
Rc::new(Expression::Identifier("a".into())),
Rc::new(Expression::IntegerLiteral(1.into()))
]
})
.into()
}
})
);
// syntax errors
let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda)]);
let e = Expression::parse(&lambda).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: lambda,
error: ParseErrorKind::Expected(
ExpectedWhat::ArgumentList,
ExpectedWhere::AfterKeyword(Keyword::Lambda)
)
}]
);
let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda), Value::Nil]);
let e = Expression::parse(&lambda).unwrap_err();
assert_eq!(
e,
vec![ParseError {
input: lambda,
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::AfterArgumentList(Keyword::Lambda)
)
}]
);
}
#[test]
fn test_parse_function_body() {
let v = Value::list_or_nil([Value::Number(1.into())]);
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap();
assert_eq!(
e,
FunctionBody {
head: vec![],
tail: Rc::new(Expression::IntegerLiteral(1.into()))
}
);
let v = Value::list_or_nil([
Value::Number(1.into()),
Value::Number(2.into()),
Value::Number(3.into()),
]);
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap();
assert_eq!(
e,
FunctionBody {
head: vec![
Rc::new(Expression::IntegerLiteral(1.into())),
Rc::new(Expression::IntegerLiteral(2.into()))
],
tail: Rc::new(Expression::IntegerLiteral(3.into()))
}
);
// syntax error
let v = Value::list_or_nil([]);
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap_err();
assert_eq!(
e,
ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::Expression,
ExpectedWhere::AfterArgumentList(Keyword::Lambda)
),
}
);
let v = Value::Identifier("a".into()).cons(Value::Identifier("b".into()));
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap_err();
assert_eq!(
e,
ParseError {
input: v,
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::AfterArgumentList(Keyword::Lambda)
),
}
);
}
#[test]
fn test_parse_function_signature() {
let args = Value::list_or_nil([]);
let v = FunctionSignature::parse(&args, &args).unwrap();
assert_eq!(
v,
FunctionSignature {
required_arguments: vec![],
optional_arguments: vec![],
rest_argument: None
}
);
let args =
Value::list_or_nil([Value::Identifier("a".into()), Value::Identifier("b".into())]);
let v = FunctionSignature::parse(&args, &args).unwrap();
assert_eq!(
v,
FunctionSignature {
required_arguments: vec!["a".into(), "b".into()],
optional_arguments: vec![],
rest_argument: None
}
);
let args = Value::list_or_nil([
Value::Identifier("a".into()),
Value::Keyword(Keyword::Optional),
Value::Identifier("b".into()),
]);
let v = FunctionSignature::parse(&args, &args).unwrap();
assert_eq!(
v,
FunctionSignature {
required_arguments: vec!["a".into()],
optional_arguments: vec!["b".into()],
rest_argument: None
}
);
let args = Value::list_or_nil([
Value::Identifier("a".into()),
Value::Keyword(Keyword::Rest),
Value::Identifier("b".into()),
]);
let v = FunctionSignature::parse(&args, &args).unwrap();
assert_eq!(
v,
FunctionSignature {
required_arguments: vec!["a".into()],
optional_arguments: vec![],
rest_argument: Some("b".into())
}
);
let args =
Value::list_or_nil([Value::Keyword(Keyword::Rest), Value::Identifier("c".into())]);
let v = FunctionSignature::parse(&args, &args).unwrap();
assert_eq!(
v,
FunctionSignature {
required_arguments: vec![],
optional_arguments: vec![],
rest_argument: Some("c".into())
}
);
let args = Value::list_or_nil([
Value::Keyword(Keyword::Optional),
Value::Identifier("b".into()),
Value::Keyword(Keyword::Rest),
Value::Identifier("c".into()),
]);
let v = FunctionSignature::parse(&args, &args).unwrap();
assert_eq!(
v,
FunctionSignature {
required_arguments: vec![],
optional_arguments: vec!["b".into()],
rest_argument: Some("c".into())
}
);
let args = Value::list_or_nil([
Value::Identifier("a".into()),
Value::Keyword(Keyword::Rest),
Value::Identifier("c".into()),
]);
let v = FunctionSignature::parse(&args, &args).unwrap();
assert_eq!(
v,
FunctionSignature {
required_arguments: vec!["a".into()],
optional_arguments: vec![],
rest_argument: Some("c".into())
}
);
// syntax errors
let args = Value::list_or_nil([Value::Keyword(Keyword::Optional)]);
let e = FunctionSignature::parse(&args, &args).unwrap_err();
assert_eq!(
e,
ParseError {
input: args,
error: ParseErrorKind::Expected(
ExpectedWhat::AtLeastOneArgument,
ExpectedWhere::AfterKeyword(Keyword::Optional)
)
}
);
let args = Value::list_or_nil([
Value::Keyword(Keyword::Rest),
Value::Identifier("a".into()),
Value::Identifier("b".into()),
]);
let e = FunctionSignature::parse(&args, &args).unwrap_err();
assert_eq!(
e,
ParseError {
input: args,
error: ParseErrorKind::Expected(
ExpectedWhat::ExactlyOneArgument,
ExpectedWhere::AfterKeyword(Keyword::Rest)
)
}
);
let args = Value::list_or_nil([Value::Boolean(false.into())]);
let e = FunctionSignature::parse(&args, &args).unwrap_err();
assert_eq!(
e,
ParseError {
input: args,
error: ParseErrorKind::Expected(
ExpectedWhat::ArgumentSpec,
ExpectedWhere::InArgumentList
)
}
);
}
}
@@ -0,0 +1,59 @@
use std::rc::Rc;
use crate::{
compile::syntax::{
CollectErrors, Expression, FunctionBody,
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
},
vm::value::{ConsCell, Keyword, Value},
};
#[derive(Debug, PartialEq)]
pub struct WhileExpression {
pub condition: Rc<Expression>,
pub body: FunctionBody,
}
#[derive(Debug, PartialEq)]
pub struct LoopExpression {
pub body: FunctionBody,
}
impl WhileExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let Value::Cons(cons) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::AfterKeyword(Keyword::While),
),
});
};
let ConsCell(car, cdr) = cons.as_ref();
let condition = Expression::parse_inner(car);
let body = FunctionBody::parse(cdr, input, Keyword::While)?;
Ok(Self { condition, body })
}
}
impl LoopExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let body = FunctionBody::parse(value, input, Keyword::Loop)?;
Ok(Self { body })
}
}
impl CollectErrors<ParseError> for WhileExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
let a = self.condition.collect_errors(errors);
let b = self.body.collect_errors(errors);
a | b
}
}
impl CollectErrors<ParseError> for LoopExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.body.collect_errors(errors)
}
}
@@ -0,0 +1,71 @@
use crate::{
compile::{
function::FunctionSignature,
syntax::{
CollectErrors, FunctionBody,
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
maybe_docstring,
},
},
vm::value::{ConsCell, IdentifierValue, Keyword, StringValue, Value},
};
#[derive(Debug, PartialEq)]
pub struct DefmacroExpression {
pub name: IdentifierValue,
pub docstring: Option<StringValue>,
pub signature: FunctionSignature,
pub body: FunctionBody,
}
impl DefmacroExpression {
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
let Value::Cons(value) = value else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InFunctionDefinition,
),
});
};
let ConsCell(identifier, cdr) = value.as_ref();
let Value::Identifier(identifier) = identifier else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::Identifier,
ExpectedWhere::AfterKeyword(Keyword::Defun),
),
});
};
let Value::Cons(cdr) = cdr else {
return Err(ParseError {
input: input.clone(),
error: ParseErrorKind::Expected(
ExpectedWhat::ProperList,
ExpectedWhere::InFunctionDefinition,
),
});
};
let ConsCell(car, cdr) = cdr.as_ref();
let signature = FunctionSignature::parse(car, input)?;
let (cdr, docstring) = maybe_docstring(cdr);
let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?;
Ok(Self {
name: identifier.clone(),
docstring,
signature,
body,
})
}
}
impl CollectErrors<ParseError> for DefmacroExpression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
self.body.collect_errors(errors)
}
}
@@ -0,0 +1,235 @@
use std::rc::Rc;
use crate::vm::value::{
BooleanValue, ConsCell, IdentifierValue, Keyword, NumberValue, StringValue, Value, Vector,
};
mod binding;
mod call;
mod condition;
mod error;
mod function;
mod loops;
mod macros;
pub use binding::*;
pub use call::*;
pub use condition::*;
pub use error::*;
pub use function::*;
pub use loops::*;
pub use macros::*;
#[derive(Debug, PartialEq)]
pub enum Expression {
Nil,
BooleanLiteral(BooleanValue),
StringLiteral(StringValue),
IntegerLiteral(NumberValue),
Identifier(IdentifierValue),
Lambda(LambdaExpression),
Defun(DefunExpression),
Call(CallExpression),
If(IfExpression),
Cond(CondExpression),
Setq(SetqExpression),
Let(LetExpression),
Defmacro(DefmacroExpression),
SyntaxError(ParseError),
Quote(Rc<Value>),
While(WhileExpression),
Loop(LoopExpression),
Progn(PrognExpression),
Vector(Rc<Vector>),
Return(ReturnExpression),
Break,
Continue,
}
impl Expression {
fn map_or<T, F: FnOnce(T) -> Self>(result: Result<T, ParseError>, map: F) -> Rc<Self> {
match result {
Ok(r) => Rc::new(map(r)),
Err(error) => Rc::new(Self::SyntaxError(error)),
}
}
pub fn parse(value: &Value) -> Result<Rc<Self>, Vec<ParseError>> {
let inner = Self::parse_inner(value);
let mut errors = vec![];
if inner.collect_errors(&mut errors) {
Err(errors)
} else {
Ok(inner)
}
}
fn parse_inner(value: &Value) -> Rc<Self> {
match value {
Value::Vector(vector) => Rc::new(Self::Vector(vector.clone())),
Value::HashTable(_) => todo!(),
Value::String(value) => Rc::new(Self::StringLiteral(value.clone())),
Value::Quasi(_value) => todo!("{value}"),
Value::Unquote(_value) => todo!("Unquote {_value}"),
Value::UnquoteSplice(_value) => todo!("UnquoteSplice {_value}"),
Value::Quote(value) => Rc::new(Self::Quote(value.clone())),
Value::Nil => Rc::new(Self::Nil),
&Value::Number(value) => Rc::new(Self::IntegerLiteral(value)),
&Value::Boolean(value) => Rc::new(Self::BooleanLiteral(value)),
Value::Identifier(value) => Rc::new(Self::Identifier(value.clone())),
Value::Cons(cons) => {
let ConsCell(car, cdr) = cons.as_ref();
match car {
Value::Keyword(Keyword::Lambda) => {
Self::map_or(LambdaExpression::parse(cdr, value), Expression::Lambda)
}
Value::Keyword(Keyword::If) => {
Self::map_or(IfExpression::parse(cdr, value), Expression::If)
}
Value::Keyword(Keyword::Cond) => {
Self::map_or(CondExpression::parse(cdr, value), Expression::Cond)
}
Value::Keyword(Keyword::Defun) => {
Self::map_or(DefunExpression::parse(cdr, value), Expression::Defun)
}
Value::Keyword(keyword @ (Keyword::Let | Keyword::LetStar)) => {
Self::map_or(LetExpression::parse(cdr, value, *keyword), Expression::Let)
}
Value::Keyword(Keyword::Setq) => {
Self::map_or(SetqExpression::parse(cdr, value), Expression::Setq)
}
Value::Keyword(Keyword::Defmacro) => {
Self::map_or(DefmacroExpression::parse(cdr, value), Expression::Defmacro)
}
Value::Keyword(Keyword::Quote) => {
let value = match cdr {
Value::Cons(cons) => cons.0.clone(),
_ => Value::Nil,
};
Rc::new(Self::Quote(value.into()))
}
Value::Keyword(Keyword::Progn) => {
Self::map_or(PrognExpression::parse(cdr, value), Expression::Progn)
}
Value::Keyword(Keyword::Loop) => {
Self::map_or(LoopExpression::parse(cdr, value), Expression::Loop)
}
Value::Keyword(Keyword::While) => {
Self::map_or(WhileExpression::parse(cdr, value), Expression::While)
}
Value::Keyword(Keyword::Break) => Rc::new(Self::Break),
Value::Keyword(Keyword::Continue) => Rc::new(Self::Continue),
Value::Keyword(Keyword::Return) => {
Self::map_or(ReturnExpression::parse(cdr, value), Expression::Return)
}
_ => Self::map_or(CallExpression::parse(cons, value), Expression::Call),
}
}
Value::Keyword(keyword) => todo!("Error here: keyword {keyword}"),
Value::Closure(_) => todo!("Error here"),
Value::Function(_) => todo!("Error here"),
Value::NativeFunction(_) => todo!("Error here"),
Value::NativeValue(_) => todo!("Error here"),
}
}
}
impl CollectErrors<ParseError> for Expression {
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
match self {
Self::SyntaxError(error) => {
errors.push(error.clone());
true
}
Self::If(condition) => condition.collect_errors(errors),
Self::Cond(condition) => condition.collect_errors(errors),
Self::Lambda(lambda) => lambda.collect_errors(errors),
Self::Defun(defun) => defun.collect_errors(errors),
Self::Call(call) => call.collect_errors(errors),
Self::Let(let_) => let_.collect_errors(errors),
Self::Setq(setq) => setq.collect_errors(errors),
Self::Defmacro(defmacro) => defmacro.collect_errors(errors),
Self::While(cloop) => cloop.collect_errors(errors),
Self::Loop(cloop) => cloop.collect_errors(errors),
Self::Progn(progn) => progn.collect_errors(errors),
Self::Return(return_) => return_.collect_errors(errors),
Self::Nil
| Self::Vector(_)
| Self::Break
| Self::Continue
| Self::IntegerLiteral(_)
| Self::Identifier(_)
| Self::BooleanLiteral(_)
| Self::Quote(_)
| Self::StringLiteral(_) => false,
}
}
}
fn maybe_docstring(input: &Value) -> (&Value, Option<StringValue>) {
if let Some((Value::String(docstring), cdr)) = input.uncons_ref() {
(cdr, Some(docstring.clone()))
} else {
(input, None)
}
}
#[cfg(test)]
mod tests {
use crate::{
compile::syntax::{ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind},
vm::value::{Keyword, Value},
};
#[test]
fn test_parse_basic() {
let v = Value::Nil;
let e = Expression::parse(&v).unwrap();
assert_eq!(e.as_ref(), &Expression::Nil);
let v = Value::Number(1234.into());
let e = Expression::parse(&v).unwrap();
assert_eq!(e.as_ref(), &Expression::IntegerLiteral(1234.into()));
let v = Value::Boolean(false.into());
let e = Expression::parse(&v).unwrap();
assert_eq!(e.as_ref(), &Expression::BooleanLiteral(false.into()));
let v = Value::Identifier("a".into());
let e = Expression::parse(&v).unwrap();
assert_eq!(e.as_ref(), &Expression::Identifier("a".into()));
}
#[test]
fn test_nested_syntax_error() {
let inner_if = Value::list_or_nil([Value::Keyword(Keyword::If)]);
let inner_lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda)]);
let outer_if = Value::list_or_nil([
Value::Keyword(Keyword::If),
inner_if.clone(),
inner_lambda.clone(),
Value::Number(3.into()),
]);
let e = Expression::parse(&outer_if).unwrap_err();
assert_eq!(
e,
vec![
ParseError {
input: inner_if,
error: ParseErrorKind::Expected(
ExpectedWhat::Condition,
ExpectedWhere::AfterKeyword(Keyword::If)
)
},
ParseError {
input: inner_lambda,
error: ParseErrorKind::Expected(
ExpectedWhat::ArgumentList,
ExpectedWhere::AfterKeyword(Keyword::Lambda)
)
}
]
);
}
}
@@ -0,0 +1,6 @@
use crate::vm::value::Value;
#[derive(Debug, PartialEq)]
pub struct VectorExpression {
pub elements: Vec<Value>,
}
+58
View File
@@ -0,0 +1,58 @@
use std::rc::Rc;
use crate::vm::{
instruction::{LocalId, MathInstruction},
value::{Value, ValueString},
};
#[derive(Debug, PartialEq)]
pub enum CompileValue {
Integer(i64),
String(ValueString),
Boolean(bool),
LocalFunction(u32, usize),
Local(LocalId),
Argument(usize),
Global(Rc<str>),
Quote(Rc<Value>),
Stack,
Nil,
}
// #[derive(Debug, Hash, PartialEq, Eq)]
// pub enum CompileConstant {
// Integer(i64),
// LocalFunction(u32, usize),
// Identifier(Rc<str>),
// String(ValueString),
// Value(Rc<Value>),
// }
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum BuiltinFunction {
Math(MathInstruction),
}
impl BuiltinFunction {
pub fn from_identifier(identifier: &str) -> Option<Self> {
match identifier {
"+" => Some(Self::Math(MathInstruction::Add)),
"-" => Some(Self::Math(MathInstruction::Sub)),
"*" => Some(Self::Math(MathInstruction::Mul)),
"/" => Some(Self::Math(MathInstruction::Div)),
"%" => Some(Self::Math(MathInstruction::Mod)),
"&" => Some(Self::Math(MathInstruction::BitwiseAnd)),
"|" => Some(Self::Math(MathInstruction::BitwiseOr)),
"^" => Some(Self::Math(MathInstruction::BitwiseXor)),
"&&" => Some(Self::Math(MathInstruction::And)),
"||" => Some(Self::Math(MathInstruction::Or)),
">" => Some(Self::Math(MathInstruction::Gt)),
">=" => Some(Self::Math(MathInstruction::Ge)),
"=" => Some(Self::Math(MathInstruction::Eq)),
"/=" => Some(Self::Math(MathInstruction::Ne)),
"<=" => Some(Self::Math(MathInstruction::Le)),
"<" => Some(Self::Math(MathInstruction::Lt)),
_ => None,
}
}
}
+194
View File
@@ -0,0 +1,194 @@
use std::{error::Error as StdError, fmt, io, rc::Rc};
use crate::{
compile::{CompileError, syntax::ParseError},
vm::{
Value,
instruction::{Instruction, InstructionDecodeError},
value::{BytecodeFunction, IdentifierValue, StringValue},
},
};
#[derive(Debug, thiserror::Error)]
#[error("{error}")]
pub struct MachineErrorAt {
// TODO ip where the error occured
pub error: MachineError,
pub location: Option<MachineErrorLocation>,
}
#[derive(Debug, PartialEq)]
pub struct MachineErrorLocation {
pub function: Rc<BytecodeFunction>,
pub offset: usize,
}
#[derive(Debug, PartialEq, thiserror::Error)]
#[error("expected {expected}, got {got}")]
pub struct ValueConversionError {
pub expected: String,
pub got: Value,
}
#[derive(Debug, thiserror::Error)]
pub enum ReadError {
#[error("{0}")]
Lexical(nom::Err<nom::error::Error<String>, nom::error::Error<String>>),
#[error("{0:?}")]
Parse(Vec<ParseError>),
#[error("{0}")]
Io(io::Error),
}
#[derive(Debug, PartialEq)]
pub struct ArgumentCountError {
pub function: Rc<BytecodeFunction>,
pub actual: usize,
}
#[derive(Debug, thiserror::Error)]
pub enum MachineError {
// VM itself
#[error("instruction pointer is undefined")]
InstructionPointerUndefined,
#[error("instruction pointer is out of bounds")]
InstructionPointerOutOfBounds,
#[error("instruction fetch failed")]
InstructionFetch,
#[error("instruction decode error: {0}")]
InstructionDecode(#[from] InstructionDecodeError),
#[error("data stack overflowed")]
DataStackOverflow,
#[error("data stack underflowed")]
DataStackUnderflow,
#[error("call stack overflowed")]
CallStackOverflow,
#[error("call stack underflowed")]
CallStackUnderflow,
#[error("undefined upvalue reference")]
UndefinedUpvalueReference,
#[error("undefined local reference")]
UndefinedLocalReference,
#[error("undefined constant reference")]
UndefinedConstantReference,
#[error("unbound identifier reference: {0}")]
UnboundIdentifier(IdentifierValue),
#[error("invalid {0} argument: {1}")]
InvalidInstructionArgument(Instruction, ValueConversionError),
#[error("invalid branch target: {0}{1:+}")]
InvalidBranchTarget(usize, isize),
#[error("GET_TEMP with an empty temp register")]
TempRegisterEmpty,
#[error("{0}")]
ArgumentCount(ArgumentCountError),
// Syntax+evaluation
// #[error("evaluation error: {0}")]
// EvaluationError(EvalError),
#[error("invalid argument count")]
InvalidArgumentCount,
#[error("aborted: {0}")]
Abort(Rc<str>),
#[error("{0}: {1}")]
LoadError(StringValue, Box<MachineErrorAt>),
#[error("{0}")]
Custom(Box<dyn StdError>),
#[error("code-raised error: {0}")]
Raised(String),
#[error("value conversion error: {0}")]
ValueConversion(#[from] ValueConversionError),
#[error("syntax error: {0}")]
Read(ReadError),
#[error("compile error: {0}")]
Compile(#[from] CompileError),
#[error("value cannot be used as a hashmap key: {0}")]
InvalidHashTableKey(Value),
}
impl MachineError {
pub fn at(self, location: Option<MachineErrorLocation>) -> MachineErrorAt {
MachineErrorAt {
error: self,
location,
}
}
pub fn at_unknown(self) -> MachineErrorAt {
self.at(None)
}
}
impl MachineErrorAt {
pub fn at_unknown(error: MachineError) -> Self {
Self::at(error, None)
}
pub fn at(error: MachineError, location: Option<MachineErrorLocation>) -> Self {
Self { error, location }
}
}
impl MachineErrorLocation {
pub fn disassemble_chunk(&self, address: usize, before: usize, after: usize, arrow: bool) {
self.function.disassemble(address, before, after, arrow);
}
}
impl fmt::Display for MachineErrorLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:p}+{}", self.function, self.offset)
}
}
impl PartialEq for ReadError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Lexical(a), Self::Lexical(b)) => a == b,
(Self::Io(a), Self::Io(b)) => a.raw_os_error() == b.raw_os_error(),
_ => false,
}
}
}
impl fmt::Display for ArgumentCountError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let too_few = self.actual < self.function.required_count;
write!(
f,
"too {} arguments for function {}: expected ",
if too_few { "few" } else { "many" },
self.function
)?;
if self.function.has_rest {
let singular = self.function.required_count == 1;
write!(
f,
"at least {} argument{}",
self.function.required_count,
if singular { "" } else { "s" }
)?;
} else if self.function.optional_count > 0 {
write!(
f,
"{}-{} arguments",
self.function.required_count,
self.function.required_count + self.function.optional_count
)?;
} else {
let singular = self.function.required_count == 1;
write!(
f,
"{} argument{}",
self.function.required_count,
if singular { "" } else { "s" }
)?;
}
write!(f, ", got {}", self.actual)?;
Ok(())
}
}
+20
View File
@@ -0,0 +1,20 @@
#![feature(
if_let_guard,
coverage_attribute,
debug_closure_helpers,
iter_next_chunk,
exact_size_is_empty,
exact_div
)]
//
// pub mod error;
pub mod parse;
pub mod read;
#[macro_use]
pub mod util;
pub mod compile;
pub mod error;
pub mod vm;
+233
View File
@@ -0,0 +1,233 @@
use std::{
fs::File,
io::{self, BufReader},
path::{Path, PathBuf},
process::ExitCode,
rc::Rc,
str::FromStr,
};
use clap::Parser;
use lysp::{
compile::{CompileOptions, syntax::ParseError},
error::{MachineError, MachineErrorAt},
read::{InteractiveReader, ModuleReader, read},
util::Either,
vm::{
env::Environment,
machine::Machine,
prelude,
value::{ClosureValue, Value},
},
};
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
enum Error {
Machine(#[from] MachineErrorAt),
Io(#[from] io::Error),
#[error("Error already printed")]
Printed,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Trace {
Compile,
Execute,
Call,
Return,
Stack,
Macro,
}
impl FromStr for Trace {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"compile" => Ok(Self::Compile),
"execute" => Ok(Self::Execute),
"call" => Ok(Self::Call),
"return" => Ok(Self::Return),
"stack" => Ok(Self::Stack),
"macro" => Ok(Self::Macro),
_ => Err(format!("Unknown trace flag: {s:?}")),
}
}
}
#[derive(Debug, Parser)]
struct Args {
#[clap(
short = 'T',
long = "trace",
help = "Enable tracing of execution/compilation steps"
)]
trace: Vec<Trace>,
module: Option<PathBuf>,
arguments: Vec<String>,
}
fn print_syntax_errors(errors: &[ParseError]) {
if errors.len() > 1 {
eprintln!("Syntax errors:");
} else {
eprintln!("Syntax error:");
}
eprintln!();
for error in errors {
let ParseError { input, error } = error;
eprintln!(" * In expression:");
eprintln!();
eprintln!(" {input}");
eprintln!();
eprintln!(" :: {error}");
}
}
fn handle_eval_error(value: Option<&Value>, input: MachineErrorAt) -> Error {
if let Some(value) = value {
eprintln!("Error in expression:");
eprintln!();
eprintln!(" {value}");
eprintln!();
}
match input.error {
MachineError::Compile(error) => {
eprintln!("Compilation error:");
eprintln!();
eprintln!(":: {error}");
}
MachineError::Read(error) => {
eprintln!("Syntax error:");
eprintln!();
eprintln!(":: {error}");
}
error => {
if let Some(location) = input.location {
eprintln!("At location {location}:");
location.disassemble_chunk(location.offset, 5, 1, true);
}
eprintln!(":: {error}");
}
}
Error::Printed
}
fn handle_module_error(input: Either<MachineErrorAt, Vec<ParseError>>) -> Error {
match input {
Either::Left(error) => handle_eval_error(None, error),
Either::Right(errors) => {
print_syntax_errors(&errors);
Error::Printed
}
}
}
fn eval(
options: &CompileOptions,
vm: &mut Machine,
env: &Rc<Environment>,
value: Value,
) -> Option<Value> {
let result = vm.evaluate_value(
options.clone(),
Some("interactive".into()),
env,
value.clone(),
);
match result {
Ok(r) => Some(r),
Err(error) => {
handle_eval_error(Some(&value), error);
None
}
}
}
fn run_interactive(
compile_options: &CompileOptions,
vm: &mut Machine,
env: &Rc<Environment>,
) -> Result<(), Error> {
let mut reader = InteractiveReader::new("> ", ">> ");
loop {
let value = match read(&mut reader, vm, env) {
Ok(Some(value)) => value,
Ok(None) => break,
Err(error) => {
eprintln!("{error}");
continue;
}
};
if let Some(value) = eval(compile_options, vm, env, value) {
println!("== {value}");
} else {
reader.reset();
}
}
Ok(())
}
fn run_module<P: AsRef<Path>>(
compile_options: &CompileOptions,
vm: &mut Machine,
env: &Rc<Environment>,
path: P,
) -> Result<(), Error> {
let path = path.as_ref();
let name = format!("{}", path.display());
let reader = BufReader::new(File::open(path)?);
let module_reader = ModuleReader::new(reader, path, vm.trace_macros);
let function = match module_reader.compile(Some(name.into()), compile_options, env) {
Ok(function) => function,
Err(error) => return Err(handle_module_error(error)),
};
let closure = ClosureValue {
function,
upvalues: vec![],
};
match vm.evaluate_closure(env, closure, 0) {
Ok(_) => Ok(()),
Err(error) => Err(handle_eval_error(None, error)),
}
}
fn main() -> ExitCode {
let args = Args::parse();
let mut vm = Machine::default();
let compile_options = CompileOptions {
trace_compile: args.trace.contains(&Trace::Compile),
};
vm.trace_instructions = args.trace.contains(&Trace::Execute);
vm.trace_calls = args.trace.contains(&Trace::Call);
vm.trace_returns = args.trace.contains(&Trace::Return);
vm.trace_stack = args.trace.contains(&Trace::Stack);
vm.trace_macros = args.trace.contains(&Trace::Macro);
let env = Rc::new(Environment::default());
prelude::load(&env);
let mut arguments = vec![];
if let Some(script) = args.module.as_ref() {
arguments.push(format!("{}", script.display()));
}
arguments.extend(args.arguments);
env.set_global_value(
"*args*",
Value::list_or_nil(arguments.into_iter().map(|arg| Value::String(arg.into()))),
)
.ok();
let result = match args.module.as_ref() {
Some(module) => run_module(&compile_options, &mut vm, &env, module),
None => run_interactive(&compile_options, &mut vm, &env),
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(Error::Printed) => ExitCode::FAILURE,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}
+589
View File
@@ -0,0 +1,589 @@
use std::rc::Rc;
use nom::{
AsChar, FindToken, IResult, Input, Parser,
branch::alt,
bytes::streaming::{is_not, tag},
character::{
anychar,
streaming::{char, one_of},
},
combinator::{map, map_res, opt, recognize, value, verify},
error::{Error, ErrorKind, FromExternalError, ParseError},
multi::{fold, fold_many1, many0},
sequence::{delimited, pair, preceded},
};
use crate::vm::value::{Keyword, NumberValue, Value, Vector};
struct IdentifierHead;
struct IdentifierTail;
impl FindToken<char> for IdentifierHead {
fn find_token(&self, token: char) -> bool {
token.is_alphabetic() || "~!@$%^&*-=_+<>?/|≠≥≤·:".contains(token)
}
}
impl FindToken<char> for IdentifierTail {
fn find_token(&self, token: char) -> bool {
token.is_alphanumeric() || "~!@$%^&*-=_+<>?/|≠≥≤·:".contains(token)
}
}
fn parse_sign(input: &str) -> IResult<&str, bool> {
map(
opt(alt((value(true, char('-')), value(false, char('+'))))),
|sign| sign.unwrap_or(false),
)
.parse(input)
}
fn parse_hex_digit(input: &str) -> IResult<&str, char> {
let (tail, ch) = anychar(input)?;
match ch {
'0'..='9' | 'a'..='f' | 'A'..='F' => Ok((tail, ch)),
_ if ch.is_alphabetic() => Err(nom::Err::Failure(Error::from_char(input, ch))),
_ => Err(nom::Err::Error(Error::from_char(input, ch))),
}
}
fn parse_dec_digit(input: &str) -> IResult<&str, char> {
let (tail, ch) = anychar(input)?;
match ch {
'0'..='9' => Ok((tail, ch)),
_ if ch.is_alphabetic() => Err(nom::Err::Failure(Error::from_char(input, ch))),
_ => Err(nom::Err::Error(Error::from_char(input, ch))),
}
}
fn parse_oct_digit(input: &str) -> IResult<&str, char> {
let (tail, ch) = anychar(input)?;
match ch {
'0'..='7' => Ok((tail, ch)),
_ if ch.is_alphanumeric() => Err(nom::Err::Failure(Error::from_char(input, ch))),
_ => Err(nom::Err::Error(Error::from_char(input, ch))),
}
}
struct OverflowError;
fn parse_integer_inner<I, E, P, D>(input: I, prefix: P, radix: u64, digit: D) -> IResult<I, u64, E>
where
I: Input,
E: ParseError<I>,
E: FromExternalError<I, OverflowError>,
P: Parser<I, Error = E>,
D: Parser<I, Output = char, Error = E>,
{
#[coverage(off)]
fn digit_to_u8(ch: char) -> u8 {
match ch {
'0'..='9' => ch as u8 - b'0',
'a'..='f' => ch as u8 - b'a' + 10,
'A'..='F' => ch as u8 - b'A' + 10,
_ => unreachable!(),
}
}
let (output, result) = preceded(
prefix,
fold_many1(
digit,
|| Ok(0u64),
move |acc, ch| match acc {
Ok(acc)
if let Some(acc) = acc
.checked_mul(radix)
.and_then(|acc| acc.checked_add(digit_to_u8(ch) as u64)) =>
{
Ok(acc)
}
Ok(_) => Err(OverflowError),
Err(e) => Err(e),
},
),
)
.parse(input.clone())?;
match result {
Ok(result) => Ok((output, result)),
Err(OverflowError) => Err(nom::Err::Failure(E::from_external_error(
input,
ErrorKind::Fold,
OverflowError,
))),
}
}
fn check_decimal(input: &str) -> IResult<&str, ()> {
let (_, ch) = anychar(input)?;
if ch.is_ascii_digit() {
Ok((input, ()))
} else {
Err(nom::Err::Error(Error::from_char(input, ch)))
}
}
fn parse_integer_oct(input: &str) -> IResult<&str, u64> {
parse_integer_inner(input, alt((tag("0o"), tag("0O"))), 8, parse_oct_digit)
}
fn parse_integer_dec(input: &str) -> IResult<&str, u64> {
parse_integer_inner(input, check_decimal, 10, parse_dec_digit)
}
fn parse_integer_hex(input: &str) -> IResult<&str, u64> {
parse_integer_inner(input, alt((tag("0x"), tag("0X"))), 16, parse_hex_digit)
}
fn parse_integer(input: &str) -> IResult<&str, Value> {
map_res(
(
parse_sign,
alt((parse_integer_hex, parse_integer_oct, parse_integer_dec)),
),
|(minus, value)| {
i64::try_from(value)
.map(|value| Value::Number((if minus { -value } else { value }).into()))
},
)
.parse(input)
}
fn parse_number(input: &str) -> IResult<&str, Value> {
alt((
value(Value::Number(NumberValue::nan()), tag("#NAN")),
value(Value::Number(NumberValue::nan()), tag("#NaN")),
value(Value::Number(NumberValue::nan()), tag("#nan")),
value(Value::Number(NumberValue::infinity()), tag("#inf")),
value(Value::Number(NumberValue::infinity()), tag("#INF")),
value(Value::Number(NumberValue::neg_infinity()), tag("-#inf")),
value(Value::Number(NumberValue::neg_infinity()), tag("-#INF")),
parse_integer,
))
.parse(input)
}
fn parse_identifier(input: &str) -> IResult<&str, &str> {
recognize(preceded(
one_of(IdentifierHead),
many0(one_of(IdentifierTail)),
))
.parse(input)
}
fn parse_identifier_or_keyword_or_nil(input: &str) -> IResult<&str, Value> {
map(parse_identifier, |ident| match ident {
"NIL" | "nil" => Value::Nil,
_ if let Some(keyword) = Keyword::parse(ident) => Value::Keyword(keyword),
_ => Value::Identifier(ident.into()),
})
.parse(input)
}
fn parse_list_or_nil(input: &str) -> IResult<&str, Value> {
let list_parens = delimited(
char('('),
many0(preceded(skip_comment_and_whitespace, parse_value)),
preceded(skip_comment_and_whitespace, char(')')),
);
let list_brackets = delimited(
char('['),
many0(preceded(skip_comment_and_whitespace, parse_value)),
preceded(skip_comment_and_whitespace, char(']')),
);
alt((list_parens, list_brackets))
.map(Value::list_or_nil)
.parse(input)
}
fn parse_boolean(input: &str) -> IResult<&str, Value> {
alt((
value(true, tag("#t")),
value(true, tag("#T")),
value(false, tag("#f")),
value(false, tag("#F")),
))
.map(Into::into)
.parse(input)
}
enum StringSegment<'a> {
Literal(&'a str),
Escape(char),
}
fn parse_literal_string_segment(input: &str) -> IResult<&str, &str> {
verify(is_not("\"\\"), |s: &str| !s.is_empty()).parse(input)
}
fn parse_escaped_char(input: &str) -> IResult<&str, char> {
preceded(
char('\\'),
alt((
value('\n', char('n')),
value('\r', char('r')),
value('\t', char('t')),
value('\\', char('\\')),
value('"', char('"')),
)),
)
.parse(input)
}
fn parse_string_segment(input: &str) -> IResult<&str, StringSegment<'_>> {
alt((
map(parse_literal_string_segment, StringSegment::Literal),
map(parse_escaped_char, StringSegment::Escape),
))
.parse(input)
}
fn parse_string(input: &str) -> IResult<&str, Value> {
delimited(
char('"'),
fold(
0..,
parse_string_segment,
String::new,
|mut string, segment| {
match segment {
StringSegment::Literal(s) => string.push_str(s),
StringSegment::Escape(c) => string.push(c),
};
string
},
),
char('"'),
)
.map(|s| Value::String(s.into()))
.parse(input)
}
pub fn skip_comment_and_whitespace(mut input: &str) -> IResult<&str, ()> {
let mut in_comment = false;
while let Some(ch) = input.chars().next() {
let next = input.ceil_char_boundary(1);
if next >= input.len() {
break;
}
if ch == ';' {
in_comment = true;
}
if in_comment {
if ch.is_newline() {
in_comment = false;
}
input = &input[next..];
continue;
} else if ch.is_whitespace() || ch.is_newline() {
input = &input[next..];
continue;
}
break;
}
input = input.trim_start();
Ok((input, ()))
}
fn parse_quasi(input: &str) -> IResult<&str, Value> {
preceded(char('`'), parse_value)
.map(Rc::new)
.map(Value::Quasi)
.parse(input)
}
fn parse_unquote(input: &str) -> IResult<&str, Value> {
preceded(char(','), parse_value)
.map(Rc::new)
.map(Value::Unquote)
.parse(input)
}
fn parse_unquote_splice(input: &str) -> IResult<&str, Value> {
preceded(tag(",@"), parse_value)
.map(Rc::new)
.map(Value::UnquoteSplice)
.parse(input)
}
fn parse_quote(input: &str) -> IResult<&str, Value> {
preceded(char('\''), parse_value)
.map(Rc::new)
.map(Value::Quote)
.parse(input)
}
fn parse_vector(input: &str) -> IResult<&str, Value> {
delimited(
tag("#["),
many0(preceded(skip_comment_and_whitespace, parse_value)),
preceded(skip_comment_and_whitespace, char(']')),
)
.map(Vector::from_iter)
.map(Into::into)
.map(Value::Vector)
.parse(input)
}
fn parse_dotted_cons(input: &str) -> IResult<&str, Value> {
delimited(
char('('),
pair(
preceded(skip_comment_and_whitespace, parse_value),
preceded(
pair(skip_comment_and_whitespace, char('.')),
preceded(skip_comment_and_whitespace, parse_value),
),
),
preceded(skip_comment_and_whitespace, char(')')),
)
.map(|(a, b)| a.cons(b))
.parse(input)
}
pub fn parse_value(input: &str) -> IResult<&str, Value> {
alt((
parse_dotted_cons,
parse_list_or_nil,
parse_vector,
parse_boolean,
parse_unquote_splice,
parse_quote,
parse_quasi,
parse_unquote,
parse_number,
parse_string,
parse_identifier_or_keyword_or_nil,
))
.parse(input)
}
#[cfg(test)]
mod tests {
use nom::error::{Error, FromExternalError, ParseError};
use crate::{
parse::{
OverflowError, parse_boolean, parse_dotted_cons, parse_identifier,
parse_identifier_or_keyword_or_nil, parse_integer, parse_integer_dec,
parse_integer_hex, parse_integer_oct, parse_list_or_nil, parse_value,
},
vm::value::{Keyword, Value},
};
#[test]
fn test_integer_dec() {
let (r, v) = parse_integer_dec("1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, 1234);
let (r, v) = parse_integer_dec("18446744073709551615\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, u64::MAX);
// illegal digit
let e = parse_integer_dec("1234A").unwrap_err();
assert_eq!(e, nom::Err::Failure(Error::from_char("A", 'A')));
// overflow
let e = parse_integer_dec("9999999999999999999999999").unwrap_err();
assert_eq!(
e,
nom::Err::Failure(Error::from_external_error(
"9999999999999999999999999",
nom::error::ErrorKind::Fold,
OverflowError
))
);
}
#[test]
fn test_integer_oct() {
let (r, v) = parse_integer_oct("0o1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, 0o1234);
let (r, v) = parse_integer_oct("0O1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, 0o1234);
// illegal digit
let e = parse_integer_oct("0o12349").unwrap_err();
assert_eq!(e, nom::Err::Failure(Error::from_char("9", '9')));
}
#[test]
fn test_integer_hex() {
let (r, v) = parse_integer_hex("0x123Fa4\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, 0x123FA4);
let (r, v) = parse_integer_hex("0X123Fa4\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, 0x123FA4);
// illegal digit
let e = parse_integer_hex("0x1AG").unwrap_err();
assert_eq!(e, nom::Err::Failure(Error::from_char("G", 'G')));
}
#[test]
fn test_integer() {
// Dec path
let (r, v) = parse_integer("1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number(1234.into()));
let (r, v) = parse_integer("+1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number(1234.into()));
let (r, v) = parse_integer("-1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number((-1234).into()));
// Oct path
let (r, v) = parse_integer("0o1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number(0o1234.into()));
let (r, v) = parse_integer("+0O1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number(0o1234.into()));
let (r, v) = parse_integer("-0o1234\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number((-0o1234).into()));
// Hex path
let (r, v) = parse_integer("0x1234AF\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number(0x1234AF.into()));
let (r, v) = parse_integer("+0X1234AF\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number(0x1234AF.into()));
let (r, v) = parse_integer("-0x1234AF\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number((-0x1234AF).into()));
// Illegal path
let e = parse_integer("0x12V34").unwrap_err();
assert_eq!(e, nom::Err::Failure(Error::from_char("V34", 'V')));
let e = parse_integer("-0X12V34").unwrap_err();
assert_eq!(e, nom::Err::Failure(Error::from_char("V34", 'V')));
let e = parse_integer("-0o81").unwrap_err();
assert_eq!(e, nom::Err::Failure(Error::from_char("81", '8')));
}
#[test]
fn test_identifier() {
let (r, v) = parse_identifier("+\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, "+");
let (r, v) = parse_identifier("a-1\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, "a-1");
}
#[test]
fn test_identifier_or_keyword_or_nil() {
let (r, v) = parse_identifier_or_keyword_or_nil("+\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Identifier("+".into()));
let (r, v) = parse_identifier_or_keyword_or_nil("lambda\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Keyword(Keyword::Lambda));
let (r, v) = parse_identifier_or_keyword_or_nil("NIL\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Nil);
}
#[test]
fn test_list_or_nil() {
let (r, v) = parse_list_or_nil("()\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Nil);
let (r, v) = parse_list_or_nil("( )\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Nil);
let (r, v) = parse_list_or_nil("( a b -1 )\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(
v,
Value::list_or_nil([
Value::Identifier("a".into()),
Value::Identifier("b".into()),
Value::Number((-1).into()),
])
);
}
#[test]
fn test_boolean() {
let (r, v) = parse_boolean("#t\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Boolean(true.into()));
let (r, v) = parse_boolean("#T\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Boolean(true.into()));
let (r, v) = parse_boolean("#F\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Boolean(false.into()));
let (r, v) = parse_boolean("#F\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Boolean(false.into()));
}
#[test]
fn test_dotted_pair() {
let (r, v) = parse_dotted_cons("(a.b)").unwrap();
assert_eq!(r, "");
assert_eq!(
v,
Value::Identifier("a".into()).cons(Value::Identifier("b".into()))
);
let (r, v) = parse_dotted_cons("(a .(b. ( c . (d.NIL ) )))").unwrap();
assert_eq!(r, "");
assert_eq!(
v,
Value::list_or_nil([
Value::Identifier("a".into()),
Value::Identifier("b".into()),
Value::Identifier("c".into()),
Value::Identifier("d".into()),
])
);
}
#[test]
fn test_value() {
let (r, v) = parse_value("+123\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number(123.into()));
let (r, v) = parse_value("-0x123\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Number((-0x123).into()));
let (r, v) = parse_value("abcdef-ghijkl\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Identifier("abcdef-ghijkl".into()));
let (r, v) = parse_value("+\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Identifier("+".into()));
let (r, v) = parse_value("+x\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Identifier("+x".into()));
let (r, v) = parse_value("lambda-\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Identifier("lambda-".into()));
let (r, v) = parse_value("lambda\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(v, Value::Keyword(Keyword::Lambda));
let (r, v) = parse_value("(f #T -0x1)\n").unwrap();
assert_eq!(r, "\n");
assert_eq!(
v,
Value::list_or_nil([
Value::Identifier("f".into()),
Value::Boolean(true.into()),
Value::Number((-0x1).into())
])
);
}
}
+163
View File
@@ -0,0 +1,163 @@
; Convenience flow control macros
(defmacro when (condition body-head &rest body)
"If condition is true, evaluates the expressions in body, otherwise returns nil"
`(if ,condition (progn ,body-head ,@body))
)
(defmacro unless (condition body-head &rest body)
"If condition is false, evaluates the expressions in body, otherwise returns nil"
`(if (not ,condition) (progn ,body-head ,@body))
)
(defmacro when-let (bindings &rest body)
"If expression evaluates to a trueish value, evaluates (let (binding expression) body...)"
(let (output nil binding-names nil)
(while bindings
(setq binding-names (cons (car bindings) binding-names))
(setq output (append output (list (car bindings) (cadr bindings))))
(setq bindings (cddr bindings))
)
(unless binding-names
(error "No bindings provided in the (when-let ...) form"))
`(let ,output
(if (and ,@binding-names)
,@body
)
)
)
)
; List functions
; TODO most of those could be ported to Rust for performance
; TODO tail recursion
(defun filter (f xs)
"Returns a new list, formed from elements of xs matching predicate f"
(cond
((nil? xs) nil)
((f (car xs)) (cons (car xs) (filter f (cdr xs))))
(&otherwise (filter f (cdr xs)))
)
)
(defun map (f xs)
"Returns a new list, with f applied to elements of xs"
(if (cons? xs)
(cons (f (car xs)) (map f (cdr xs)))
nil
)
)
(defun flatmap (f xs)
"Returns a new list, with f applied to list elements of xs, then appended together"
(let (ys nil)
(while (cons? xs)
(setq ys (append ys (f (car xs))))
(setq xs (cdr xs)))
ys))
(defun fold (f acc xs)
"Returns the value calculated by sequentially applying f to acc and each element of xs"
(while (cons? xs)
(setq acc (f acc (car xs)))
(setq xs (cdr xs)))
acc)
(defun reverse (xs)
"Returns the reversed list"
(let (ys nil)
(while (cons? xs)
(setq ys (cons (car xs) ys))
(setq xs (cdr xs)))
ys))
(defun unzip (xs)
"Converts a list of lists into a single-level list"
(flatmap identity xs))
; Logic functions
(defun or_ (forms)
(cond
((nil? forms) #F)
((nil? (cdr forms)) (car forms))
(&otherwise
(let (tmp (gensym))
`(let (,tmp ,(car forms))
(if ,tmp ,tmp ,(or_ (cdr forms)))
)
)
)
))
(defmacro or (&rest forms)
"Sequentially evaluates the forms, stopping and returning a form if it's trueish"
;; (or x y z) -> (cond (x x) (y y) (z z) (&otherwise #t))
(or_ forms))
(defun and_ (forms)
(cond
((nil? forms) #T)
((nil? (cdr forms)) (car forms))
(&otherwise `(if ,(car forms) ,(and_ (cdr forms))))
))
(defmacro and (&rest forms)
"Sequentially evaluates the forms, returning the last form if all the forms are trueish"
;; (and x y z) -> (if x (if y (if z z)))
(and_ forms)
)
; Result handling functions
(defun result/ok? (x)
"Returns #t if x is an ok"
(= 'ok (car x))
)
(defun result/err? (x)
"Returns #t if x is an error"
(= 'err (car x))
)
(defun result/map (f x)
"If x is an ok, applies f to its value, otherwise does nothing"
(if (result/ok? x)
`(ok ,(f (cadr x)))
x
)
)
(defun result/map-err (f x)
"If x is an error, applies f to its value, otherwise does nothing"
(if (result/err? x)
`(err ,(f (cadr x)))
x
)
)
; Convenience error handling macros
(defmacro catch (expression error-symbol handler)
"Evaluates expression, running the handler if evaluation fails, introducing error-symbol as error"
(let (eval-result-symbol (gensym))
`(let (,eval-result-symbol (eval (quote ,expression)))
(cond
((result/ok? ,eval-result-symbol) (cadr ,eval-result-symbol))
(&otherwise
(let (,error-symbol (cadr ,eval-result-symbol))
,handler
)
)
)
)
)
)
(defmacro compile-debug (expression)
"Prints the input expression during macro expansion/compile time and evaluates the expression in runtime"
(eprint expression)
expression
)
(defmacro runtime-debug (expression)
"Prints the input expression and evaluates it in runtime"
`(progn
(eprint (quote ,expression))
,expression
)
)
; Convenience list functions
(defun caar (x) "Alias for (car (car x))" (car (car x)))
(defun cadr (x) "Alias for (car (cdr x))" (car (cdr x)))
(defun cdar (x) "Alias for (cdr (car x))" (cdr (car x)))
(defun cddr (x) "Alias for (cdr (cdr x))" (cdr (cdr x)))
(defun caddr (x) "Alias for (car (cdr (cdr x)))" (car (cdr (cdr x))))
(defun cadar (x) "Alias for (car (cdr (car x)))" (car (cdr (car x))))
+265
View File
@@ -0,0 +1,265 @@
use std::{
borrow::Cow,
io::{BufRead, Write, stdin, stdout},
path::PathBuf,
rc::Rc,
};
use crate::{
compile::{
Compile, CompileContext, CompileOptions,
syntax::{Expression, FunctionBody, ParseError},
},
error::{MachineError, MachineErrorAt, ReadError},
parse::{self, parse_value},
util::Either,
vm::{
env::Environment,
instruction::Instruction,
machine::Machine,
value::{BytecodeFunction, IdentifierValue, Value},
},
};
pub trait Reader {
type Error: Into<MachineErrorAt>;
fn read(&mut self) -> Result<Option<Value>, Self::Error>;
}
pub struct InteractiveReader {
buffer: String,
prompt_empty: Cow<'static, str>,
prompt_continuation: Cow<'static, str>,
}
pub struct FileReader<R: BufRead> {
reader: R,
buffer: String,
}
pub struct ModuleReader<R: BufRead> {
reader: FileReader<R>,
path: PathBuf,
macro_machine: Machine,
}
impl InteractiveReader {
pub fn new<T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>>(
prompt_empty: T,
prompt_continuation: U,
) -> Self {
Self {
buffer: String::new(),
prompt_empty: prompt_empty.into(),
prompt_continuation: prompt_continuation.into(),
}
}
pub fn reset(&mut self) {
self.buffer.clear();
}
}
impl Reader for InteractiveReader {
type Error = MachineErrorAt;
fn read(&mut self) -> Result<Option<Value>, Self::Error> {
let stdin = stdin();
read_inner(
&mut stdin.lock(),
&mut self.buffer,
Some((
self.prompt_empty.as_ref(),
self.prompt_continuation.as_ref(),
)),
)
}
}
impl<R: BufRead> FileReader<R> {
pub fn new(reader: R) -> Self {
Self {
buffer: String::new(),
reader,
}
}
}
impl<R: BufRead> Reader for FileReader<R> {
type Error = MachineErrorAt;
fn read(&mut self) -> Result<Option<Value>, Self::Error> {
read_inner(&mut self.reader, &mut self.buffer, None)
}
}
impl<R: BufRead> ModuleReader<R> {
pub fn new<P: Into<PathBuf>>(reader: R, path: P, trace_macros: bool) -> Self {
let mut macro_machine = Machine::default();
macro_machine.trace_macros = trace_macros;
Self {
reader: FileReader::new(reader),
path: path.into(),
macro_machine,
}
}
pub fn read_expression(
&mut self,
options: &CompileOptions,
env: &Rc<Environment>,
) -> Result<Option<Rc<Expression>>, Either<MachineErrorAt, Vec<ParseError>>> {
loop {
let value =
read(&mut self.reader, &mut self.macro_machine, env).map_err(Either::Left)?;
let Some(value) = value else {
return Ok(None);
};
let expression = Expression::parse(&value).map_err(Either::Right)?;
if let Expression::Defmacro(_) = expression.as_ref() {
self.macro_machine
.evaluate_value(options.clone(), Some("defmacro".into()), env, value)
.map_err(Either::Left)?;
continue;
}
return Ok(Some(expression));
}
}
pub fn compile(
mut self,
module_name: Option<IdentifierValue>,
options: &CompileOptions,
env: &Rc<Environment>,
) -> Result<Rc<BytecodeFunction>, Either<MachineErrorAt, Vec<ParseError>>> {
let mut cx =
CompileContext::new(options.clone(), module_name, Some(self.path.clone().into()));
let mut body = FunctionBody {
head: vec![],
tail: Rc::new(Expression::Nil),
};
let mut syntax_errors = vec![];
loop {
let expression = match self.read_expression(options, env) {
Ok(Some(expression)) => expression,
Ok(None) => break,
Err(Either::Left(error)) => return Err(Either::Left(error)),
Err(Either::Right(errors)) => {
syntax_errors.extend(errors);
continue;
}
};
body.head.push(expression);
}
if !syntax_errors.is_empty() {
return Err(Either::Right(syntax_errors));
}
let value = body
.compile(&mut cx)
.map_err(MachineError::Compile)
.map_err(MachineErrorAt::at_unknown)
.map_err(Either::Left)?;
cx.push(value)
.map_err(MachineError::Compile)
.map_err(MachineErrorAt::at_unknown)
.map_err(Either::Left)?;
cx.emit(Instruction::Return);
let function = cx
.to_bytecode()
.map_err(MachineError::Compile)
.map_err(MachineErrorAt::at_unknown)
.map_err(Either::Left)?;
Ok(function)
}
}
fn read_inner<R: BufRead>(
reader: &mut R,
buffer: &mut String,
prompt: Option<(&str, &str)>,
) -> Result<Option<Value>, MachineErrorAt> {
loop {
let mut incomplete = None;
let mut i = buffer.trim_start();
while !i.is_empty() {
i = match parse::skip_comment_and_whitespace(i) {
Ok((i, _)) => i,
Err(_error) => {
buffer.clear();
i = buffer.trim_start();
continue;
}
};
if i.is_empty() {
buffer.clear();
break;
}
let result = parse_value(i);
let (tail, value) = match result {
Ok(r) => r,
Err(nom::Err::Incomplete(error)) => {
incomplete = Some(error);
break;
}
Err(error) => {
let error = ReadError::Lexical(error.map_input(|i| i.into()));
let error = MachineError::Read(error);
buffer.clear();
return Err(error.at_unknown());
}
};
*buffer = tail.trim_start().into();
return Ok(Some(value));
}
*buffer = buffer.trim_start().into();
if let Some((prompt_empty, prompt_continuation)) = prompt {
if buffer.is_empty() {
print!("{prompt_empty}");
} else {
print!("{prompt_continuation}");
}
stdout().flush().ok();
}
let len = reader
.read_line(buffer)
.map_err(ReadError::Io)
.map_err(MachineError::Read)
.map_err(MachineErrorAt::at_unknown)?;
if len == 0 {
return if let Some(incomplete) = incomplete {
let error = ReadError::Lexical(nom::Err::Incomplete(incomplete));
let error = MachineError::Read(error);
Err(error.at_unknown())
} else {
assert!(buffer.is_empty());
Ok(None)
};
}
}
}
pub fn read<R: Reader>(
reader: &mut R,
vm: &mut Machine,
env: &Rc<Environment>,
) -> Result<Option<Value>, MachineErrorAt> {
let raw_value = reader.read().map_err(Into::into)?;
let Some(raw_value) = raw_value else {
return Ok(None);
};
let exp_value = vm.macro_expand(env, &raw_value)?;
Ok(Some(exp_value))
}
+109
View File
@@ -0,0 +1,109 @@
use std::fmt;
pub enum Either<A, B> {
Left(A),
Right(B),
}
pub struct TryFilter<I, E1, E2, T, F>
where
I: Iterator<Item = Result<T, E1>>,
F: FnMut(&T) -> Result<bool, E2>,
E2: From<E1>,
{
iterator: I,
filter: F,
}
pub trait IteratorExt<I, E1, T> {
fn try_filter<E2, F>(self, filter: F) -> impl Iterator<Item = Result<T, E2>>
where
F: FnMut(&T) -> Result<bool, E2>,
E2: From<E1>;
}
#[macro_export]
macro_rules! primitive_enum {
(
$(#[$meta:meta])*
$vis:vis enum $ident:ident: $repr:ty [$conversion_err:ident] {
$($variant:ident = $discriminant:literal),+ $(,)?
}
) => {
$(#[$meta])*
#[repr($repr)]
$vis enum $ident {
$($variant = $discriminant),+
}
impl From<$ident> for $repr {
fn from(value: $ident) -> $repr {
value as $repr
}
}
impl TryFrom<$repr> for $ident {
type Error = $conversion_err;
fn try_from(value: $repr) -> Result<$ident, $conversion_err> {
match value {
$($discriminant => Ok($ident::$variant),)+
_ => Err($conversion_err(value))
}
}
}
};
}
impl<A: fmt::Debug, B: fmt::Debug> fmt::Debug for Either<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Left(a) => fmt::Debug::fmt(a, f),
Self::Right(b) => fmt::Debug::fmt(b, f),
}
}
}
impl<I, E1, T> IteratorExt<I, E1, T> for I
where
I: Iterator<Item = Result<T, E1>>,
{
fn try_filter<E2, F>(self, filter: F) -> impl Iterator<Item = Result<T, E2>>
where
F: FnMut(&T) -> Result<bool, E2>,
E2: From<E1>,
{
TryFilter {
iterator: self,
filter,
}
}
}
impl<I, E1, E2, T, F> Iterator for TryFilter<I, E1, E2, T, F>
where
I: Iterator<Item = Result<T, E1>>,
F: FnMut(&T) -> Result<bool, E2>,
E2: From<E1>,
{
type Item = Result<T, E2>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let value = self.iterator.next()?;
let value = match value {
Ok(value) => value,
Err(error) => return Some(Err(error.into())),
};
let cond = match (self.filter)(&value) {
Ok(value) => value,
Err(error) => return Some(Err(error)),
};
match cond {
true => return Some(Ok(value)),
false => continue,
}
}
}
}
+195
View File
@@ -0,0 +1,195 @@
use std::{
borrow::Borrow,
cell::RefCell,
collections::HashMap,
fmt,
hash::Hash,
rc::Rc,
sync::atomic::{AtomicU32, Ordering},
};
use crate::{
error::MachineError,
vm::{
machine::Machine,
value::{
BytecodeFunction, IdentifierValue, NativeFunction, NativeObject, StringValue, Value,
convert::{AnyFunction, TryFromValue},
},
},
};
#[derive(Clone, Debug)]
pub enum Macro {
Native(NativeFunction),
Bytecode(Rc<BytecodeFunction>),
}
pub trait EnvironmentAccessHook {
fn read_variable(&mut self, name: &str) -> Option<Value>;
fn write_variable(
&mut self,
name: &IdentifierValue,
value: &Value,
) -> Result<bool, MachineError>;
}
#[derive(Default)]
pub struct Environment {
globals: RefCell<HashMap<IdentifierValue, Value>>,
macros: RefCell<HashMap<IdentifierValue, Macro>>,
parent: Option<Rc<Environment>>,
gensym_index: AtomicU32,
access_hook: RefCell<Option<Box<dyn EnvironmentAccessHook>>>,
}
impl Environment {
pub fn new(parent: Option<Rc<Self>>) -> Self {
Self {
parent,
..Default::default()
}
}
pub fn gensym(&self) -> IdentifierValue {
// TODO do this in a root environment?
let index = self.gensym_index.fetch_add(1, Ordering::SeqCst);
format!("___gensym{index}").into()
}
pub fn set_access_hook(
&self,
hook: Option<Box<dyn EnvironmentAccessHook>>,
) -> Option<Box<dyn EnvironmentAccessHook>> {
self.access_hook.replace(hook)
}
pub fn defun_native<S, D, F>(&self, identifier: S, docstring: D, function: F) -> Value
where
S: Into<IdentifierValue>,
D: Into<StringValue>,
F: Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static,
{
let identifier = identifier.into();
let native = NativeFunction::new(identifier.clone(), docstring, function);
let value = Value::NativeFunction(native);
self.set_global_value(identifier, value.clone())
.expect("defun_native failed");
value
}
pub fn defmacro_native<S, D, F>(&self, identifier: S, docstring: D, function: F)
where
S: Into<IdentifierValue>,
D: Into<StringValue>,
F: Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static,
{
let identifier = identifier.into();
let native = NativeFunction::new(identifier.clone(), docstring, function);
self.macros
.borrow_mut()
.insert(identifier, Macro::Native(native));
}
pub fn defmacro_bytecode<S: Into<IdentifierValue>>(
&self,
identifier: S,
value: Rc<BytecodeFunction>,
) {
let identifier = identifier.into();
// println!("Export macro: {identifier}: {value}");
self.macros
.borrow_mut()
.insert(identifier, Macro::Bytecode(value));
}
pub fn global_value<Q>(&self, identifier: &Q) -> Option<Value>
where
IdentifierValue: Borrow<Q>,
Q: Hash + Eq + ?Sized + AsRef<str>,
{
if let Some(hook) = &mut *self.access_hook.borrow_mut()
&& let Some(value) = hook.read_variable(identifier.as_ref())
{
return Some(value);
}
self.globals.borrow().get(identifier).cloned().or_else(|| {
self.parent
.as_ref()
.and_then(|parent| parent.global_value(identifier))
})
}
pub fn set_global_value<S: Into<IdentifierValue>, V: Into<Value>>(
&self,
identifier: S,
value: V,
) -> Result<(), MachineError> {
let identifier = identifier.into();
let value = value.into();
if let Some(hook) = &mut *self.access_hook.borrow_mut() {
match hook.write_variable(&identifier, &value) {
Ok(true) => return Ok(()),
Ok(false) => {}
Err(error) => return Err(error),
}
}
self.globals.borrow_mut().insert(identifier, value);
Ok(())
}
pub fn global_macro<Q>(&self, identifier: &Q) -> Option<Macro>
where
IdentifierValue: Borrow<Q>,
Q: Hash + Eq,
{
self.macros.borrow().get(identifier).cloned().or_else(|| {
self.parent
.as_ref()
.and_then(|parent| parent.global_macro(identifier))
})
}
pub fn evaluate_global_function<Q>(
self: &Rc<Self>,
vm: &mut Machine,
identifier: &Q,
arguments: &[Value],
) -> Result<Value, MachineError>
where
IdentifierValue: Borrow<Q>,
Q: Hash + Eq + ?Sized + AsRef<str>,
for<'a> &'a Q: Into<IdentifierValue>,
{
let value = self
.global_value(identifier)
.ok_or_else(|| MachineError::UnboundIdentifier(identifier.into()))?;
let function = AnyFunction::try_from_value(&value)?;
function.invoke(vm, self, arguments)
}
}
impl fmt::Display for Environment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "environment")
}
}
impl fmt::Debug for Environment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Environment")
.field("globals", &self.globals)
.field("macros", &self.macros)
.finish_non_exhaustive()
}
}
impl NativeObject for Environment {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
+266
View File
@@ -0,0 +1,266 @@
use std::fmt;
use crate::{
error::MachineError,
vm::{machine::Machine, value::NumberValue},
};
#[derive(Debug, PartialEq, thiserror::Error)]
#[error("unrecognized instruction: {0}")]
pub struct InstructionDecodeError(u8);
primitive_enum! {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Instruction: u8 [InstructionDecodeError] {
// Stack manipulation
PushNil = 0,
PushInteger = 1,
PushTrue = 2,
PushFalse = 3,
PushConstant = 4,
CloseUpvalue = 5,
Drop = 6,
SetTemp = 7,
GetTemp = 8,
// Binding manupulation
GetLocal = 10,
SetLocal = 11,
GetUpvalue = 12,
SetUpvalue = 13,
GetGlobal = 14,
SetGlobal = 15,
DeclareGlobal = 16,
DeclareMacro = 17,
// Arithmetic
Add = 20,
Sub = 21,
Mul = 22,
Div = 23,
Mod = 24,
Gt = 25,
Lt = 26,
Eq = 27,
Ge = 28,
Le = 29,
Ne = 30,
Not = 31,
// Branching
Branch = 40,
Jump = 41,
// Functions and closures
Call = 50,
Return = 51,
MakeClosure = 52,
}
}
macro_rules! impl_fixed_unsigned {
($vis:vis $ident:ident : $repr:ty, $bits:literal) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
$vis struct $ident($repr);
impl $ident {
pub const BITS: usize = $bits;
pub fn to_bytes(&self) -> [u8; $bits / 8] {
self.0.to_le_bytes()
}
}
impl TryFrom<usize> for $ident {
type Error = FixedConvertError<usize, $bits>;
fn try_from(value: usize) -> Result<Self, Self::Error> {
if let Ok(value) = <$repr>::try_from(value) {
Ok(Self(value))
} else {
Err(FixedConvertError(value))
}
}
}
impl From<$repr> for $ident {
fn from(value: $repr) -> $ident {
$ident(value)
}
}
impl From<$ident> for usize {
fn from(value: $ident) -> usize {
value.0.into()
}
}
impl From<$ident> for $repr {
fn from(value: $ident) -> $repr {
value.0
}
}
};
}
macro_rules! impl_fixed {
($vis:vis $ident:ident: u8) => {
impl_fixed_unsigned!($vis $ident : u8, 8);
impl ReadEncoded for $ident {
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
let b0 = stream.fetch_byte()?;
Ok(Self(b0))
}
}
};
($vis:vis $ident:ident: u16) => {
impl_fixed_unsigned!($vis $ident : u16, 16);
impl ReadEncoded for $ident {
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
let b0 = stream.fetch_byte()?;
let b1 = stream.fetch_byte()?;
Ok(Self(u16::from_le_bytes([b0, b1])))
}
}
};
}
#[derive(Debug, thiserror::Error)]
#[error("cannot fit {0} into {N} bits")]
pub struct FixedConvertError<T: fmt::Display, const N: usize>(T);
impl_fixed!(pub LocalId: u8);
impl_fixed!(pub ConstantId: u16);
impl_fixed!(pub ArgumentCount: u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct ImmediateInteger(i16);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct BranchOffset(i16);
impl ImmediateInteger {
pub fn sign_extend_i64(self) -> i64 {
self.0 as i64
}
pub fn to_bytes(&self) -> [u8; 2] {
self.0.to_le_bytes()
}
}
impl From<i16> for ImmediateInteger {
fn from(value: i16) -> Self {
Self(value)
}
}
impl ReadEncoded for ImmediateInteger {
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
let b0 = stream.fetch_byte()?;
let b1 = stream.fetch_byte()?;
Ok(Self(i16::from_le_bytes([b0, b1])))
}
}
impl TryFrom<NumberValue> for ImmediateInteger {
type Error = FixedConvertError<NumberValue, 16>;
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
match value {
NumberValue::Int(value) if let Ok(value) = value.try_into() => Ok(Self(value)),
_ => Err(FixedConvertError(value)),
}
}
}
impl TryFrom<i64> for ImmediateInteger {
type Error = FixedConvertError<i64, 16>;
fn try_from(value: i64) -> Result<Self, Self::Error> {
if let Ok(value) = value.try_into() {
Ok(Self(value))
} else {
Err(FixedConvertError(value))
}
}
}
impl BranchOffset {
pub fn sign_extend_isize(self) -> isize {
self.0 as isize
}
pub fn to_bytes(&self) -> [u8; 2] {
self.0.to_le_bytes()
}
}
impl TryFrom<isize> for BranchOffset {
type Error = FixedConvertError<isize, 16>;
fn try_from(value: isize) -> Result<Self, Self::Error> {
if let Ok(value) = value.try_into() {
Ok(Self(value))
} else {
Err(FixedConvertError(value))
}
}
}
impl From<i16> for BranchOffset {
fn from(value: i16) -> Self {
Self(value)
}
}
impl ReadEncoded for BranchOffset {
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
let b0 = stream.fetch_byte()?;
let b1 = stream.fetch_byte()?;
Ok(Self(i16::from_le_bytes([b0, b1])))
}
}
pub trait ReadEncoded: Sized {
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError>;
}
impl fmt::Display for Instruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::PushNil => "PUSH_NIL",
Self::PushInteger => "PUSH_INTEGER",
Self::PushTrue => "PUSH_TRUE",
Self::PushFalse => "PUSH_FALSE",
Self::PushConstant => "PUSH_CONSTANT",
Self::CloseUpvalue => "CLOSE_UPVALUE",
Self::Drop => "DROP",
Self::SetTemp => "SET_TEMP",
Self::GetTemp => "GET_TEMP",
Self::GetLocal => "GET_LOCAL",
Self::SetLocal => "SET_LOCAL",
Self::GetUpvalue => "GET_UPVALUE",
Self::SetUpvalue => "SET_UPVALUE",
Self::GetGlobal => "GET_GLOBAL",
Self::SetGlobal => "SET_GLOBAL",
Self::DeclareGlobal => "DECLARE_GLOBAL",
Self::DeclareMacro => "DECLARE_MACRO",
Self::Add => "ADD",
Self::Sub => "SUB",
Self::Mul => "MUL",
Self::Div => "DIV",
Self::Mod => "MOD",
Self::Gt => "GT",
Self::Lt => "LT",
Self::Eq => "EQ",
Self::Ge => "GE",
Self::Le => "LE",
Self::Ne => "NE",
Self::Not => "NOT",
Self::Branch => "BRANCH",
Self::Jump => "JUMP",
Self::Call => "CALL",
Self::Return => "RETURN",
Self::MakeClosure => "MAKE_CLOSURE",
};
f.write_str(name)
}
}
+971
View File
@@ -0,0 +1,971 @@
use std::{
fs::File,
io::{self, BufReader, Read},
path::Path,
rc::Rc,
};
use crate::{
compile::{CompileContext, CompileOptions},
error::{
ArgumentCountError, MachineError, MachineErrorAt, MachineErrorLocation, ReadError,
ValueConversionError,
},
read::{self, FileReader, ModuleReader},
util::Either,
vm::{
Value,
env::Environment,
instruction::{
ArgumentCount, BranchOffset, ConstantId, ImmediateInteger, Instruction, LocalId,
ReadEncoded,
},
macros::MacroExpand,
prelude,
stack::Stack,
value::{
BytecodeFunction, ClosureValue, IdentifierValue, NumberValue, Upvalue, UpvalueRef,
},
},
};
#[derive(Debug)]
pub struct CallFrame {
pub closure: ClosureValue,
pub ip: usize,
pub base_pointer: usize,
open_upvalue_trackers: Vec<OpenUpvalueTracker>,
}
pub struct Machine {
data_stack: Stack<Value>,
call_stack: Stack<CallFrame>,
tmp: Option<Value>,
// upvalue_arena: Vec<UpvalueValue>,
pub trace_instructions: bool,
pub trace_returns: bool,
pub trace_stack: bool,
pub trace_calls: bool,
pub trace_macros: bool,
}
#[derive(Debug)]
struct OpenUpvalueTracker {
sp: usize,
upvalue_ref: UpvalueRef,
}
impl From<usize> for OpenUpvalueTracker {
fn from(value: usize) -> Self {
Self {
sp: value,
upvalue_ref: UpvalueRef::open(value),
}
}
}
impl Default for Machine {
fn default() -> Self {
Self {
data_stack: Stack::new(1024),
call_stack: Stack::new(64),
tmp: None,
// upvalue_arena: vec![],
trace_calls: false,
trace_stack: false,
trace_returns: false,
trace_instructions: false,
trace_macros: false,
}
}
}
impl Machine {
pub(crate) fn fetch_byte(&mut self) -> Result<u8, MachineError> {
let frame = self
.call_stack
.head_mut()
.ok_or(MachineError::InstructionPointerUndefined)?;
let byte = frame
.closure
.instruction_byte(frame.ip)
.ok_or(MachineError::InstructionPointerOutOfBounds)?;
frame.ip += 1;
Ok(byte)
}
#[inline]
fn fetch_opcode(&mut self) -> Result<Instruction, MachineError> {
let byte = self.fetch_byte()?;
Ok(byte.try_into()?)
}
fn push(&mut self, value: Value) -> Result<(), MachineError> {
self.data_stack
.push(value)
.map_err(|_| MachineError::DataStackOverflow)
}
fn pop(&mut self) -> Result<Value, MachineError> {
self.data_stack
.pop()
.ok_or(MachineError::DataStackUnderflow)
}
pub fn read_call_stack(&self, depth: usize) -> Option<&CallFrame> {
self.call_stack.nth(depth)
}
pub fn current_location(&self) -> Option<MachineErrorLocation> {
self.call_stack.head().map(|frame| MachineErrorLocation {
function: frame.closure.function.clone(),
offset: frame.ip,
})
}
fn local_slot(&mut self, id: LocalId) -> Result<&mut Value, MachineError> {
let frame = self
.call_stack
.head()
.ok_or(MachineError::CallStackUnderflow)?;
let sp = frame.base_pointer + 1 + usize::from(id);
self.data_stack
.get_mut(sp)
.ok_or(MachineError::UndefinedLocalReference)
}
fn execute_get_local(&mut self, id: LocalId) -> Result<(), MachineError> {
let value = self.local_slot(id)?.clone();
self.push(value)
}
fn execute_set_local(&mut self, id: LocalId) -> Result<(), MachineError> {
let value = self.pop()?;
*self.local_slot(id)? = value;
Ok(())
}
fn execute_get_upvalue(&mut self, id: LocalId) -> Result<(), MachineError> {
let frame = self
.call_stack
.head()
.ok_or(MachineError::CallStackUnderflow)?;
let value = frame.closure.upvalues[usize::from(id)].read(&self.data_stack)?;
self.push(value)?;
Ok(())
}
fn execute_set_upvalue(&mut self, id: LocalId) -> Result<(), MachineError> {
let value = self.pop()?;
let frame = self
.call_stack
.head()
.ok_or(MachineError::CallStackUnderflow)?;
frame.closure.upvalues[usize::from(id)].write(&mut self.data_stack, value)?;
Ok(())
}
fn execute_get_global(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
let identifier = self.pop()?;
let Value::Identifier(identifier) = identifier else {
return Err(MachineError::InvalidInstructionArgument(
Instruction::GetGlobal,
ValueConversionError {
expected: "identifier".into(),
got: identifier,
},
));
};
let value = env
.global_value(&identifier)
.ok_or(MachineError::UnboundIdentifier(identifier))?;
self.push(value)?;
Ok(())
}
fn execute_set_global(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
let identifier = self.pop()?;
let Value::Identifier(identifier) = identifier else {
return Err(MachineError::InvalidInstructionArgument(
Instruction::SetGlobal,
ValueConversionError {
expected: "identifier".into(),
got: identifier,
},
));
};
let value = self.pop()?;
env.set_global_value(identifier, value)?;
Ok(())
}
fn collect_rest_argument(&mut self, count: usize) -> Result<Value, MachineError> {
let mut rest = Value::Nil;
for _ in 0..count {
let value = self.pop()?;
rest = value.cons(rest);
}
Ok(rest)
}
fn collect_call_arguments(
&mut self,
function: &Rc<BytecodeFunction>,
argument_count: usize,
) -> Result<(), MachineError> {
if !(function.min_arity()..=function.max_arity()).contains(&argument_count) {
return Err(MachineError::ArgumentCount(ArgumentCountError {
function: function.clone(),
actual: argument_count,
}));
}
// Fill out missing optionals
let mut remaining = argument_count - function.required_count;
if remaining > function.optional_count {
// Collect into &rest X
remaining -= function.optional_count;
} else {
// Pad missing optionals
for _ in remaining..function.optional_count {
self.push(Value::Nil)?;
}
remaining = 0;
}
if function.has_rest {
let rest = self.collect_rest_argument(remaining)?;
self.push(rest)?;
}
Ok(())
}
fn execute_call(
&mut self,
env: &Rc<Environment>,
argument_count: usize,
) -> Result<(), MachineError> {
let base_pointer = self
.data_stack
.pointer()
.checked_sub(argument_count + 1)
.ok_or(MachineError::DataStackUnderflow)?;
let callable = &self.data_stack[base_pointer];
let closure = match callable {
Value::Closure(closure) => closure.clone(),
// Make closure from just the function
Value::Function(function) => ClosureValue {
function: function.clone(),
upvalues: vec![],
},
Value::NativeFunction(function) => {
let function = function.clone();
// TODO remove argument cloning
let mut arguments = (0..argument_count)
.map(|_| self.pop())
.collect::<Result<Vec<_>, _>>()?;
arguments.reverse();
if self.trace_calls {
eprintln!("TRACE: Call native");
eprintln!("TRACE: {function}");
if let Some(location) = self.current_location() {
eprintln!("TRACE: From {location}");
} else {
eprintln!("TRACE: From <unknown>");
}
if !arguments.is_empty() {
eprintln!("TRACE: With arguments:");
for (i, arg) in arguments.iter().enumerate() {
eprintln!("TRACE: [{i}] {arg}");
}
}
}
let value = function.invoke(self, env, &arguments[..])?;
// Drop native function itself from the stack
self.pop()?;
self.push(value)?;
return Ok(());
}
_ => {
return Err(MachineError::InvalidInstructionArgument(
Instruction::Call,
ValueConversionError {
expected: "closure, function or native function".into(),
got: callable.clone(),
},
));
}
};
self.collect_call_arguments(&closure.function, argument_count)?;
if self.trace_calls {
eprintln!("TRACE: Call closure");
eprintln!("TRACE: {closure}");
if let Some(location) = self.current_location() {
eprintln!("TRACE: From {location}");
} else {
eprintln!("TRACE: From <unknown>");
}
if argument_count != 0 {
eprintln!("TRACE: With arguments:");
for i in 0..argument_count {
let sp = base_pointer + 1 + i;
if let Some(argument) = self.data_stack.get(sp) {
eprintln!("TRACE: [{i}] {argument}");
} else {
eprintln!("TRACE: [{i}] <invalid>");
}
}
}
}
let frame = CallFrame {
closure,
base_pointer,
ip: 0,
open_upvalue_trackers: vec![],
};
self.call_stack
.push(frame)
.map_err(|_| MachineError::CallStackOverflow)?;
Ok(())
}
fn execute_make_closure(&mut self) -> Result<(), MachineError> {
let value = self.pop()?;
let Value::Function(function) = value else {
return Err(MachineError::InvalidInstructionArgument(
Instruction::MakeClosure,
ValueConversionError {
expected: "function".into(),
got: value,
},
));
};
let mut closure = ClosureValue {
function,
upvalues: vec![],
};
let frame = self
.call_stack
.head_mut()
.ok_or(MachineError::CallStackUnderflow)?;
// eprintln!("BEGIN MAKE CLOSURE");
for upvalue_def in closure.function.upvalues.iter() {
let upvalue_ref = if upvalue_def.is_local {
let sp = frame.base_pointer + 1 + usize::from(upvalue_def.index);
let tracker_index = frame
.open_upvalue_trackers
.binary_search_by_key(&sp, |t| t.sp)
// .inspect(|i| {
// eprintln!("UPVALUE: found open tracker i={i}, sp={sp}");
// })
.unwrap_or_else(|i| {
// eprintln!("UPVALUE: create new open tracker i={i}, sp={sp}");
frame
.open_upvalue_trackers
.insert(i, OpenUpvalueTracker::from(sp));
i
});
frame.open_upvalue_trackers[tracker_index]
.upvalue_ref
.clone()
} else {
todo!()
};
closure.upvalues.push(upvalue_ref);
}
// for (i, upvalue) in closure.upvalues.iter().enumerate() {
// eprintln!(
// "UPVALUE #{i}: {upvalue:?} => {}",
// upvalue.read(&self.data_stack).unwrap()
// );
// }
// eprintln!("END MAKE CLOSURE");
self.push(Value::Closure(closure))?;
Ok(())
}
fn execute_return(&mut self) -> Result<(), MachineError> {
let return_value = self.pop()?;
if self.trace_returns {
eprintln!("TRACE: Function return");
if let Some(location) = self.current_location() {
eprintln!("TRACE: From {location}");
} else {
eprintln!("TRACE: From <unknown>");
}
let csp = self.call_stack.pointer();
if csp > 0
&& let Some(frame) = self.call_stack.get(csp - 1)
{
eprintln!("TRACE: To {}+{}", frame.closure, frame.ip);
} else {
eprintln!("TRACE: To <unknown>");
}
eprintln!("TRACE: With value {return_value}");
}
let frame = self
.call_stack
.pop()
.ok_or(MachineError::CallStackUnderflow)?;
self.data_stack.set_pointer(frame.base_pointer);
self.push(return_value)?;
Ok(())
}
fn execute_declare_macro(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
let identifier = self.pop()?;
let function = self.pop()?;
let Value::Identifier(identifier) = identifier else {
return Err(MachineError::InvalidInstructionArgument(
Instruction::DeclareMacro,
ValueConversionError {
expected: "identifier".into(),
got: identifier,
},
));
};
let Value::Function(function) = function else {
return Err(MachineError::InvalidInstructionArgument(
Instruction::DeclareMacro,
ValueConversionError {
expected: "function".into(),
got: function,
},
));
};
env.defmacro_bytecode(identifier, function);
Ok(())
}
fn execute_branch(&mut self, check_condition: bool, target: isize) -> Result<(), MachineError> {
let do_branch = if check_condition {
let condition_value = self.pop()?;
!condition_value.is_trueish()
} else {
true
};
if do_branch {
let frame = self
.call_stack
.head_mut()
.ok_or(MachineError::CallStackUnderflow)?;
let ip = frame
.ip
.checked_add_signed(target)
.ok_or(MachineError::InvalidBranchTarget(frame.ip, target))?;
frame.ip = ip;
}
Ok(())
}
fn trace_stack(&self) {
eprint!("TRACE: [");
for v in 0..self.data_stack.pointer() {
if v != 0 {
eprint!(" ");
}
eprint!("{}", self.data_stack[v]);
}
eprintln!("]");
}
fn trace_instruction(&self) {
let Some(frame) = self.call_stack.head() else {
eprintln!("<undefined>");
return;
};
frame.closure.function.disassemble(frame.ip, 0, 0, false);
}
fn execute_next(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
if self.trace_instructions {
if self.trace_stack {
self.trace_stack();
}
self.trace_instruction();
}
let opcode = self.fetch_opcode()?;
match opcode {
// values
Instruction::PushNil => self.push(Value::Nil)?,
Instruction::PushInteger => {
let value = ImmediateInteger::read_encoded(self)?;
self.push(Value::Number(NumberValue::Int(value.sign_extend_i64())))?;
}
Instruction::PushTrue => self.push(true.into())?,
Instruction::PushFalse => self.push(false.into())?,
Instruction::PushConstant => {
let index = ConstantId::read_encoded(self)?;
let frame = self.call_stack.head().expect("unreachable");
let value = frame
.closure
.function
.constants
.get(usize::from(index))
.cloned()
.ok_or(MachineError::UndefinedConstantReference)?;
self.push(value)?;
}
Instruction::Drop => {
let _ = self.pop()?;
}
// binding
Instruction::SetLocal => {
let id = LocalId::read_encoded(self)?;
self.execute_set_local(id)?;
}
Instruction::GetLocal => {
let id = LocalId::read_encoded(self)?;
self.execute_get_local(id)?;
}
Instruction::SetUpvalue => {
let id = LocalId::read_encoded(self)?;
self.execute_set_upvalue(id)?;
}
Instruction::GetUpvalue => {
let id = LocalId::read_encoded(self)?;
self.execute_get_upvalue(id)?;
}
Instruction::SetGlobal => self.execute_set_global(env)?,
Instruction::GetGlobal => self.execute_get_global(env)?,
Instruction::DeclareMacro => self.execute_declare_macro(env)?,
// arithmetic
Instruction::Gt
| Instruction::Lt
| Instruction::Eq
| Instruction::Ge
| Instruction::Le
| Instruction::Ne
| Instruction::Add
| Instruction::Sub
| Instruction::Mul
| Instruction::Div
| Instruction::Mod
| Instruction::Not => {
let argument_count = usize::from(ArgumentCount::read_encoded(self)?);
let mut arguments = (0..argument_count)
.map(|_| self.pop())
.collect::<Result<Vec<_>, _>>()?;
arguments.reverse();
let function = prelude::dispatch_arithmetic(opcode);
let value = (function)(self, env, &arguments[..])?;
self.push(value)?;
}
// function
Instruction::Return => {
self.execute_return()?;
}
Instruction::Call => {
let argument_count = usize::from(ArgumentCount::read_encoded(self)?);
self.execute_call(env, argument_count)?;
}
Instruction::MakeClosure => {
self.execute_make_closure()?;
}
Instruction::SetTemp => {
self.tmp = Some(self.pop()?);
}
Instruction::GetTemp => {
let tmp = self.tmp.take().ok_or(MachineError::TempRegisterEmpty)?;
self.push(tmp)?;
}
Instruction::CloseUpvalue => {
let frame = self
.call_stack
.head_mut()
.ok_or(MachineError::CallStackUnderflow)?;
let upvalue_sp = self.data_stack.pointer() - 1;
let start = frame
.open_upvalue_trackers
.binary_search_by_key(&upvalue_sp, |t| t.sp)
.unwrap_or_else(|i| i);
for tracker in frame.open_upvalue_trackers.drain(start..) {
let value = self
.data_stack
.get(tracker.sp)
.cloned()
.ok_or(MachineError::UndefinedUpvalueReference)?;
let old = tracker.upvalue_ref.close(value);
// eprintln!("CLOSE UPVALUE sp={} -> {}", tracker.sp, value);
assert_eq!(old, Upvalue::Open(tracker.sp));
}
self.pop()?;
}
Instruction::Branch => {
let offset = BranchOffset::read_encoded(self)?;
self.execute_branch(true, offset.sign_extend_isize())?;
}
Instruction::Jump => {
let offset = BranchOffset::read_encoded(self)?;
self.execute_branch(false, offset.sign_extend_isize())?;
}
Instruction::DeclareGlobal => todo!(),
}
Ok(())
}
fn unwind_to(&mut self, depth: usize) {
// Data stack management
while self.call_stack.pointer() != depth {
let frame = self.call_stack.pop().unwrap();
self.data_stack.set_pointer(frame.base_pointer);
}
}
pub fn evaluate_closure(
&mut self,
env: &Rc<Environment>,
closure: ClosureValue,
argument_count: usize,
) -> Result<Value, MachineErrorAt> {
let unwind_target = self.call_stack.pointer();
self.push(Value::Closure(closure))
.map_err(MachineErrorAt::at_unknown)?;
self.execute_call(env, argument_count)
.map_err(MachineErrorAt::at_unknown)?;
while self.call_stack.pointer() != unwind_target {
let location = self.current_location();
if let Err(error) = self.execute_next(env) {
// Unwind up to entry depth
self.unwind_to(unwind_target);
return Err(error.at(location));
}
}
self.pop().map_err(MachineErrorAt::at_unknown)
}
pub fn evaluate_closure_args(
&mut self,
env: &Rc<Environment>,
closure: ClosureValue,
args: &[Value],
) -> Result<Value, MachineErrorAt> {
self.push(Value::Closure(closure))
.map_err(MachineErrorAt::at_unknown)?;
for arg in args {
self.push(arg.clone()).map_err(MachineErrorAt::at_unknown)?;
}
let unwind_target = self.call_stack.pointer();
self.execute_call(env, args.len())
.map_err(MachineErrorAt::at_unknown)?;
while self.call_stack.pointer() != unwind_target {
let location = self.current_location();
if let Err(error) = self.execute_next(env) {
// Unwind up to entry depth
self.unwind_to(unwind_target);
return Err(error.at(location));
}
}
self.pop().map_err(MachineErrorAt::at_unknown)
}
pub fn macro_expand(
&mut self,
env: &Rc<Environment>,
value: &Value,
) -> Result<Value, MachineErrorAt> {
match value.macro_expand(self, env, false) {
Ok(result) => {
if self.trace_macros && *value != result {
eprintln!("TRACE: Macro expansion:");
eprintln!("TRACE: {value}");
eprintln!("TRACE: VVVVV");
eprintln!("TRACE: {result}");
}
Ok(result)
}
Err(error) => {
if self.trace_macros {
eprintln!("TRACE: Macro expansion:");
eprintln!("TRACE: {value}");
eprintln!("TRACE: VVVVV");
eprintln!("TRACE: {error}");
}
Err(error)
}
}
}
pub fn evaluate_value(
&mut self,
compile_options: CompileOptions,
chunk_name: Option<IdentifierValue>,
env: &Rc<Environment>,
value: Value,
) -> Result<Value, MachineErrorAt> {
let value_expanded = self.macro_expand(env, &value)?;
let function =
CompileContext::compile_value(compile_options, chunk_name, &value_expanded, None)
.map_err(MachineError::Compile)
.map_err(MachineErrorAt::at_unknown)?;
let closure = ClosureValue {
function,
upvalues: vec![],
};
let value = self.evaluate_closure(env, closure, 0)?;
Ok(value)
}
pub fn load_file<P: AsRef<Path>>(
&mut self,
compile_options: CompileOptions,
env: &Rc<Environment>,
path: P,
) -> Result<(), MachineErrorAt> {
let path = path.as_ref();
let name = format!("{}", path.display());
let reader = BufReader::new(
File::open(path)
.map_err(ReadError::Io)
.map_err(MachineError::Read)
.map_err(MachineErrorAt::at_unknown)?,
);
let module_reader = ModuleReader::new(reader, path, self.trace_macros);
let function = match module_reader.compile(Some(name.into()), &compile_options, env) {
Ok(function) => function,
Err(Either::Left(error)) => return Err(error),
Err(Either::Right(syntax)) => {
return Err(MachineError::Read(ReadError::Parse(syntax)).at_unknown());
}
};
let closure = ClosureValue {
function,
upvalues: vec![],
};
self.evaluate_closure(env, closure, 0)?;
Ok(())
}
pub fn evaluate_str(
&mut self,
compile_options: CompileOptions,
chunk_name: Option<IdentifierValue>,
env: &Rc<Environment>,
text: &str,
) -> Result<Value, MachineErrorAt> {
struct SliceReader<'a>(&'a [u8]);
impl Read for SliceReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let count = self.0.len().min(buf.len());
buf[..count].copy_from_slice(&self.0[..count]);
self.0 = &self.0[count..];
Ok(count)
}
}
let reader = BufReader::new(SliceReader(text.as_bytes()));
let mut reader = FileReader::new(reader);
let mut last_value = Value::Nil;
loop {
let value = match read::read(&mut reader, self, env)? {
Some(value) => value,
None => break,
};
last_value =
self.evaluate_value(compile_options.clone(), chunk_name.clone(), env, value)?;
}
Ok(last_value)
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use crate::{
error::{MachineError, MachineErrorAt},
vm::{
Value,
env::Environment,
instruction::Instruction,
machine::Machine,
value::{BytecodeFunction, ClosureValue, NumberValue},
},
};
fn try_eval(
env: &Rc<Environment>,
mut instructions: Vec<u8>,
constants: Vec<Value>,
) -> (Machine, Result<Value, MachineErrorAt>) {
instructions.push(Instruction::Return.into());
let closure = ClosureValue {
upvalues: vec![],
function: Rc::new(BytecodeFunction {
identifier: Some("test-script".into()),
docstring: None,
instructions: instructions.into(),
constants: constants.into(),
upvalues: [].into(),
required_count: 0,
optional_count: 0,
has_rest: false,
script: None,
}),
};
let mut machine = Machine::default();
let result = machine.evaluate_closure(env, closure, 0);
(machine, result)
}
fn eval0(
env: &Rc<Environment>,
instructions: Vec<u8>,
constants: Vec<Value>,
) -> (Machine, Value) {
let (machine, value) = try_eval(env, instructions, constants);
let value = value.expect("evaluation failed");
(machine, value)
}
#[test]
fn test_stack_execution() {
let env = Rc::new(Environment::default());
let (m, v) = eval0(&env, vec![Instruction::PushNil.into()], vec![]);
assert_eq!(v, Value::Nil);
assert!(m.data_stack.is_empty());
assert!(m.call_stack.is_empty());
let (m, v) = eval0(&env, vec![Instruction::PushTrue.into()], vec![]);
assert_eq!(v, true.into());
assert!(m.data_stack.is_empty());
assert!(m.call_stack.is_empty());
let (m, v) = eval0(&env, vec![Instruction::PushFalse.into()], vec![]);
assert_eq!(v, false.into());
assert!(m.data_stack.is_empty());
assert!(m.call_stack.is_empty());
}
#[test]
fn test_unwind() {
let env = Rc::new(Environment::default());
// Cause data stack underflow for unwind
let (m, r) = try_eval(&env, vec![Instruction::Drop.into()], vec![]);
let e = r.unwrap_err();
assert_eq!(e.location.map(|a| a.offset), Some(1));
assert!(matches!(e.error, MachineError::DataStackUnderflow));
assert!(m.data_stack.is_empty());
assert!(m.call_stack.is_empty());
}
#[test]
fn test_closure_call_no_upvalues_yes_locals() {
let env = Rc::new(Environment::default());
// (lambda (y) (let (x 123) (+ x y)))
let lambda_function = Rc::new(BytecodeFunction {
script: None,
identifier: None,
docstring: None,
instructions: [
// x 123
Instruction::PushInteger.into(),
123,
0,
Instruction::GetLocal.into(),
0,
Instruction::GetLocal.into(),
1,
Instruction::Add.into(),
2,
Instruction::SetTemp.into(),
Instruction::Drop.into(),
Instruction::GetTemp.into(),
Instruction::Return.into(),
]
.into(),
constants: [].into(),
upvalues: [].into(),
required_count: 1,
optional_count: 0,
has_rest: false,
});
let (m, r) = eval0(
&env,
vec![
Instruction::PushConstant.into(),
0,
0,
Instruction::PushInteger.into(),
65,
1,
Instruction::Call.into(),
1,
],
vec![Value::Function(lambda_function)],
);
assert!(m.data_stack.is_empty());
assert!(m.call_stack.is_empty());
assert_eq!(r, Value::Number(NumberValue::Int(444)));
}
#[test]
fn test_closure_call_no_upvalues_no_locals() {
let env = Rc::new(Environment::default());
// (lambda (x y) (+ x y))
let lambda_function = Rc::new(BytecodeFunction {
script: None,
identifier: None,
docstring: None,
instructions: [
Instruction::GetLocal.into(),
0,
Instruction::GetLocal.into(),
1,
Instruction::Add.into(),
2,
Instruction::Return.into(),
]
.into(),
constants: [].into(),
upvalues: [].into(),
required_count: 2,
optional_count: 0,
has_rest: false,
});
let (m, r) = eval0(
&env,
vec![
Instruction::PushConstant.into(),
0,
0,
Instruction::PushInteger.into(),
123,
0,
Instruction::PushInteger.into(),
65,
1,
Instruction::Call.into(),
2,
],
vec![Value::Function(lambda_function)],
);
assert!(m.data_stack.is_empty());
assert!(m.call_stack.is_empty());
assert_eq!(r, Value::Number(NumberValue::Int(444)));
}
}
+124
View File
@@ -0,0 +1,124 @@
use std::rc::Rc;
use crate::{
error::MachineErrorAt,
vm::{
env::{Environment, Macro},
machine::Machine,
value::{ClosureValue, ConsCell, Value},
},
};
pub trait MacroExpand: Sized {
fn macro_expand(
&self,
vm: &mut Machine,
env: &Rc<Environment>,
tail: bool,
) -> Result<Self, MachineErrorAt>;
}
impl MacroExpand for Value {
fn macro_expand(
&self,
vm: &mut Machine,
env: &Rc<Environment>,
tail: bool,
) -> Result<Self, MachineErrorAt> {
match self {
Self::Nil
| Self::Identifier(_)
| Self::Number(_)
| Self::Boolean(_)
| Self::String(_)
| Self::Keyword(_)
| Self::Quote(_)
| Self::Closure(_)
| Self::Function(_)
| Self::NativeFunction(_)
| Self::NativeValue(_)
| Self::Vector(_)
| Self::HashTable(_)
| Self::UnquoteSplice(_)
| Self::Unquote(_) => Ok(self.clone()),
Self::Cons(cons) => {
let ConsCell(car, cdr) = cons.as_ref();
let car = car.macro_expand(vm, env, false)?;
let cdr = cdr.macro_expand(vm, env, true)?;
if tail {
let cons = Rc::new(ConsCell(car, cdr.clone()));
return Ok(Self::Cons(cons));
}
let (Self::Identifier(identifier), Some(args)) = (&car, cdr.as_proper_list())
else {
let cons = Rc::new(ConsCell(car, cdr.clone()));
return Ok(Self::Cons(cons));
};
let Some(mac) = env.global_macro(identifier) else {
// eprintln!("{identifier} is not a macro");
let cons = Rc::new(ConsCell(car, cdr.clone()));
return Ok(Self::Cons(cons));
};
match mac {
Macro::Native(native) => {
let result = native.invoke(vm, env, &args[..]).expect("TODO");
result.macro_expand(vm, env, tail)
}
Macro::Bytecode(bytecode) => {
let closure = ClosureValue {
function: bytecode.clone(),
upvalues: vec![],
};
let result = vm.evaluate_closure_args(env, closure, &args[..])?;
result.macro_expand(vm, env, tail)
}
}
}
Self::Quasi(value) => {
let value = expand_quasiquote(value);
value.macro_expand(vm, env, false)
}
}
}
}
fn expand_quasiquote(value: &Value) -> Value {
match value {
Value::Nil => Value::Nil,
// Toplevel-only
Value::Unquote(inner) => inner.as_ref().clone(),
Value::UnquoteSplice(inner) => inner.as_ref().clone(),
Value::Cons(_) => {
let mut elements = vec![];
let mut current = value;
elements.push(Value::Identifier("append".into()));
while !current.is_nil() {
let Value::Cons(cons) = current else { todo!() };
let ConsCell(car, cdr) = cons.as_ref();
match car {
Value::UnquoteSplice(splice) => {
elements.push(splice.as_ref().clone());
}
_ => {
let exp_car = expand_quasiquote(car);
elements.push(Value::list_or_nil([
Value::Identifier("list".into()),
exp_car,
]));
}
}
current = cdr;
}
Value::list_or_nil(elements)
}
_ => value.clone().quote(),
}
}
+9
View File
@@ -0,0 +1,9 @@
pub mod env;
pub mod instruction;
pub mod machine;
pub mod macros;
pub mod prelude;
pub mod stack;
pub mod value;
pub use value::Value;
@@ -0,0 +1,374 @@
use std::{rc::Rc, slice};
use crate::{
error::{MachineError, ValueConversionError},
vm::{
Value,
env::Environment,
value::{
ConsCell, HashTable, HashTableData,
convert::{AnyFunction, TryFromValue},
},
},
};
pub fn load(env: &Rc<Environment>) {
// // vectors
// env.defun_native("getv", |vm, _, args| {
// let [vec, index] = args else {
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
// };
// let Value::Vector(vec) = vec else {
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
// };
// let index = i64::try_from_value(index).map_err(|e| vm.error_at_ip(e))? as isize;
// let value = if index < 0 {
// todo!()
// } else {
// vec.value_at(index as usize).unwrap_or(Value::Nil)
// };
// Ok(value)
// });
// env.defun_native("setv", |vm, _, args| {
// let [vec, index, value] = args else {
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
// };
// let Value::Vector(vec) = vec else {
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
// };
// let index = i64::try_from_value(index).map_err(|e| vm.error_at_ip(e))? as isize;
// if index < 0 {
// todo!()
// } else {
// vec.set_value_at(index as usize, value.clone());
// }
// Ok(Value::Nil)
// });
// lists
env.defun_native(
"append",
"Concatenates the lists into one list",
|_, _, args| match args {
[] => Ok(Value::Nil),
[xs] => Ok(xs.clone()),
[head @ .., tail] => {
let mut elements = vec![];
for arg in head {
let iter = arg.proper_iter(ValueConversionError {
expected: "proper list".into(),
got: arg.clone(),
});
for element in iter {
elements.push(element?.clone());
}
}
let mut output = tail.clone();
for element in elements.into_iter().rev() {
output = element.cons(output);
}
Ok(output)
}
},
);
env.defun_native(
"car",
"Returns the CAR value of a cons-cell",
|_, _, args| {
let [x] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let cons: &ConsCell = TryFromValue::try_from_value(x)?;
Ok(cons.0.clone())
},
);
env.defun_native(
"cdr",
"Returns the CDR value of a cons-cell",
|_, _, args| {
let [x] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let cons: &ConsCell = TryFromValue::try_from_value(x)?;
Ok(cons.1.clone())
},
);
env.defun_native(
"cons",
"Constructs a cons-cell from CAR and CDR arguments",
|_, _, args| {
let [car, cdr] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(Value::Cons(Rc::new(ConsCell(car.clone(), cdr.clone()))))
},
);
env.defun_native(
"list",
"Constructs a list from the arguments",
|_, _, args| {
let out = Value::list_or_nil(args.iter().cloned());
Ok(out)
},
);
env.defun_native(
"length",
"Returns the length of the given list",
|_, _, args| {
let [xs] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let mut xs = xs;
let mut count = 0;
while !xs.is_nil() {
let Value::Cons(cons) = xs else {
break;
};
let ConsCell(_, cdr) = cons.as_ref();
count += 1;
xs = cdr;
}
Ok(count.into())
},
);
env.defun_native(
"find",
"Finds an entry in the list matching given predicate",
|vm, env, args| {
let [predicate, list] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let predicate = AnyFunction::try_from_value(predicate)?;
let mut list = list;
while !list.is_nil() {
let Value::Cons(cons) = list else {
break;
};
let pred_value = predicate.invoke(vm, env, slice::from_ref(&cons.0))?;
if pred_value.is_trueish() {
return Ok(cons.0.clone());
}
list = &cons.1;
}
Ok(Value::Nil)
},
);
// Hash table
env.defun_native(
"hash/new",
"Creates a hash table from the list of pairs",
|_, _, args| {
let mut hash = HashTableData::new(16);
for arg in args {
let Value::Cons(cons) = arg else {
return Err(MachineError::ValueConversion(ValueConversionError {
expected: "a pair".into(),
got: arg.clone(),
}));
};
let ConsCell(car, cdr) = cons.as_ref();
hash.insert(car.clone(), cdr.clone())?;
}
let hash = HashTable::from(hash);
Ok(hash.into())
},
);
env.defun_native(
"hash/length",
"Returns the number of associations in the hashtable",
|_, _, args| {
let [table] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let len = table.borrow().len();
Ok(len.into())
},
);
env.defun_native(
"hash->list",
"Converts a hashtable into a list of pairs",
|_, _, args| {
let [table] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let mut list = Value::Nil;
for (key, value) in table.borrow().iter() {
list = key.clone().cons(value.clone()).cons(list);
}
Ok(list)
},
);
env.defun_native(
"list->hash",
"Converts a list of pairs into a hashtable",
|_, _, args| {
let [pairs] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let mut hash = HashTableData::new(16);
let pair_iter =
pairs.proper_iter(MachineError::ValueConversion(ValueConversionError {
expected: "a list of pairs".into(),
got: pairs.clone(),
}));
for pair in pair_iter {
let pair = pair?;
let Value::Cons(cons) = pair else {
return Err(MachineError::ValueConversion(ValueConversionError {
expected: "a pair".into(),
got: pair.clone(),
}));
};
let ConsCell(car, cdr) = cons.as_ref();
hash.insert(car.clone(), cdr.clone())?;
}
let hash = HashTable::from(hash);
Ok(hash.into())
},
);
env.defun_native(
"hash/put!",
"Inserts an association into the hashtable",
|_, _, args| {
let [table, key, value] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let value = table.borrow_mut().insert(key.clone(), value.clone())?;
Ok(value)
},
);
env.defun_native(
"hash/update!",
"Applies a v -> v' function to the value referencing the key",
|vm, env, args| {
let [function, table, key] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let function = AnyFunction::try_from_value(function)?;
let value = {
let mut borrow = table.borrow_mut();
match borrow.get_mut(key) {
Some(old_value) => {
let new_value = function.invoke(vm, env, slice::from_ref(old_value))?;
*old_value = new_value.clone();
new_value
}
None => {
let new_value = function.invoke(vm, env, &[Value::Nil])?;
borrow.insert(key.clone(), new_value.clone())?;
new_value
}
}
};
Ok(value)
},
);
env.defun_native(
"hash/remove!",
"Removes an association from the hashtable",
|_, _, args| {
let [table, key] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let value = table.borrow_mut().remove(key).unwrap_or(Value::Nil);
Ok(value)
},
);
env.defun_native(
"hash/for-each",
"Applies a (k, v) -> void function to all associations in the hashtable",
|vm, env, args| {
let [function, table] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let function = AnyFunction::try_from_value(function)?;
for (key, value) in table.borrow().iter() {
function.invoke(vm, env, &[key.clone(), value.clone()])?;
}
Ok(Value::Nil)
},
);
env.defun_native(
"hash/fold",
"Applies a (acc, k, v) -> acc' function to all associations in the hashtable",
|vm, env, args| {
let [acc, function, table] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let function = AnyFunction::try_from_value(function)?;
let mut acc = acc.clone();
for (key, value) in table.borrow().iter() {
acc = function.invoke(vm, env, &[acc, key.clone(), value.clone()])?;
}
Ok(acc)
},
);
env.defun_native(
"hash/filter!",
"Removes associations not matching the predicate from the hashtable",
|vm, env, args| {
let [predicate, table] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let predicate = AnyFunction::try_from_value(predicate)?;
table.borrow_mut().retain_invoke(vm, env, &predicate)?;
Ok(Value::Nil)
},
);
env.defun_native(
"hash/map!",
"Applies a (k, v) -> v' transform on the hashtable",
|vm, env, args| {
let [transform, table] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let transform = AnyFunction::try_from_value(transform)?;
{
let mut borrow = table.borrow_mut();
borrow.iter_mut().try_for_each(|(key, value)| {
let output = transform.invoke(vm, env, &[key.clone(), value.clone()])?;
*value = output;
Ok::<_, MachineError>(())
})?;
}
Ok(Value::Nil)
},
);
env.defun_native(
"hash/get",
"Retrieves a value from the hashtable associated with given key",
|_, _, args| {
let (table, key, default) = match args {
[table, key] => (table, key, &Value::Nil),
[table, key, default] => (table, key, default),
_ => return Err(MachineError::InvalidArgumentCount),
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
let value = table.borrow().get(key).unwrap_or(default).clone();
Ok(value)
},
);
env.defun_native(
"hash/has?",
"Returns #t if the hashtable contains the key",
|_, _, args| {
let [table, key] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
Ok(table.borrow().contains_key(key).into())
},
);
}
@@ -0,0 +1,150 @@
use std::rc::Rc;
use crate::{
error::MachineError,
vm::{
Value,
env::Environment,
value::{NumberValue, StringValue, convert::TryFromValue},
},
};
pub fn load(env: &Rc<Environment>) {
env.defun_native(
"symbol?",
"Returns #t if the argument is an identifier",
|_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(matches!(arg, Value::Identifier(_)).into())
},
);
env.defun_native(
"quote?",
"Returns #t if the argument is a quote",
|_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(matches!(arg, Value::Quote(_)).into())
},
);
env.defun_native(
"list?",
"Returns #t if the argument is a list",
|_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(matches!(arg, Value::Cons(_) | Value::Nil).into())
},
);
env.defun_native(
"cons?",
"Returns #t if the argument is a cons pair",
|_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(matches!(arg, Value::Cons(_)).into())
},
);
env.defun_native(
"number?",
"Returns #t if the argument is a number",
|_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(matches!(arg, Value::Number(_)).into())
},
);
env.defun_native(
"string?",
"Returns #t if the argument is a string",
|_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(matches!(arg, Value::String(_)).into())
},
);
env.defun_native("nil?", "Returns #t if the argument is nil", |_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(matches!(arg, Value::Nil).into())
});
env.defun_native(
"symbol",
"Constructs a symbol from a string",
|_, _, args| {
let [string] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
Ok(Value::Identifier((&*string).into()))
},
);
env.defun_native(
"->string",
"Converts a value to string representation",
|_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
match arg {
Value::String(_) => Ok(arg.clone()),
_ => Ok(Value::String(format!("{arg}").into())),
}
},
);
env.defun_native(
"string->number",
"Converts a string to a number with optional radix (default=10)",
|_, _, args| {
let (string, radix) = match args {
[string] => (StringValue::try_from_value(string)?, 10),
[string, radix] => (
StringValue::try_from_value(string)?,
i64::try_from_value(radix)?,
),
_ => return Err(MachineError::InvalidArgumentCount),
};
let Ok(radix) = u32::try_from(radix) else {
return Ok(Value::Nil);
};
match NumberValue::from_str_radix(string.as_ref(), radix) {
Some(value) => Ok(Value::Number(value)),
None => Ok(Value::Nil),
}
},
);
// // conversion
// env.defun_native("string->int", |vm, _, args| {
// let [arg] = args else {
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
// };
// let arg = ValueString::try_from_value(arg).map_err(|e| vm.error_at_ip(e))?;
// let result = arg.parse::<i64>().map(Value::Integer).unwrap_or(Value::Nil);
// Ok(result)
// });
// env.defun_native("int->string", |vm, _, args| {
// let [arg] = args else {
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
// };
// let arg = i64::try_from_value(arg).map_err(|e| vm.error_at_ip(e))?;
// let result = Value::String(format!("{arg}").into());
// Ok(result)
// });
// env.defun_native("type", |vm, _, args| {
// let [arg] = args else {
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
// };
// Ok(arg.typeid())
// });
}
+175
View File
@@ -0,0 +1,175 @@
use std::{
path::{Path, PathBuf},
rc::Rc,
};
use crate::{
error::MachineError,
vm::{
Value,
env::{Environment, Macro},
value::{Keyword, StringValue, convert::TryFromValue},
},
};
pub fn load(env: &Rc<Environment>) {
env.defun_native(
"import",
"Evaluates the given script file",
|vm, env, args| {
if args.is_empty() {
return Err(MachineError::InvalidArgumentCount);
}
let caller = vm
.read_call_stack(0)
.ok_or(MachineError::CallStackUnderflow)?;
let caller_directory = caller
.closure
.function
.script
.as_ref()
.and_then(|path| path.parent())
.map(Path::to_owned)
.unwrap_or_else(|| PathBuf::from("."));
for arg in args {
let path_str = StringValue::try_from_value(arg)?;
let mut path = PathBuf::from(&*path_str);
if path.is_relative() {
let in_caller_directory = caller_directory.join(&path);
if in_caller_directory.exists() {
path = in_caller_directory;
}
}
vm.load_file(Default::default(), env, &path)
.map_err(|error| MachineError::LoadError(path_str, error.into()))?;
}
Ok(Value::Nil)
},
);
env.defun_native("gensym", "Provides an unique symbol name", |_, env, _| {
Ok(env.gensym().into())
});
env.defmacro_native(
"explain",
"Provides an explanation for a given value",
|_, env, args| {
let [argument] = args else {
return Err(MachineError::InvalidArgumentCount);
};
if let Value::Identifier(identifier) = argument
&& let Some(mac) = env.global_macro(identifier)
{
return match mac {
Macro::Native(mac) => Ok(Value::String(
format!("built-in macro {}: {}", mac.name(), mac.docstring()).into(),
)),
Macro::Bytecode(mac) => Ok(Value::String(
format!("macro {}: {}", mac.name(), mac.docstring()).into(),
)),
};
}
Ok(Value::list_or_nil([
Value::Identifier("explain_".into()),
argument.clone(),
]))
},
);
env.defun_native(
"explain_",
"Provides an explanation for a given value",
|_, _, args| {
let [argument] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let explanation = match argument {
Value::Nil => "an empty list".into(),
Value::Cons(_) => "a cons-cell".into(),
Value::Number(_) => "a number".into(),
Value::Boolean(_) => "a boolean value".into(),
Value::Quasi(_) => "a quasi-quoted value".into(),
Value::Quote(_) => "a quoted value".into(),
Value::Unquote(_) => "an unquoted value".into(),
Value::UnquoteSplice(_) => "an unquote-spliced value".into(),
Value::Identifier(identifier) => format!("an identifier {:?}", identifier.as_ref()),
Value::Vector(_) => "a vector".into(),
Value::HashTable(_) => "a hash table".into(),
Value::String(_) => "a string".into(),
Value::Keyword(_) => "a keyword".into(),
Value::Closure(closure) => {
format!(
"function {}: {}",
closure.function.name(),
closure.function.docstring()
)
}
Value::Function(function) => {
format!("function {}: {}", function.name(), function.docstring())
}
Value::NativeFunction(function) => {
format!(
"built-in function {}: {}",
function.name(),
function.docstring()
)
}
Value::NativeValue(_) => "a native value".into(),
};
Ok(Value::String(explanation.into()))
},
);
env.defun_native(
"type",
"Returns the data type of the argument",
|_, _, args| {
let [argument] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(argument.type_id())
},
);
env.defmacro_native(
"assert",
"Checks that the condition is true, otherwise aborts the execution",
|vm, _, args| match args {
[] => Err(MachineError::InvalidArgumentCount),
[cond] => {
let assertion_failure_msg = match vm.current_location() {
Some(ip) => format!("{ip}: assertion failed: {cond}"),
None => format!("<undefined>: assertion failed: {cond}"),
};
let assertion_failure = Value::list_or_nil([
Value::Identifier("abort".into()),
Value::String(assertion_failure_msg.into()),
]);
Ok(Value::list_or_nil([
Value::Keyword(Keyword::If),
cond.clone(),
Value::Nil,
assertion_failure,
]))
}
_ => Err(MachineError::InvalidArgumentCount),
},
);
env.defun_native(
"abort",
"Aborts the execution with the given reason message",
|_, _, args| {
let mut message = String::new();
for (i, arg) in args.iter().enumerate() {
if i != 0 {
message.push(' ');
}
message.push_str(&format!("{arg}"));
}
Err(MachineError::Abort(message.into()))
},
);
}
+190
View File
@@ -0,0 +1,190 @@
use std::rc::Rc;
use crate::{
error::MachineError,
read::{self, InteractiveReader},
vm::{
Value,
env::Environment,
value::{IdentifierValue, NativeObject, StringValue, convert::TryFromValue},
},
};
pub fn load(env: &Rc<Environment>) {
env.defun_native(
"get",
"Reads the value of a global variable",
|_, env, args| {
let (symbol, default) = match args {
[symbol] => (symbol, &Value::Nil),
[symbol, default] => (symbol, default),
_ => todo!(),
};
let symbol = IdentifierValue::try_from_value(symbol)?;
match env.global_value(&symbol) {
Some(value) => Ok(value.clone()),
None => Ok(default.clone()),
}
},
);
env.defun_native(
"set",
"Sets the value of a global variable",
|_, env, args| {
let [symbol, value] = args else { todo!() };
let symbol = match symbol {
Value::Identifier(identifier) => identifier,
Value::Quote(quoted) if let Value::Identifier(identifier) = quoted.as_ref() => {
identifier
}
_ => todo!(),
};
env.set_global_value(symbol.clone(), value.clone())?;
Ok(Value::Nil)
},
);
env.defun_native("eval", "Evaluates the given expression", |vm, env, args| {
let (env, value) = match args {
[value] => (env.clone(), value),
[env, value] => (env.as_native()?, value),
_ => return Err(MachineError::InvalidArgumentCount),
};
let outcome =
match vm.evaluate_value(Default::default(), Some("eval".into()), &env, value.clone()) {
Ok(result) => Value::list_or_nil([Value::Identifier("ok".into()), result]),
Err(error) => Value::list_or_nil([
Value::Identifier("err".into()),
Value::String(format!("{error}").into()),
]),
};
Ok(outcome)
});
env.defun_native(
"read-eval",
"Evaluates the given expression, passed as a string",
|vm, env, args| {
let (env, input) = match args {
[input] => (env.clone(), input),
[env, input] => (env.as_native()?, input),
_ => return Err(MachineError::InvalidArgumentCount),
};
let input = StringValue::try_from_value(input)?;
let mut input = String::from(&*input);
input.push('\n');
let outcome =
match vm.evaluate_str(Default::default(), Some("eval".into()), &env, &input) {
Ok(result) => Value::list_or_nil([Value::Identifier("ok".into()), result]),
Err(error) => Value::list_or_nil([
Value::Identifier("err".into()),
Value::String(format!("{error}").into()),
]),
};
Ok(outcome)
},
);
env.defun_native("env/create", "Create a new environment", |_, env, args| {
let parent = match args {
[] => Some(env.clone()),
[env] if env.is_nil() => None,
[env] => Some(env.as_native()?),
_ => return Err(MachineError::InvalidArgumentCount),
};
let environment: Rc<dyn NativeObject> = Rc::new(Environment::new(parent));
Ok(Value::NativeValue(environment.into()))
});
env.defun_native(
"env/load-prelude",
"Adds prelude definitions to the environment",
|_, _, args| {
let [env] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let env = env.as_native::<Environment>()?;
super::load(&env);
Ok(Value::Nil)
},
);
env.defun_native(
"read",
"Reads a form from standard input",
|vm, env, _args| {
let mut reader = InteractiveReader::new("> ", ">> ");
let value = read::read(&mut reader, vm, env);
let value = match value {
Ok(Some(value)) => {
Value::list_or_nil([Value::Identifier("ok".into()), value]).quote()
}
Ok(None) => Value::Nil,
Err(error) => Value::list_or_nil([
Value::Identifier("err".into()),
Value::String(format!("{error}").into()),
])
.quote(),
};
Ok(value)
},
);
env.defun_native(
"unquote",
"For quoted values, strips the quote and returns the inner value",
|_, _, args| {
let [x] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let unquoted = x.unquote()?;
Ok(unquoted)
},
);
env.defun_native(
"print",
"Prints the arguments to the standard output",
|_, _, args| {
for (i, arg) in args.iter().enumerate() {
if i != 0 {
print!(" ");
}
if let Value::String(string) = arg {
print!("{}", &**string);
} else {
print!("{arg}");
}
}
println!();
Ok(Value::Nil)
},
);
env.defun_native(
"eprint",
"Prints the arguments to the standard error output",
|_, _, args| {
for (i, arg) in args.iter().enumerate() {
if i != 0 {
eprint!(" ");
}
if let Value::String(string) = arg {
eprint!("{}", &**string);
} else {
eprint!("{arg}");
}
}
eprintln!();
Ok(Value::Nil)
},
);
env.defun_native("error", "Raises an error condition", |_, _, args| {
let mut message = String::new();
for (i, arg) in args.iter().enumerate() {
if i != 0 {
message.push(' ');
}
if let Value::String(string) = arg {
message.push_str(&format!("{}", *string));
} else {
message.push_str(&format!("{arg}"));
}
}
Err(MachineError::Raised(message))
});
}
@@ -0,0 +1,36 @@
use std::rc::Rc;
use crate::{
error::{MachineError, ValueConversionError},
vm::{
env::Environment,
value::convert::{AnyFunction, TryFromValue},
},
};
pub fn load(env: &Rc<Environment>) {
env.defun_native(
"apply",
"Applies the function to a given argument list",
|vm, env, args| {
let [f, xs] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let f = AnyFunction::try_from_value(f)?;
let args = xs
.proper_iter(MachineError::ValueConversion(ValueConversionError {
expected: "proper list".into(),
got: xs.clone(),
}))
.map(|x| x.cloned())
.collect::<Result<Vec<_>, _>>()?;
f.invoke(vm, env, &args[..])
},
);
env.defun_native("identity", "Returns the argument as is", |_, _, args| {
let [arg] = args else {
return Err(MachineError::InvalidArgumentCount);
};
Ok(arg.clone())
});
}
+237
View File
@@ -0,0 +1,237 @@
use std::{
any::Any,
cell::RefCell,
fmt,
fs::{self, File},
io::{Read, Write, stdin, stdout},
path::Path,
rc::Rc,
};
use crate::{
error::{MachineError, ValueConversionError},
vm::{
Value,
env::Environment,
value::{NativeObject, NativeValue, StringValue, convert::TryFromValue},
},
};
#[derive(Debug)]
enum Stream {
Stdin,
Stdout,
File(RefCell<Option<File>>),
}
impl NativeObject for Stream {
fn as_any(&self) -> &dyn Any {
self
}
}
impl fmt::Display for Stream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("stream")
}
}
impl Stream {
pub fn read(&self, amount: usize) -> Option<Value> {
let mut output = vec![];
let mut remaining = amount;
let mut buffer = [0; 4096];
while remaining != 0 {
let want = remaining.min(buffer.len());
let len = match self {
Self::Stdin => stdin().read(&mut buffer[..want]).ok()?,
Self::File(cell) => match &mut *cell.borrow_mut() {
Some(file) => file.read(&mut buffer[..want]).ok()?,
None => return None,
},
Self::Stdout => return None,
};
if len == 0 {
break;
}
output.extend_from_slice(&buffer[..len]);
remaining -= len;
}
Some(output.into())
}
fn write(&self, bytes: &[u8]) -> Option<Value> {
let len = match self {
Self::Stdin => return None,
Self::Stdout => stdout().write(bytes).ok()?,
Self::File(cell) => match &mut *cell.borrow_mut() {
Some(file) => file.write(bytes).ok()?,
None => return None,
},
};
Some(Value::Number(len.into()))
}
}
pub fn load(env: &Rc<Environment>) {
let stdin: Rc<dyn NativeObject> = Rc::new(Stream::Stdin);
let stdout: Rc<dyn NativeObject> = Rc::new(Stream::Stdout);
// FS API
env.defun_native(
"fs/exists?",
"Returns #t if the path exists",
|_, _, args| {
let [path] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let path = StringValue::try_from_value(path)?;
let path: &Path = (*path).as_ref();
Ok(Value::Boolean(path.exists().into()))
},
);
env.defun_native(
"fs/directory?",
"Returns #t if the path is a directory",
|_, _, args| {
let [path] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let path = StringValue::try_from_value(path)?;
let path: &Path = (*path).as_ref();
Ok(Value::Boolean(path.is_dir().into()))
},
);
env.defun_native(
"fs/file?",
"Returns #t if the path is a file",
|_, _, args| {
let [path] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let path = StringValue::try_from_value(path)?;
let path: &Path = (*path).as_ref();
Ok(Value::Boolean(path.is_file().into()))
},
);
env.defun_native(
"fs/read-to-string",
"Reads the file into a string",
|_, _, args| {
let [path] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let path = StringValue::try_from_value(path)?;
// TODO error handling
match fs::read_to_string(&*path) {
Ok(data) => Ok(Value::String(data.into())),
Err(_error) => Ok(Value::Nil),
}
},
);
env.defun_native(
"fs/read-to-vector",
"Reads the file into a byte vector",
|_, _, args| {
let [path] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let path = StringValue::try_from_value(path)?;
// TODO error handling
match fs::read(&*path) {
Ok(data) => Ok(Value::Vector(Rc::new(data.into()))),
Err(_error) => Ok(Value::Nil),
}
},
);
env.defun_native(
"fs/home-directory",
"Returns the path to user's home directory",
|_, _, _| {
if let Some(dir) = cross::path::home_dir() {
Ok(Value::String(format!("{}", dir.display()).into()))
} else {
Ok(Value::Nil)
}
},
);
// Stream API
env.set_global_value("stream/stdin", NativeValue::from(stdin))
.expect("set_global_value failed");
env.set_global_value("stream/stdout", NativeValue::from(stdout))
.expect("set_global_value failed");
env.defun_native("stream/open", "Opens a file", |_, _, args| {
let [path] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let path = StringValue::try_from_value(path)?;
match File::open(&*path) {
Ok(file) => {
let object: Rc<dyn NativeObject> = Rc::new(Stream::File(RefCell::new(Some(file))));
Ok(Value::NativeValue(object.into()))
}
Err(error) => {
eprintln!("TODO: not sure whether to use result/exceptions");
eprintln!("{path}: {error}");
todo!()
}
}
});
env.defun_native(
"stream/read",
"Reads data as a byte vector from the stream",
|_, _, args| {
let (stream, amount) = match args {
// TODO read to end instead
[stream] => (stream.as_native::<Stream>()?, 4096),
[stream, amount] => (
stream.as_native::<Stream>()?,
usize::try_from_value(amount)?,
),
_ => return Err(MachineError::InvalidArgumentCount),
};
match stream.read(amount) {
Some(value) => Ok(value),
None => todo!(),
}
},
);
env.defun_native("stream/write", "Writes data to the stream", |_, _, args| {
let [stream, data] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let stream = stream.as_native::<Stream>()?;
let result = match data {
Value::Vector(value) if let Some(bytes) = value.as_bytes() => stream.write(&bytes[..]),
Value::String(value) => stream.write(value.as_bytes()),
_ => {
return Err(MachineError::ValueConversion(ValueConversionError {
expected: "byte vector or string".into(),
got: data.clone(),
}));
}
};
match result {
Some(value) => Ok(value),
None => todo!(),
}
});
env.defun_native("stream/close", "Closes the stream", |_, _, args| {
let [stream] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let stream = stream.as_native::<Stream>()?;
if let Stream::File(cell) = stream.as_ref() {
cell.replace(None);
}
Ok(Value::Nil)
});
}
+360
View File
@@ -0,0 +1,360 @@
use std::{cmp::Ordering, ops::Mul, rc::Rc};
use crate::{
error::MachineError,
vm::{
env::Environment,
instruction::Instruction,
machine::Machine,
value::{NumberValue, Value, convert::TryFromValue},
},
};
#[allow(clippy::type_complexity)]
pub(crate) fn dispatch_arithmetic(
instruction: Instruction,
) -> fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> {
match instruction {
Instruction::Add => builtin_add,
Instruction::Sub => builtin_sub,
Instruction::Mul => builtin_mul,
Instruction::Div => builtin_div,
Instruction::Mod => builtin_mod,
Instruction::Gt => builtin_cmp_gt,
Instruction::Lt => builtin_cmp_lt,
Instruction::Ge => builtin_cmp_ge,
Instruction::Le => builtin_cmp_le,
Instruction::Eq => builtin_cmp_eq,
Instruction::Ne => builtin_cmp_ne,
Instruction::Not => builtin_not,
_ => unreachable!(),
}
}
pub(super) fn load(env: &Rc<Environment>) {
env.defun_native("+", "Adds values", builtin_add);
env.defun_native("-", "Subtracts values", builtin_sub);
let mul = env.defun_native("*", "Multiplies values", builtin_mul);
env.set_global_value("·", mul)
.expect("set_global_value failed"); // alias for *
let div = env.defun_native("/", "Divides values", builtin_div);
env.set_global_value("÷", div)
.expect("set_global_value failed"); // alias for /
env.defun_native(
"%",
"Calculates a modulo/remainder of value divison",
builtin_mod,
);
env.defun_native(
">",
"Returns #t if the arguments are monotonically decreasing",
builtin_cmp_gt,
);
let ge = env.defun_native(
">=",
"Returns #t if the arguments are monotonically non-increasing",
builtin_cmp_ge,
);
env.set_global_value("", ge)
.expect("set_global_value failed"); // alias for >=
env.defun_native(
"<",
"Returns #t if the arguments are monotonically increasing",
builtin_cmp_lt,
);
let le = env.defun_native(
"<=",
"Returns #t if the arguments are monotonically non-decreasing",
builtin_cmp_le,
);
env.set_global_value("", le)
.expect("set_global_value failed"); // alias for <=
env.defun_native(
"=",
"Returns #t if all the arguments are equal",
builtin_cmp_eq,
);
let ne = env.defun_native(
"/=",
"Returns #t if none of the arguments are equal",
builtin_cmp_ne,
);
env.set_global_value("", ne)
.expect("set_global_value failed"); // alias for /=
// env.defun_native("&", builtin_bitwise_and);
// env.defun_native("|", builtin_bitwise_or);
// env.defun_native("^", builtin_bitwise_xor);
}
fn value_add(a: &Value, b: &Value) -> Result<Value, MachineError> {
match (a, b) {
(Value::String(a), Value::String(b)) => {
Ok(Value::String(format!("{}{}", &**a, &**b).into()))
}
(Value::String(a), _) => Ok(Value::String(format!("{}{b}", &**a).into())),
(_, Value::String(b)) => Ok(Value::String(format!("{a}{}", &**b).into())),
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(*a + *b)),
(Value::Number(a), _) => Ok(Value::Number(*a + NumberValue::try_from_value(b)?)),
(_, Value::Number(b)) => Ok(Value::Number(NumberValue::try_from_value(a)? + *b)),
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Number(*a + *b)),
_ => todo!("TODO error"),
}
}
pub(crate) enum CompareOperation {
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
fn builtin_fold<F>(fold: F, mut accumulator: Value, args: &[Value]) -> Result<Value, MachineError>
where
F: Fn(&Value, &Value) -> Result<Value, MachineError>,
{
for (i, arg) in args.iter().enumerate() {
if i == 0 {
accumulator = arg.clone();
} else {
accumulator = fold(&accumulator, arg)?;
}
}
Ok(accumulator)
}
fn builtin_fold_t<'a, T, F>(
fold: F,
mut accumulator: T,
args: &'a [Value],
) -> Result<Value, MachineError>
where
F: Fn(T, T) -> T,
T: TryFromValue<'a> + Into<Value>,
{
for arg in args {
let arg = T::try_from_value(arg)?;
accumulator = fold(accumulator, arg);
}
Ok(accumulator.into())
}
pub(crate) fn builtin_add(
_vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_fold(value_add, Value::Number(0.into()), args)
}
pub(crate) fn builtin_sub(
_vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
match args {
[] => Err(MachineError::InvalidArgumentCount),
[x] => {
let x = NumberValue::try_from_value(x)?;
Ok((-x).into())
}
[x, rest @ ..] => {
let mut accumulator = NumberValue::try_from_value(x)?;
for argument in rest {
let value = NumberValue::try_from_value(argument)?;
accumulator -= value;
}
Ok(Value::Number(accumulator))
}
}
}
pub(crate) fn builtin_mul(
_vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_fold_t(Mul::mul, NumberValue::from(1), args)
}
pub(crate) fn builtin_mod(
_vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
let [x, y] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let x = NumberValue::try_from_value(x)?;
let y = NumberValue::try_from_value(y)?;
Ok(Value::Number(x % y))
}
pub(crate) fn builtin_div(
_vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
match args {
[] => Err(MachineError::InvalidArgumentCount),
[x] => {
let x = NumberValue::try_from_value(x)?;
Ok(x.reciprocal().into())
}
[x, rest @ ..] => {
let mut accumulator = NumberValue::try_from_value(x)?;
for argument in rest {
let argument = NumberValue::try_from_value(argument)?;
accumulator /= argument;
}
Ok(accumulator.into())
}
}
}
// pub(crate) fn builtin_and(
// vm: &mut Machine,
// _env: &Rc<Environment>,
// args: &[Value],
// ) -> Result<Value, MachineError> {
// builtin_fold_t(BitAnd::bitand, true, args)
// }
// pub(crate) fn builtin_or(
// vm: &mut Machine,
// _env: &Rc<Environment>,
// args: &[Value],
// ) -> Result<Value, MachineError> {
// builtin_fold_t(BitOr::bitor, false, args)
// }
// pub(crate) fn builtin_bitwise_and(
// vm: &mut Machine,
// _env: &Rc<Environment>,
// args: &[Value],
// ) -> Result<Value, MachineError> {
// builtin_fold_t(BitAnd::bitand, 1, args)
// }
// pub(crate) fn builtin_bitwise_or(
// vm: &mut Machine,
// _env: &Rc<Environment>,
// args: &[Value],
// ) -> Result<Value, MachineError> {
// builtin_fold_t(BitOr::bitor, 0, args)
// }
// pub(crate) fn builtin_bitwise_xor(
// vm: &mut Machine,
// _env: &Rc<Environment>,
// args: &[Value],
// ) -> Result<Value, MachineError> {
// builtin_fold_t(BitXor::bitxor, 0, args)
// }
pub(crate) fn builtin_cmp(
_vm: &mut Machine,
args: &[Value],
op: CompareOperation,
) -> Result<Value, MachineError> {
fn ordering(a: &Value, b: &Value) -> Option<Ordering> {
match (a, b) {
(Value::Number(a), Value::Number(b)) => PartialOrd::partial_cmp(a, b),
(Value::Boolean(a), Value::Boolean(b)) => Some(Ord::cmp(a, b)),
(Value::String(a), Value::String(b)) => Some(Ord::cmp(a, b)),
(Value::Identifier(a), Value::Identifier(b)) => Some(Ord::cmp(a, b)),
_ => None,
}
}
let mut accumulator = true;
if !args.is_empty() {
for i in 0..args.len() - 1 {
let a = &args[i];
let b = &args[i + 1];
match op {
CompareOperation::Eq => {
accumulator &= a == b;
continue;
}
CompareOperation::Ne => {
accumulator &= a != b;
continue;
}
_ => (),
}
let ord = ordering(a, b);
// No ordering is specified
let Some(ord) = ord else {
accumulator = false;
continue;
};
let check = match op {
CompareOperation::Eq => ord.is_eq(),
CompareOperation::Ne => ord.is_ne(),
CompareOperation::Lt => ord.is_lt(),
CompareOperation::Gt => ord.is_gt(),
CompareOperation::Le => ord.is_le(),
CompareOperation::Ge => ord.is_ge(),
};
accumulator &= check;
}
}
Ok(accumulator.into())
}
pub(crate) fn builtin_cmp_eq(
vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_cmp(vm, args, CompareOperation::Eq)
}
pub(crate) fn builtin_cmp_ne(
vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_cmp(vm, args, CompareOperation::Ne)
}
pub(crate) fn builtin_cmp_gt(
vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_cmp(vm, args, CompareOperation::Gt)
}
pub(crate) fn builtin_cmp_lt(
vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_cmp(vm, args, CompareOperation::Lt)
}
pub(crate) fn builtin_cmp_ge(
vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_cmp(vm, args, CompareOperation::Ge)
}
pub(crate) fn builtin_cmp_le(
vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
builtin_cmp(vm, args, CompareOperation::Le)
}
pub(crate) fn builtin_not(
_vm: &mut Machine,
_env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
let [x] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let result = match x {
Value::Boolean(value) => Value::Boolean(!*value),
_ => Value::Boolean((!x.is_trueish()).into()),
};
Ok(result)
}
+33
View File
@@ -0,0 +1,33 @@
use std::rc::Rc;
use crate::vm::{env::Environment, machine::Machine};
mod collections;
mod convert;
mod debug;
mod eval;
mod functional;
mod io;
mod math;
mod string;
pub(crate) use math::*;
const PRELUDE_SOURCE: &str = include_str!("../../prelude.lysp");
pub fn load(env: &Rc<Environment>) {
let mut vm = Machine::default();
math::load(env);
eval::load(env);
functional::load(env);
collections::load(env);
convert::load(env);
debug::load(env);
io::load(env);
string::load(env);
// Load the lysp part of the prelude
vm.evaluate_str(Default::default(), None, env, PRELUDE_SOURCE)
.expect("Couldn't evaluate prelude lysp part");
}
+145
View File
@@ -0,0 +1,145 @@
use std::rc::Rc;
use crate::{
error::MachineError,
vm::{
Value,
env::Environment,
value::{StringValue, convert::TryFromValue},
},
};
fn classify<F: Fn(char) -> bool>(args: &[Value], predicate: F) -> Result<Value, MachineError> {
let [string] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
Ok(string.chars().all(predicate).into())
}
pub fn load(env: &Rc<Environment>) {
env.defun_native(
"string/trim",
"Trims leading and trailing whitespace from a string",
|_, _, args| {
let [string] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
Ok(StringValue::from(string.trim()).into())
},
);
env.defun_native(
"string/pop",
"Removes the last character from the string",
|_, _, args| {
let [string] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let mut string = (*StringValue::try_from_value(string)?).to_owned();
string.pop();
Ok(StringValue::from(string).into())
},
);
env.defun_native(
"string/strip-prefix",
"Removes the prefix from a string. Returns NIL if string does not start with the prefix",
|_, _, args| {
let [string, prefix] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
let prefix = StringValue::try_from_value(prefix)?;
match string.strip_prefix(&*prefix) {
Some(string) => Ok(StringValue::from(string).into()),
None => Ok(Value::Nil),
}
},
);
env.defun_native(
"string/strip-suffix",
"Removes the suffix from a string. Returns NIL if string does not end with the suffix",
|_, _, args| {
let [string, suffix] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
let suffix = StringValue::try_from_value(suffix)?;
match string.strip_suffix(&*suffix) {
Some(string) => Ok(StringValue::from(string).into()),
None => Ok(Value::Nil),
}
},
);
env.defun_native(
"string/split",
"Splits a string by given separator",
|_, _, args| {
let (separator, string) = match args {
[string] => (&StringValue::from(" "), string),
[separator, string] => (&StringValue::try_from_value(separator)?, string),
_ => return Err(MachineError::InvalidArgumentCount),
};
let string = StringValue::try_from_value(string)?;
let mut items = Value::Nil;
for item in string.rsplit(&**separator) {
items = Value::String(item.into()).cons(items);
}
Ok(items)
},
);
env.defun_native(
"string/length",
"Returns the length of the string",
|_, _, args| {
let [string] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
Ok(string.len().into())
},
);
env.defun_native(
"string/to-upper",
"Converts a string to UPPERCASE",
|_, _, args| {
let [string] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
Ok(Value::String(string.to_uppercase().into()))
},
);
env.defun_native(
"string/to-lower",
"Converts a string to lowercase",
|_, _, args| {
let [string] = args else {
return Err(MachineError::InvalidArgumentCount);
};
let string = StringValue::try_from_value(string)?;
Ok(Value::String(string.to_lowercase().into()))
},
);
env.defun_native(
"string/alpha?",
"Returns #t if all the characters in the string are alphabetic",
|_, _, args| classify(args, char::is_alphabetic),
);
env.defun_native(
"string/digit?",
"Returns #t if all the characters in the string are digits (0-9)",
|_, _, args| classify(args, |ch| ch.is_ascii_digit()),
);
env.defun_native(
"string/ascii-graphic?",
"Returns #t if all the characters in the string are ASCII graphic (non-control)",
|_, _, args| classify(args, |ch| ch.is_ascii_graphic()),
);
env.defun_native(
"string/ascii?",
"Returns #t if all the characters in the string are ASCII",
|_, _, args| classify(args, |ch| ch.is_ascii()),
);
}
+228
View File
@@ -0,0 +1,228 @@
use std::{
mem::MaybeUninit,
ops::{Bound, Index, RangeBounds},
};
pub struct Stack<T> {
data: Box<[MaybeUninit<T>]>,
pointer: usize,
}
impl<T> Stack<T> {
pub fn new(limit: usize) -> Self {
assert_ne!(limit, 0);
Self {
data: Box::new_uninit_slice(limit),
pointer: 0,
}
}
pub fn is_empty(&self) -> bool {
self.pointer == 0
}
pub fn checked_slice<I: RangeBounds<usize>>(&self, range: I) -> Option<&[T]> {
let start = match range.start_bound() {
Bound::Unbounded => 0,
Bound::Included(start) | Bound::Excluded(start) => *start,
};
let end = match range.end_bound() {
Bound::Unbounded => self.pointer,
Bound::Excluded(end) => *end,
Bound::Included(end) => *end + 1,
};
if start <= self.pointer && end <= self.pointer {
Some(unsafe { self.data[start..end].assume_init_ref() })
} else {
None
}
}
pub fn checked_last(&self, n: usize) -> Option<&[T]> {
if n < self.pointer {
Some(unsafe { self.data[self.pointer - n..self.pointer].assume_init_ref() })
} else {
None
}
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if index < self.pointer {
Some(unsafe { self.data[index].assume_init_mut() })
} else {
None
}
}
pub fn nth(&self, n: usize) -> Option<&T> {
if n >= self.pointer {
None
} else {
Some(unsafe { self.data[self.pointer - n - 1].assume_init_ref() })
}
}
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.pointer {
Some(unsafe { self.data[index].assume_init_ref() })
} else {
None
}
}
pub fn head(&self) -> Option<&T> {
if self.pointer > 0 {
Some(unsafe { self.data[self.pointer - 1].assume_init_ref() })
} else {
None
}
}
pub fn head_mut(&mut self) -> Option<&mut T> {
if self.pointer > 0 {
Some(unsafe { self.data[self.pointer - 1].assume_init_mut() })
} else {
None
}
}
pub fn pointer(&self) -> usize {
self.pointer
}
pub fn set_pointer(&mut self, pointer: usize) {
if pointer < self.pointer {
// Call drop for values between
for slot in &mut self.data[pointer..self.pointer] {
unsafe { slot.assume_init_drop() };
}
} else if pointer > self.pointer {
panic!("Cannot set stack pointer higher than it currently is");
}
self.pointer = pointer;
}
pub fn pop(&mut self) -> Option<T> {
if self.pointer > 0 {
self.pointer -= 1;
let value = unsafe { self.data[self.pointer].assume_init_read() };
Some(value)
} else {
None
}
}
pub fn push(&mut self, value: T) -> Result<(), T> {
if self.pointer < self.data.len() {
self.data[self.pointer].write(value);
self.pointer += 1;
Ok(())
} else {
Err(value)
}
}
}
impl<T> Index<usize> for Stack<T> {
type Output = T;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
self.get(index).expect("stack index out of bounds")
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicBool, Ordering};
use crate::vm::stack::Stack;
#[derive(Debug)]
struct DropTracker(&'static AtomicBool);
impl Drop for DropTracker {
fn drop(&mut self) {
self.0.store(true, Ordering::Release);
}
}
#[test]
fn test_checked_slice() {
let mut s = Stack::<u32>::new(32);
s.push(1).unwrap();
s.push(2).unwrap();
s.push(3).unwrap();
s.push(4).unwrap();
s.push(5).unwrap();
// unbound..unbound
assert_eq!(s.checked_slice(..).unwrap(), &[1, 2, 3, 4, 5]);
// unbound..included
assert_eq!(s.checked_slice(..=3).unwrap(), &[1, 2, 3, 4]);
// unbound..excluded
assert_eq!(s.checked_slice(..3).unwrap(), &[1, 2, 3]);
// included..unbound
assert_eq!(s.checked_slice(2..).unwrap(), &[3, 4, 5]);
// included..included
assert_eq!(s.checked_slice(2..=3).unwrap(), &[3, 4]);
// included..excluded
assert_eq!(s.checked_slice(2..3).unwrap(), &[3]);
// Invalid
assert!(s.checked_slice(..10).is_none());
assert!(s.checked_slice(10..).is_none());
assert!(s.checked_slice(10..9).is_none());
}
#[test]
fn test_underflow() {
let mut s = Stack::<u32>::new(128);
assert!(s.pop().is_none());
}
#[test]
fn test_overflow() {
let mut s = Stack::<u32>::new(4);
for i in 0..4 {
s.push(i).unwrap();
}
assert_eq!(s.push(1234).unwrap_err(), 1234);
}
#[test]
fn test_no_drop_on_pop() {
static DROP0: AtomicBool = AtomicBool::new(false);
let mut s = Stack::<DropTracker>::new(4);
s.push(DropTracker(&DROP0)).unwrap();
assert!(!DROP0.load(Ordering::Acquire));
let v = s.pop().unwrap();
assert!(!DROP0.load(Ordering::Acquire));
drop(v);
assert!(DROP0.load(Ordering::Acquire));
}
#[test]
fn test_drop_on_set_pointer_lower() {
static DROP0: AtomicBool = AtomicBool::new(false);
static DROP1: AtomicBool = AtomicBool::new(false);
let mut s = Stack::<DropTracker>::new(4);
s.push(DropTracker(&DROP0)).unwrap();
s.push(DropTracker(&DROP1)).unwrap();
s.set_pointer(1);
assert!(!DROP0.load(Ordering::Acquire));
assert!(DROP1.load(Ordering::Acquire));
}
#[test]
fn test_stack_head() {
let mut s = Stack::<u32>::new(4);
assert!(s.head().is_none());
s.push(1234).unwrap();
assert_eq!(s.head().unwrap(), &1234);
}
}
@@ -0,0 +1,40 @@
use std::{
fmt,
ops::{Add, Not},
};
use crate::vm::value::NumberValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BooleanValue(pub bool);
impl From<bool> for BooleanValue {
fn from(value: bool) -> Self {
Self(value)
}
}
impl Not for BooleanValue {
type Output = Self;
fn not(self) -> Self::Output {
Self(!self.0)
}
}
impl Add for BooleanValue {
type Output = NumberValue;
fn add(self, rhs: Self) -> Self::Output {
NumberValue::Int(self.0 as i64 + rhs.0 as i64)
}
}
impl fmt::Display for BooleanValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
true => write!(f, "#T"),
false => write!(f, "#F"),
}
}
}
@@ -0,0 +1,80 @@
use std::{cell::RefCell, fmt, rc::Rc};
use crate::{
error::MachineError,
vm::{Value, stack::Stack, value::BytecodeFunction},
};
// #[derive(Debug, Clone, PartialEq)]
// pub enum UpvalueValue {
// Open(usize),
// Closed(Box<Value>),
// }
#[derive(Debug, PartialEq)]
pub enum Upvalue {
Open(usize),
Closed(Value),
}
#[derive(Clone, Debug, PartialEq)]
pub struct UpvalueRef(Rc<RefCell<Upvalue>>);
#[derive(Clone, PartialEq)]
pub struct ClosureValue {
pub function: Rc<BytecodeFunction>,
pub upvalues: Vec<UpvalueRef>,
}
impl UpvalueRef {
pub fn open(sp: usize) -> Self {
Self(Rc::new(RefCell::new(Upvalue::Open(sp))))
}
pub fn read(&self, stack: &Stack<Value>) -> Result<Value, MachineError> {
match &*self.0.borrow() {
&Upvalue::Open(sp) => stack
.get(sp)
.cloned()
.ok_or(MachineError::UndefinedUpvalueReference),
Upvalue::Closed(value) => Ok(value.clone()),
}
}
pub fn write(&self, stack: &mut Stack<Value>, value: Value) -> Result<(), MachineError> {
match &mut *self.0.borrow_mut() {
&mut Upvalue::Open(sp) => {
let slot = stack
.get_mut(sp)
.ok_or(MachineError::UndefinedUpvalueReference)?;
*slot = value;
}
Upvalue::Closed(slot) => *slot = value,
}
Ok(())
}
pub fn close(&self, value: Value) -> Upvalue {
self.0.replace(Upvalue::Closed(value))
}
}
impl ClosureValue {
pub fn instruction_byte(&self, address: usize) -> Option<u8> {
self.function.instructions.get(address).copied()
}
}
impl fmt::Debug for ClosureValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClosureValue")
.field("function", &self.function)
.finish_non_exhaustive()
}
}
impl fmt::Display for ClosureValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<closure {}@{:p}>", self.function.name(), self.function)
}
}
+33
View File
@@ -0,0 +1,33 @@
use std::fmt;
use crate::vm::value::Value;
#[derive(Debug, Clone, PartialEq)]
pub struct ConsCell(pub Value, pub Value);
impl ConsCell {
pub fn end(value: Value) -> Self {
Self(value, Value::Nil)
}
fn fmt_inner(&self, f: &mut fmt::Formatter<'_>, first: bool) -> fmt::Result {
let Self(car, cdr) = self;
if !first {
write!(f, " ")?;
}
write!(f, "{car}")?;
match cdr {
Value::Nil => Ok(()),
Value::Cons(cons) => cons.fmt_inner(f, false),
_ => {
write!(f, " . {cdr}")
}
}
}
}
impl fmt::Display for ConsCell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_inner(f, true)
}
}
+244
View File
@@ -0,0 +1,244 @@
use std::{fmt, rc::Rc};
use crate::{
error::{MachineError, ValueConversionError},
vm::{
Value,
env::Environment,
machine::Machine,
value::{
BooleanValue, BytecodeFunction, ClosureValue, ConsCell, HashTable, IdentifierValue,
NativeFunction, NativeValue, NumberValue, StringValue, Vector,
},
},
};
pub trait TryFromValue<'a>: Sized {
fn try_from_value(value: &'a Value) -> Result<Self, ValueConversionError>;
}
#[derive(Debug, Clone)]
pub enum AnyFunction {
Native(NativeFunction),
Function(Rc<BytecodeFunction>),
Closure(ClosureValue),
}
macro_rules! impl_integer {
($($ty:ty : $bits:expr),+ $(,)?) => {
$(
impl TryFromValue<'_> for $ty {
fn try_from_value(value: &Value) -> Result<Self, ValueConversionError> {
match value {
#[allow(irrefutable_let_patterns)]
Value::Number(value) if let Ok(value) = <$ty>::try_from(*value) => Ok(value),
_ => Err(ValueConversionError {
expected: format!("{}-bit integer", $bits),
got: value.clone()
})
}
}
}
impl From<$ty> for Value {
fn from(value: $ty) -> Value {
Value::Number(value.into())
}
}
)+
};
}
impl TryFromValue<'_> for bool {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
Value::Boolean(BooleanValue(value)) => Ok(*value),
_ => Err(ValueConversionError {
expected: "#T or #F".into(),
got: value.clone(),
}),
}
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Value::Boolean(value.into())
}
}
impl TryFromValue<'_> for NumberValue {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
Value::Number(value) => Ok(*value),
_ => Err(ValueConversionError {
expected: "integer value".into(),
got: value.clone(),
}),
}
}
}
impl From<NumberValue> for Value {
fn from(value: NumberValue) -> Self {
Self::Number(value)
}
}
impl<'a> TryFromValue<'a> for &'a ConsCell {
fn try_from_value(value: &'a Value) -> Result<Self, ValueConversionError> {
match value {
Value::Cons(cons) => Ok(cons.as_ref()),
_ => Err(ValueConversionError {
expected: "cons cell".into(),
got: value.clone(),
}),
}
}
}
impl From<Rc<Vector>> for Value {
fn from(value: Rc<Vector>) -> Self {
Self::Vector(value)
}
}
impl TryFromValue<'_> for StringValue {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
Value::String(value) => Ok(value.clone()),
_ => Err(ValueConversionError {
expected: "string".into(),
got: value.clone(),
}),
}
}
}
impl From<NativeValue> for Value {
fn from(value: NativeValue) -> Self {
Self::NativeValue(value)
}
}
impl From<Vec<u8>> for Value {
fn from(value: Vec<u8>) -> Self {
Value::Vector(Rc::new(Vector::from_iter(value)))
}
}
impl From<IdentifierValue> for Value {
fn from(value: IdentifierValue) -> Self {
Value::Identifier(value)
}
}
impl From<StringValue> for Value {
fn from(value: StringValue) -> Self {
Value::String(value)
}
}
impl TryFromValue<'_> for Rc<HashTable> {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
Value::HashTable(table) => Ok(table.clone()),
_ => Err(ValueConversionError {
expected: "hash".into(),
got: value.clone(),
}),
}
}
}
impl From<HashTable> for Value {
fn from(value: HashTable) -> Self {
Value::HashTable(Rc::new(value))
}
}
impl TryFromValue<'_> for IdentifierValue {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
Value::Identifier(value) => Ok(value.clone()),
_ => Err(ValueConversionError {
expected: "identifier".into(),
got: value.clone(),
}),
}
}
}
impl TryFromValue<'_> for AnyFunction {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
Value::NativeFunction(function) => Ok(Self::Native(function.clone())),
Value::Closure(closure) => Ok(Self::Closure(closure.clone())),
Value::Function(function) => Ok(Self::Function(function.clone())),
_ => Err(ValueConversionError {
expected: "function".into(),
got: value.clone(),
}),
}
}
}
impl AnyFunction {
pub fn name(&self) -> IdentifierValue {
match self {
Self::Native(native) => native.name().clone(),
Self::Closure(closure) => closure.function.name().into(),
Self::Function(function) => function.name().into(),
}
}
pub fn invoke(
&self,
vm: &mut Machine,
env: &Rc<Environment>,
args: &[Value],
) -> Result<Value, MachineError> {
match self {
Self::Native(function) => function.invoke(vm, env, args),
Self::Closure(closure) => vm
.evaluate_closure_args(env, closure.clone(), args)
.map_err(|error| error.error),
Self::Function(function) => {
let closure = ClosureValue {
function: function.clone(),
upvalues: vec![],
};
vm.evaluate_closure_args(env, closure, args)
.map_err(|error| error.error)
}
}
}
}
impl From<AnyFunction> for Value {
fn from(value: AnyFunction) -> Self {
match value {
AnyFunction::Function(value) => Value::Function(value),
AnyFunction::Native(value) => Value::NativeFunction(value),
AnyFunction::Closure(value) => Value::Closure(value),
}
}
}
impl fmt::Display for AnyFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Native(value) => fmt::Display::fmt(value, f),
Self::Closure(value) => fmt::Display::fmt(value, f),
Self::Function(value) => fmt::Display::fmt(value, f),
}
}
}
impl_integer!(
i8: 8,
i16: 16,
i32: 32,
i64: 64,
usize: usize::BITS,
);
+237
View File
@@ -0,0 +1,237 @@
use std::{fmt, path::Path, rc::Rc};
use crate::{
compile::UpvalueDef,
vm::{
Value,
instruction::{BranchOffset, ConstantId, ImmediateInteger, Instruction},
value::{IdentifierValue, StringValue},
},
};
#[derive(Debug, PartialEq)]
pub struct BytecodeFunction {
pub identifier: Option<IdentifierValue>,
pub docstring: Option<StringValue>,
pub instructions: Box<[u8]>,
pub constants: Box<[Value]>,
pub upvalues: Box<[UpvalueDef]>,
pub script: Option<Rc<Path>>,
pub required_count: usize,
pub optional_count: usize,
pub has_rest: bool,
}
enum TraceArgument {
Constant,
ImmediateInteger,
BranchTarget,
Byte,
None,
}
impl fmt::Display for BytecodeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<function {}@{:p}>", self.name(), self)
}
}
impl BytecodeFunction {
pub fn name(&self) -> &str {
match self.identifier.as_ref() {
Some(name) => name.as_ref(),
None => "unnamed",
}
}
pub fn docstring(&self) -> &str {
match self.docstring.as_ref() {
Some(docstring) => docstring.as_ref(),
None => "",
}
}
pub fn script_path(&self) -> Option<&Path> {
self.script.as_deref()
}
pub fn min_arity(&self) -> usize {
self.required_count
}
pub fn rest_argument_start(&self) -> Option<usize> {
self.has_rest
.then_some(self.required_count + self.optional_count)
}
pub fn max_arity(&self) -> usize {
if self.has_rest {
usize::MAX
} else {
self.required_count + self.optional_count
}
}
fn trace_immediate_integer_at(&self, address: usize) -> Option<ImmediateInteger> {
let Some(b0) = self.instructions.get(address).copied() else {
eprint!(" <??> <??>");
return None;
};
eprint!(" {b0:02x}");
let Some(b1) = self.instructions.get(address + 1).copied() else {
eprint!(" <??>");
return None;
};
eprint!(" {b1:02x}");
Some(ImmediateInteger::from(i16::from_le_bytes([b0, b1])))
}
fn trace_branch_offset_at(&self, address: usize) -> Option<BranchOffset> {
let Some(b0) = self.instructions.get(address).copied() else {
eprint!(" <??> <??>");
return None;
};
eprint!(" {b0:02x}");
let Some(b1) = self.instructions.get(address + 1).copied() else {
eprint!(" <??>");
return None;
};
eprint!(" {b1:02x}");
Some(BranchOffset::from(i16::from_le_bytes([b0, b1])))
}
fn trace_constant_at(&self, address: usize) -> Option<Option<&Value>> {
let Some(b0) = self.instructions.get(address).copied() else {
eprint!(" <??> <??>");
return None;
};
eprint!(" {b0:02x}");
let Some(b1) = self.instructions.get(address + 1).copied() else {
eprint!(" <??>");
return None;
};
eprint!(" {b1:02x}");
let id = usize::from(ConstantId::from(u16::from_le_bytes([b0, b1])));
Some(self.constants.get(id))
}
fn trace_byte_at(&self, address: usize) -> Option<u8> {
let Some(b0) = self.instructions.get(address).copied() else {
eprint!(" <??>");
return None;
};
eprint!(" {b0:02x}");
Some(b0)
}
pub fn disassemble(&self, address: usize, before: usize, after: usize, arrow: bool) {
let start = address.saturating_sub(before);
let end = (address + after + 1).min(self.instructions.len());
let mut position = start;
while position < end {
let arrow = if arrow && position == address {
"==>"
} else {
" "
};
eprint!("{self:p}:{position:<4}{arrow} ");
let opcode = self.instructions[position];
eprint!("{opcode:02x}");
let Ok(instruction) = Instruction::try_from(opcode) else {
eprintln!(" <UNKNOWN>");
position += 1;
continue;
};
let argument = match instruction {
// Stack
Instruction::PushNil => TraceArgument::None,
Instruction::PushTrue | Instruction::PushFalse => TraceArgument::None,
Instruction::PushInteger => TraceArgument::ImmediateInteger,
Instruction::PushConstant => TraceArgument::Constant,
Instruction::SetTemp | Instruction::GetTemp => TraceArgument::None,
Instruction::SetLocal | Instruction::GetLocal => TraceArgument::Byte,
Instruction::SetGlobal | Instruction::GetGlobal => TraceArgument::None,
Instruction::SetUpvalue | Instruction::GetUpvalue => TraceArgument::Byte,
Instruction::Drop => TraceArgument::None,
Instruction::DeclareGlobal => TraceArgument::None,
Instruction::DeclareMacro => TraceArgument::None,
// Arithmetic
Instruction::Ne
| Instruction::Gt
| Instruction::Lt
| Instruction::Eq
| Instruction::Ge
| Instruction::Le
| Instruction::Add
| Instruction::Sub
| Instruction::Mul
| Instruction::Div
| Instruction::Mod
| Instruction::Not => TraceArgument::Byte,
// Function
Instruction::Call => TraceArgument::Byte,
Instruction::Return => TraceArgument::None,
Instruction::MakeClosure => TraceArgument::None,
Instruction::CloseUpvalue => TraceArgument::Byte,
// Branch
Instruction::Branch => TraceArgument::BranchTarget,
Instruction::Jump => TraceArgument::BranchTarget,
};
position += 1;
match argument {
TraceArgument::Byte => {
let Some(byte) = self.trace_byte_at(position) else {
eprintln!("\t\t{instruction} <invalid>");
break;
};
position += 1;
eprintln!("\t\t{instruction} {byte}");
}
TraceArgument::Constant => {
let Some(constant) = self.trace_constant_at(position) else {
eprintln!("\t\t{instruction} <invalid>");
break;
};
position += 2;
let Some(constant) = constant else {
eprintln!("\t\t{instruction} <invalid>");
continue;
};
eprintln!("\t\t{instruction} {constant}");
}
TraceArgument::ImmediateInteger => {
let Some(value) = self.trace_immediate_integer_at(position) else {
eprintln!("\t\t{instruction} <invalid>");
break;
};
position += 2;
eprintln!("\t\t{instruction} {}", value.sign_extend_i64());
}
TraceArgument::None => {
eprintln!("\t\t{instruction}");
}
TraceArgument::BranchTarget => {
let Some(offset) = self.trace_branch_offset_at(position) else {
eprintln!("\t\t{instruction} <invalid>");
break;
};
position += 2;
let offset = offset.sign_extend_isize();
eprintln!(
"\t\t{instruction} {:+} (-> {})",
offset,
position.saturating_add_signed(offset)
);
}
}
}
}
}
@@ -0,0 +1,254 @@
use std::{
cell::{Ref, RefCell, RefMut},
fmt,
rc::Rc,
slice,
};
use crate::{
error::MachineError,
vm::{Value, env::Environment, machine::Machine, value::convert::AnyFunction},
};
#[derive(Debug, Clone, PartialEq)]
pub struct HashTable(RefCell<HashTableData>);
#[derive(Clone)]
pub struct HashTableData {
buckets: Box<[Vec<(Value, Value)>]>,
length: usize,
}
pub struct HashTableIter<'a> {
buckets: slice::Iter<'a, Vec<(Value, Value)>>,
inner: Option<slice::Iter<'a, (Value, Value)>>,
}
pub struct HashTableIterMut<'a> {
buckets: slice::IterMut<'a, Vec<(Value, Value)>>,
inner: Option<slice::IterMut<'a, (Value, Value)>>,
}
impl HashTable {
pub fn borrow(&self) -> Ref<'_, HashTableData> {
self.0.borrow()
}
pub fn borrow_mut(&self) -> RefMut<'_, HashTableData> {
self.0.borrow_mut()
}
}
impl fmt::Display for HashTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.0.borrow(), f)
}
}
impl HashTableData {
pub fn new(bucket_count: usize) -> Self {
let buckets = vec![vec![]; bucket_count].into_boxed_slice();
Self { buckets, length: 0 }
}
pub fn is_empty(&self) -> bool {
self.length == 0
}
pub fn len(&self) -> usize {
self.length
}
pub fn clear(&mut self) {
for bucket in self.buckets.iter_mut() {
bucket.clear();
}
self.length = 0;
}
// Returns the value inserted
pub fn insert(&mut self, key: Value, value: Value) -> Result<Value, MachineError> {
let hash = key
.hash()
.ok_or_else(|| MachineError::InvalidHashTableKey(key.clone()))?;
let bucket_index = (hash % self.buckets.len() as u64) as usize;
let slot_index = self.buckets[bucket_index]
.iter()
.position(|(k, _)| k == &key);
if let Some(slot_index) = slot_index {
self.buckets[bucket_index][slot_index].1 = value.clone();
} else {
self.buckets[bucket_index].push((key, value.clone()));
self.length += 1;
}
Ok(value)
}
pub fn retain_invoke(
&mut self,
vm: &mut Machine,
env: &Rc<Environment>,
predicate: &AnyFunction,
) -> Result<(), MachineError> {
for bucket in self.buckets.iter_mut() {
let mut index = 0;
loop {
if index >= bucket.len() {
break;
}
let (key, value) = &bucket[index];
let output = predicate.invoke(vm, env, &[key.clone(), value.clone()])?;
if !output.is_trueish() {
bucket.remove(index);
self.length -= 1;
} else {
index += 1;
}
}
}
Ok(())
}
pub fn contains_key(&self, key: &Value) -> bool {
let Some(hash) = key.hash() else { return false };
let bucket_index = (hash % self.buckets.len() as u64) as usize;
self.buckets[bucket_index]
.iter()
.find(|(k, _)| k == key)
.is_some()
}
pub fn get(&self, key: &Value) -> Option<&Value> {
let hash = key.hash()?;
let bucket_index = (hash % self.buckets.len() as u64) as usize;
self.buckets[bucket_index]
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v)
}
pub fn get_mut(&mut self, key: &Value) -> Option<&mut Value> {
let hash = key.hash()?;
let bucket_index = (hash % self.buckets.len() as u64) as usize;
self.buckets[bucket_index]
.iter_mut()
.find(|(k, _)| k == key)
.map(|(_, v)| v)
}
pub fn remove(&mut self, key: &Value) -> Option<Value> {
let hash = key.hash()?;
let bucket_index = (hash % self.buckets.len() as u64) as usize;
let slot_index = self.buckets[bucket_index]
.iter()
.position(|(k, _)| k == key)?;
let value = self.buckets[bucket_index].remove(slot_index);
self.length -= 1;
Some(value.1)
}
pub fn iter(&self) -> HashTableIter<'_> {
HashTableIter {
buckets: self.buckets.iter(),
inner: None,
}
}
pub fn iter_mut(&mut self) -> HashTableIterMut<'_> {
HashTableIterMut {
buckets: self.buckets.iter_mut(),
inner: None,
}
}
}
impl PartialEq for HashTableData {
fn eq(&self, other: &Self) -> bool {
if self.length != other.length {
return false;
}
for (key, value1) in self.iter() {
let Some(value2) = other.get(key) else {
return false;
};
if value1 != value2 {
return false;
}
}
true
}
}
impl fmt::Debug for HashTableData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut list = f.debug_list();
for bucket in self.buckets.iter() {
for cell in bucket.iter() {
list.entry(cell);
}
}
list.finish()
}
}
impl fmt::Display for HashTableData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut index = 0;
for bucket in self.buckets.iter() {
for (key, value) in bucket.iter() {
if index != 0 {
write!(f, " ")?;
}
write!(f, "({key} . {value})")?;
index += 1;
}
}
Ok(())
}
}
impl From<HashTableData> for HashTable {
fn from(value: HashTableData) -> Self {
Self(RefCell::new(value))
}
}
impl<'a> Iterator for HashTableIter<'a> {
type Item = (&'a Value, &'a Value);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(inner) = &mut self.inner
&& let Some((key, value)) = inner.next()
{
return Some((key, value));
}
let next_bucket = self.buckets.next()?;
self.inner = Some(next_bucket.iter());
}
}
}
impl<'a> Iterator for HashTableIterMut<'a> {
type Item = (&'a Value, &'a mut Value);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(inner) = &mut self.inner
&& let Some((key, value)) = inner.next()
{
return Some((key, value));
}
let next_bucket = self.buckets.next()?;
self.inner = Some(next_bucket.iter_mut());
}
}
}
@@ -0,0 +1,34 @@
use std::{borrow::Borrow, fmt, rc::Rc};
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct IdentifierValue(Rc<str>);
impl From<&str> for IdentifierValue {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl From<String> for IdentifierValue {
fn from(value: String) -> Self {
Self(value.into())
}
}
impl AsRef<str> for IdentifierValue {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl Borrow<str> for IdentifierValue {
fn borrow(&self) -> &str {
self.as_ref()
}
}
impl fmt::Display for IdentifierValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
+23
View File
@@ -0,0 +1,23 @@
use crate::vm::value::{ConsCell, Value};
pub struct ProperListIter<'a, E> {
pub(super) head: Option<&'a Value>,
pub(super) error: Option<E>,
}
impl<'a, E> Iterator for ProperListIter<'a, E> {
type Item = Result<&'a Value, E>;
fn next(&mut self) -> Option<Self::Item> {
let head = self.head.take()?;
match head {
Value::Cons(cons) => {
let ConsCell(car, cdr) = cons.as_ref();
self.head = Some(cdr);
Some(Ok(car))
}
Value::Nil => None,
_ => self.error.take().map(Err),
}
}
}
@@ -0,0 +1,59 @@
macro_rules! impl_keyword {
(
$vis:vis enum $name:ident {
$($variant:ident => $s:literal),* $(,)?
}
) => {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
$vis enum $name {
$($variant),*
}
impl $name {
pub fn parse(s: &str) -> Option<Self> {
match s {
$(
$s => Some(Self::$variant),
)*
_ => None,
}
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
$(
Self::$variant => $s
),*
})
}
}
};
}
impl_keyword! {
pub enum Keyword {
Lambda => "lambda",
Defun => "defun",
If => "if",
Cond => "cond",
While => "while",
Otherwise => "&otherwise",
Optional => "&optional",
Rest => "&rest",
Setq => "setq",
Let => "let",
LetStar => "let*",
Defmacro => "defmacro",
Quote => "quote",
Progn => "progn",
Loop => "loop",
Break => "break",
Continue => "continue",
Declare => "declare",
Error => "&error",
Return => "return",
// Cons => "cons",
}
}
+326
View File
@@ -0,0 +1,326 @@
use std::{
fmt::{self, Write},
hash::{DefaultHasher, Hash, Hasher},
rc::Rc,
};
mod boolean;
mod closure;
mod cons;
mod function;
mod hashtable;
mod identifier;
mod iter;
mod keyword;
mod native;
mod number;
mod string;
mod vector;
pub mod convert;
pub use boolean::BooleanValue;
pub use closure::{ClosureValue, Upvalue, UpvalueRef};
pub use cons::ConsCell;
pub use function::BytecodeFunction;
pub use hashtable::{HashTable, HashTableData};
pub use identifier::IdentifierValue;
pub use keyword::Keyword;
pub use native::{NativeFunction, NativeObject, NativeValue};
pub use number::NumberValue;
pub use string::StringValue;
pub use vector::Vector;
pub use iter::ProperListIter;
use crate::{
compile::syntax::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
error::ValueConversionError,
};
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
// Syntactic
Nil,
Cons(Rc<ConsCell>),
Number(NumberValue),
Boolean(BooleanValue),
Keyword(Keyword),
Identifier(IdentifierValue),
String(StringValue),
Quasi(Rc<Value>),
Quote(Rc<Value>),
Unquote(Rc<Value>),
UnquoteSplice(Rc<Value>),
// Collections
Vector(Rc<Vector>),
HashTable(Rc<HashTable>),
// Semantic
Closure(ClosureValue),
Function(Rc<BytecodeFunction>),
// Native
NativeFunction(NativeFunction),
NativeValue(NativeValue),
}
impl Value {
fn hash_inner<H: Hasher>(&self, state: &mut H) -> bool {
match self {
Self::Nil => state.write_u8(0),
Self::Cons(cons) => {
let ConsCell(car, cdr) = cons.as_ref();
state.write_u8(1);
if !car.hash_inner(state) {
return false;
}
if !cdr.hash_inner(state) {
return false;
}
}
Self::Number(value) => {
state.write_u8(2);
return value.hash_inner(state);
}
Self::Boolean(value) => {
state.write_u8(3);
value.hash(state);
}
Self::Keyword(value) => {
state.write_u8(4);
value.hash(state);
}
Self::Identifier(value) => {
state.write_u8(5);
value.hash(state);
}
Self::String(value) => {
state.write_u8(6);
value.hash(state);
}
Self::Quasi(value) => {
state.write_u8(7);
return value.hash_inner(state);
}
Self::Quote(value) => {
state.write_u8(8);
return value.hash_inner(state);
}
Self::Unquote(value) => {
state.write_u8(9);
return value.hash_inner(state);
}
Self::UnquoteSplice(value) => {
state.write_u8(10);
return value.hash_inner(state);
}
Self::Vector(_)
| Self::HashTable(_)
| Self::NativeFunction(_)
| Self::Function(_)
| Self::NativeValue(_)
| Self::Closure(_) => return false,
}
true
}
pub fn hash(&self) -> Option<u64> {
let mut hasher = DefaultHasher::new();
if !self.hash_inner(&mut hasher) {
return None;
}
Some(hasher.finish())
}
pub fn is_nil(&self) -> bool {
matches!(self, Self::Nil)
}
pub fn unquote(&self) -> Result<Value, ValueConversionError> {
match self {
Self::Quote(value) => Ok(value.as_ref().clone()),
_ => Err(ValueConversionError {
expected: "quoted value".into(),
got: self.clone(),
}),
}
}
pub fn uncons_ref(&self) -> Option<(&Value, &Value)> {
match self {
Self::Cons(cons) => Some((&cons.0, &cons.1)),
_ => None,
}
}
pub fn quote(self) -> Value {
Value::Quote(self.into())
}
pub fn type_id(&self) -> Value {
match self {
Self::Nil | Self::Cons(_) => Value::Identifier("list".into()),
Self::Identifier(_) => Value::Identifier("identifier".into()),
Self::Number(_) => Value::Identifier("number".into()),
Self::Boolean(_) => Value::Identifier("boolean".into()),
Self::Quote(value) => {
Value::list_or_nil([Self::Identifier("quote".into()), value.type_id()])
}
Self::Quasi(value) => {
Value::list_or_nil([Self::Identifier("quasi".into()), value.type_id()])
}
Self::Unquote(value) => {
Value::list_or_nil([Self::Identifier("unquote".into()), value.type_id()])
}
Self::UnquoteSplice(value) => {
Value::list_or_nil([Self::Identifier("unquote-splice".into()), value.type_id()])
}
Self::Vector(_) => Self::Identifier("vector".into()),
Self::HashTable(_) => Self::Identifier("hash".into()),
Self::Keyword(_) => Self::Identifier("keyword".into()),
Self::String(_) => Self::Identifier("string".into()),
Self::NativeValue(_) => Self::Identifier("native".into()),
Self::NativeFunction(_) | Self::Function(_) | Self::Closure(_) => {
Self::Identifier("function".into())
}
}
}
pub fn is_trueish(&self) -> bool {
match self {
Self::Nil => false,
Self::Cons(_) => true,
Self::Vector(vector) => !vector.is_empty(),
Self::HashTable(table) => !table.borrow().is_empty(),
Self::Number(value) => value.is_trueish(),
Self::String(value) => !value.is_empty(),
Self::Boolean(BooleanValue(value)) => *value,
Self::Keyword(_)
| Self::Identifier(_)
| Self::Quasi(_)
| Self::Quote(_)
| Self::Unquote(_)
| Self::UnquoteSplice(_)
| Self::Closure(_)
| Self::NativeFunction(_)
| Self::NativeValue(_)
| Self::Function(_) => true,
}
}
pub fn cons(self, cdr: Value) -> Self {
Self::Cons(Rc::new(ConsCell(self, cdr)))
}
pub fn try_list_or_nil<E, I: IntoIterator<Item = Result<Self, E>>>(items: I) -> Result<Self, E>
where
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
{
let iter = items.into_iter();
let mut result = Value::Nil;
for element in iter.rev() {
result = element?.cons(result);
}
Ok(result)
}
pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self
where
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
{
let iter = items.into_iter();
let mut result = Value::Nil;
for element in iter.rev() {
result = element.cons(result);
}
result
}
pub fn proper_iter<E>(&self, error: E) -> ProperListIter<'_, E> {
ProperListIter {
head: Some(self),
error: Some(error),
}
}
pub fn syntax_iter(&self, location: ExpectedWhere) -> ProperListIter<'_, ParseError> {
self.proper_iter(ParseError {
input: self.clone(),
error: ParseErrorKind::Expected(ExpectedWhat::ProperList, location),
})
}
pub fn as_proper_list(&self) -> Option<Vec<Self>> {
self.proper_iter(())
.map(|x| x.cloned())
.collect::<Result<_, _>>()
.ok()
}
pub fn as_native<T: NativeObject>(&self) -> Result<Rc<T>, ValueConversionError> {
match self {
Self::NativeValue(value) if let Some(value) = value.cast_rc() => Ok(value),
_ => Err(ValueConversionError {
expected: "native value".into(),
got: self.clone(),
}),
}
}
pub fn as_native_ref<T: NativeObject>(&self) -> Result<&T, ValueConversionError> {
match self {
Self::NativeValue(value) if let Some(value) = value.cast_ref() => Ok(value),
_ => Err(ValueConversionError {
expected: "native value".into(),
got: self.clone(),
}),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Nil => write!(f, "NIL"),
Self::Cons(cons) => {
f.write_char('(')?;
fmt::Display::fmt(cons, f)?;
f.write_char(')')
}
Self::Keyword(keyword) => write!(f, "{keyword}"),
Self::Identifier(identifier) => write!(f, "{identifier}"),
Self::String(value) => fmt::Display::fmt(value, f),
Self::Quote(value) => {
f.write_char('\'')?;
fmt::Display::fmt(value, f)
}
Self::Quasi(value) => {
f.write_char('`')?;
fmt::Display::fmt(value, f)
}
Self::Unquote(value) => {
f.write_char(',')?;
fmt::Display::fmt(value, f)
}
Self::UnquoteSplice(value) => {
f.write_str(",@")?;
fmt::Display::fmt(value, f)
}
Self::Number(value) => fmt::Display::fmt(value, f),
Self::Boolean(value) => fmt::Display::fmt(value, f),
Self::Closure(value) => fmt::Display::fmt(value, f),
Self::Function(value) => fmt::Display::fmt(value, f),
Self::NativeValue(value) => fmt::Display::fmt(value, f),
Self::NativeFunction(value) => fmt::Display::fmt(value, f),
Self::Vector(vector) => {
f.write_str("#[")?;
fmt::Display::fmt(vector, f)?;
f.write_char(']')
}
Self::HashTable(table) => {
f.write_str("#hash{")?;
fmt::Display::fmt(table, f)?;
f.write_char('}')
}
}
}
}
+127
View File
@@ -0,0 +1,127 @@
use std::{
any::Any,
fmt,
hash::{Hash, Hasher},
rc::Rc,
};
use crate::{
error::MachineError,
vm::{
env::Environment,
machine::Machine,
value::{IdentifierValue, StringValue, Value},
},
};
pub trait NativeObject: Any + fmt::Debug + fmt::Display {
fn as_any(&self) -> &dyn Any;
}
#[derive(Clone, Debug)]
pub struct NativeValue(Rc<dyn NativeObject>);
pub type NativeFunctionImpl =
Rc<dyn Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static>;
#[derive(Clone)]
pub struct NativeFunction {
name: IdentifierValue,
inner: NativeFunctionImpl,
docstring: StringValue,
}
impl NativeFunction {
pub fn new<S, D, F>(name: S, docstring: D, function: F) -> Self
where
S: Into<IdentifierValue>,
D: Into<StringValue>,
F: Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static,
{
Self {
name: name.into(),
docstring: docstring.into(),
inner: Rc::new(function),
}
}
pub fn name(&self) -> &IdentifierValue {
&self.name
}
pub fn docstring(&self) -> &StringValue {
&self.docstring
}
pub fn invoke(
&self,
machine: &mut Machine,
environment: &Rc<Environment>,
arguments: &[Value],
) -> Result<Value, MachineError> {
(self.inner)(machine, environment, arguments)
}
}
impl Hash for NativeFunction {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
(&raw const *self.inner.as_ref()).addr().hash(state);
}
}
impl PartialEq for NativeFunction {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && Rc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for NativeFunction {}
impl fmt::Debug for NativeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NativeFunction")
.field("name", &self.name)
.field_with("inner", |f| write!(f, "{:p}", self.inner))
.finish()
}
}
impl fmt::Display for NativeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"<native {}@{:#x}>",
self.name,
(&raw const *self.inner.as_ref()).addr()
)
}
}
impl NativeValue {
pub fn cast_rc<T: NativeObject>(&self) -> Option<Rc<T>> {
Rc::downcast(self.0.clone()).ok()
}
pub fn cast_ref<T: NativeObject>(&self) -> Option<&T> {
self.0.as_any().downcast_ref()
}
}
impl From<Rc<dyn NativeObject + 'static>> for NativeValue {
fn from(value: Rc<dyn NativeObject>) -> Self {
Self(value)
}
}
impl PartialEq for NativeValue {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl fmt::Display for NativeValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<native {}>", self.0)
}
}
+282
View File
@@ -0,0 +1,282 @@
use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
num::TryFromIntError,
ops::{Add, Div, DivAssign, Mul, Neg, Rem, RemAssign, Sub, SubAssign},
};
pub struct NumberConvertError;
impl From<TryFromIntError> for NumberConvertError {
fn from(_: TryFromIntError) -> Self {
Self
}
}
#[derive(Debug, Clone, Copy)]
pub enum NumberValue {
Int(i64),
Float(f64),
}
impl NumberValue {
pub(super) fn hash_inner<H: Hasher>(&self, state: &mut H) -> bool {
match self {
Self::Int(value) => {
state.write_u8(1);
value.hash(state)
}
Self::Float(value) if !value.is_nan() => {
state.write_u8(2);
state.write(&value.to_le_bytes());
}
_ => return false,
}
true
}
pub const fn nan() -> Self {
Self::Float(f64::NAN)
}
pub const fn infinity() -> Self {
Self::Float(f64::INFINITY)
}
pub const fn neg_infinity() -> Self {
Self::Float(f64::NEG_INFINITY)
}
pub fn reciprocal(self) -> Self {
Self::Float(1.0 / self.as_float())
}
pub fn from_str_radix(string: &str, radix: u32) -> Option<Self> {
if !(2..=36).contains(&radix) {
return None;
}
if let Ok(value) = i64::from_str_radix(string, radix) {
Some(Self::Int(value))
} else if radix == 10
&& let Ok(value) = string.parse()
{
Some(Self::Float(value))
} else {
None
}
}
pub fn as_float(self) -> f64 {
match self {
Self::Int(value) => value as f64,
Self::Float(value) => value,
}
}
pub fn is_trueish(&self) -> bool {
match self {
Self::Int(value) => *value != 0,
Self::Float(value) => *value != 0.0 && !value.is_nan(),
}
}
}
impl PartialEq for NumberValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Int(a), Self::Int(b)) => a == b,
_ => self.as_float() == other.as_float(),
}
}
}
impl PartialOrd for NumberValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::Int(a), Self::Int(b)) => PartialOrd::partial_cmp(a, b),
_ => PartialOrd::partial_cmp(&self.as_float(), &other.as_float()),
}
}
}
impl Add for NumberValue {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_add(b) => Self::Int(c),
_ => todo!("TODO promote to bigint"),
}
}
}
impl Sub for NumberValue {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_sub(b) => Self::Int(c),
_ => todo!("TODO promote to bigint"),
}
}
}
impl SubAssign for NumberValue {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl Mul for NumberValue {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_mul(b) => Self::Int(c),
_ => todo!("TODO promote to bigint"),
}
}
}
impl Div for NumberValue {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
match (self, rhs) {
// TODO convert inexact division to rational
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_div_exact(b) => Self::Int(c),
_ => Self::Float(self.as_float() / rhs.as_float()),
}
}
}
impl DivAssign for NumberValue {
fn div_assign(&mut self, rhs: Self) {
*self = *self / rhs;
}
}
impl Rem for NumberValue {
type Output = Self;
fn rem(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_rem(b) => Self::Int(c),
_ => todo!("TODO NaN"),
}
}
}
impl RemAssign for NumberValue {
fn rem_assign(&mut self, rhs: Self) {
*self = *self % rhs;
}
}
impl Neg for NumberValue {
type Output = NumberValue;
fn neg(self) -> Self::Output {
match self {
Self::Int(a) if let Some(b) = a.checked_neg() => Self::Int(b),
_ => todo!("TODO promote to bigint"),
}
}
}
impl From<i8> for NumberValue {
fn from(value: i8) -> Self {
Self::Int(value.into())
}
}
impl From<i16> for NumberValue {
fn from(value: i16) -> Self {
Self::Int(value.into())
}
}
impl From<i32> for NumberValue {
fn from(value: i32) -> Self {
Self::Int(value.into())
}
}
impl From<i64> for NumberValue {
fn from(value: i64) -> Self {
Self::Int(value)
}
}
impl From<usize> for NumberValue {
fn from(value: usize) -> Self {
if let Ok(value) = i64::try_from(value) {
Self::Int(value)
} else {
todo!("TODO: promote to bigint")
}
}
}
impl TryFrom<NumberValue> for i8 {
type Error = NumberConvertError;
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
let NumberValue::Int(value) = value else {
return Err(NumberConvertError);
};
value.try_into().map_err(From::from)
}
}
impl TryFrom<NumberValue> for i16 {
type Error = NumberConvertError;
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
let NumberValue::Int(value) = value else {
return Err(NumberConvertError);
};
value.try_into().map_err(From::from)
}
}
impl TryFrom<NumberValue> for i32 {
type Error = NumberConvertError;
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
let NumberValue::Int(value) = value else {
return Err(NumberConvertError);
};
value.try_into().map_err(From::from)
}
}
impl TryFrom<NumberValue> for i64 {
type Error = NumberConvertError;
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
let NumberValue::Int(value) = value else {
return Err(NumberConvertError);
};
Ok(value)
}
}
impl TryFrom<NumberValue> for usize {
type Error = NumberConvertError;
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
match value {
NumberValue::Int(value) if let Ok(value) = value.try_into() => Ok(value),
_ => Err(NumberConvertError),
}
}
}
impl fmt::Display for NumberValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Int(value) => fmt::Display::fmt(value, f),
Self::Float(value) if value.is_infinite() && value.is_sign_negative() => {
write!(f, "-#inf")
}
Self::Float(value) if value.is_infinite() => write!(f, "#inf"),
Self::Float(value) if value.is_nan() => write!(f, "#nan"),
Self::Float(value) => fmt::Display::fmt(value, f),
}
}
}
+36
View File
@@ -0,0 +1,36 @@
use std::{fmt, ops::Deref, rc::Rc};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct StringValue(Rc<str>);
impl StringValue {
pub fn as_rc_str(&self) -> &Rc<str> {
&self.0
}
}
impl From<&str> for StringValue {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl From<String> for StringValue {
fn from(value: String) -> Self {
Self(value.into())
}
}
impl Deref for StringValue {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl fmt::Display for StringValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0.as_ref(), f)
}
}
+374
View File
@@ -0,0 +1,374 @@
use std::{
cell::{Ref, RefCell},
fmt, mem,
};
use crate::vm::value::{NumberValue, Value};
#[derive(Debug, PartialEq)]
pub enum VectorStorage {
I8(Vec<i8>),
I16(Vec<i16>),
I32(Vec<i32>),
I64(Vec<i64>),
Any(Vec<Value>),
}
#[derive(Debug, PartialEq)]
pub struct Vector(RefCell<VectorStorage>);
impl Vector {
pub fn is_empty(&self) -> bool {
self.0.borrow().is_empty()
}
pub fn len(&self) -> usize {
self.0.borrow().len()
}
pub fn borrow(&self) -> Ref<'_, VectorStorage> {
self.0.borrow()
}
pub fn value_at(&self, index: usize) -> Option<Value> {
self.0.borrow().value_at(index)
}
pub fn set_value_at(&self, index: usize, value: Value) {
self.0.borrow_mut().set_value_at(index, value);
}
pub fn as_bytes(&self) -> Option<Ref<'_, [u8]>> {
Ref::filter_map(self.borrow(), |r| match r {
VectorStorage::I8(values) => {
Some(unsafe { mem::transmute::<&[i8], &[u8]>(&values[..]) })
}
_ => None,
})
.ok()
}
}
impl fmt::Display for Vector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.0.borrow(), f)
}
}
impl<A> FromIterator<A> for Vector
where
VectorStorage: FromIterator<A>,
{
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
Self(RefCell::new(VectorStorage::from_iter(iter)))
}
}
impl From<Vec<u8>> for Vector {
fn from(value: Vec<u8>) -> Self {
let (ptr, len, cap) = value.into_raw_parts();
Self::from(unsafe { Vec::from_raw_parts(ptr.cast::<i8>(), len, cap) })
}
}
impl From<Vec<i8>> for Vector {
fn from(value: Vec<i8>) -> Self {
Vector(RefCell::new(VectorStorage::I8(value)))
}
}
macro_rules! static_dispatch {
([$input:expr] $value:ident => $body:expr) => {
match $input {
Self::I8($value) => $body,
Self::I16($value) => $body,
Self::I32($value) => $body,
Self::I64($value) => $body,
Self::Any($value) => $body,
}
};
}
macro_rules! dispatch_integer_convert {
($self:ident ($storage:ident, $value:ident) => $f:expr) => {
match $self {
Self::I8($storage) if let Ok($value) = i8::try_from($value) => {
$f;
}
Self::I8($storage) if let Ok($value) = i16::try_from($value) => {
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
$f;
*$self = Self::I16($storage);
}
Self::I8($storage) if let Ok($value) = i32::try_from($value) => {
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
$f;
*$self = Self::I32($storage);
}
Self::I8($storage) => {
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
$f;
*$self = Self::I64($storage);
}
Self::I16($storage) if let Ok($value) = i16::try_from($value) => {
$f;
}
Self::I16($storage) if let Ok($value) = i32::try_from($value) => {
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
$f;
*$self = Self::I32($storage);
}
Self::I16($storage) => {
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
$f;
*$self = Self::I64($storage);
}
Self::I32($storage) if let Ok($value) = i32::try_from($value) => {
$f;
}
Self::I32($storage) => {
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
$f;
*$self = Self::I64($storage);
}
Self::I64($storage) => {
$f;
}
Self::Any($storage) => {
let $value = Value::Number(NumberValue::Int($value));
$f;
}
}
};
}
macro_rules! dispatch_any_convert {
($self:ident ($storage:ident) => $f:expr) => {
match $self {
Self::I8($storage) => {
let mut $storage = $storage
.iter()
.copied()
.map(Into::into)
.map(Value::Number)
.collect::<Vec<_>>();
$f;
*$self = Self::Any($storage);
}
Self::I16($storage) => {
let mut $storage = $storage
.iter()
.copied()
.map(Into::into)
.map(Value::Number)
.collect::<Vec<_>>();
$f;
*$self = Self::Any($storage);
}
Self::I32($storage) => {
let mut $storage = $storage
.iter()
.copied()
.map(Into::into)
.map(Value::Number)
.collect::<Vec<_>>();
$f;
*$self = Self::Any($storage);
}
Self::I64($storage) => {
let mut $storage = $storage
.iter()
.copied()
.map(Into::into)
.map(Value::Number)
.collect::<Vec<_>>();
$f;
*$self = Self::Any($storage);
}
Self::Any($storage) => {
$f;
}
}
};
}
impl VectorStorage {
fn value_at(&self, index: usize) -> Option<Value> {
match self {
Self::I8(values) if let Some(value) = values.get(index).copied() => {
Some(Value::Number(value.into()))
}
Self::I16(values) if let Some(value) = values.get(index).copied() => {
Some(Value::Number(value.into()))
}
Self::I32(values) if let Some(value) = values.get(index).copied() => {
Some(Value::Number(value.into()))
}
Self::I64(values) if let Some(value) = values.get(index).copied() => {
Some(Value::Number(value.into()))
}
Self::Any(values) if let Some(value) = values.get(index).cloned() => Some(value),
_ => None,
}
}
fn len(&self) -> usize {
static_dispatch!([self] vec => vec.len())
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn push(&mut self, value: Value) {
match value {
Value::Number(NumberValue::Int(value)) => self.push_integer(value),
value => match self {
Self::Any(vec) => vec.push(value),
_ => self.push_any(value),
},
}
}
fn set_value_at(&mut self, index: usize, value: Value) {
if index >= self.len() {
return;
}
match value {
Value::Number(NumberValue::Int(value)) => self.set_integer(index, value),
value => match self {
Self::Any(vec) => vec[index] = value,
_ => self.set_any(index, value),
},
}
}
fn push_any(&mut self, value: Value) {
dispatch_any_convert!(self (w) => {
w.push(value);
});
}
fn set_any(&mut self, index: usize, value: Value) {
dispatch_any_convert!(self (w) => {
w[index] = value;
});
}
fn push_integer(&mut self, value: i64) {
dispatch_integer_convert!(self (w, value) => {
w.push(value);
});
}
fn set_integer(&mut self, index: usize, value: i64) {
dispatch_integer_convert!(self (w, value) => {
w[index] = value;
});
}
}
impl FromIterator<Value> for VectorStorage {
fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
let mut accumulator = Self::I8(vec![]);
for item in iter {
accumulator.push(item);
}
accumulator
}
}
fn display_vector<T: fmt::Display>(value: &[T], f: &mut fmt::Formatter) -> fmt::Result {
for (i, value) in value.iter().enumerate() {
if i != 0 {
write!(f, " ")?;
}
write!(f, "{value}")?;
}
Ok(())
}
impl fmt::Display for VectorStorage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[")?;
match self {
Self::I8(vec) => display_vector(vec, f),
Self::I16(vec) => display_vector(vec, f),
Self::I32(vec) => display_vector(vec, f),
Self::I64(vec) => display_vector(vec, f),
Self::Any(vec) => display_vector(vec, f),
}?;
write!(f, "]")
}
}
macro_rules! impl_primitive_from_iter {
($signed_ty:ty, $unsigned_ty:ty => $variant:ident) => {
impl FromIterator<$signed_ty> for VectorStorage {
fn from_iter<T: IntoIterator<Item = $signed_ty>>(iter: T) -> Self {
Self::$variant(iter.into_iter().collect())
}
}
impl FromIterator<$unsigned_ty> for VectorStorage {
fn from_iter<T: IntoIterator<Item = $unsigned_ty>>(iter: T) -> Self {
Self::$variant(iter.into_iter().map(|x| x as $signed_ty).collect())
}
}
};
}
impl_primitive_from_iter!(i8, u8 => I8);
impl_primitive_from_iter!(i16, u16 => I16);
impl_primitive_from_iter!(i32, u32 => I32);
impl_primitive_from_iter!(i64, u64 => I64);
#[cfg(test)]
mod tests {
use crate::vm::value::{Value, vector::VectorStorage};
#[test]
fn test_vector_storage_from_iter() {
let v = VectorStorage::from_iter([1i8, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
let v = VectorStorage::from_iter([1u8, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
let v = VectorStorage::from_iter([1i16, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
let v = VectorStorage::from_iter([1u16, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
let v = VectorStorage::from_iter([1i32, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
let v = VectorStorage::from_iter([1u32, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
let v = VectorStorage::from_iter([1i64, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
let v = VectorStorage::from_iter([1u64, 2, 3, 4, 5, 6, 7]);
assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
}
#[test]
fn test_vector_unspecialize() {
let mut v = VectorStorage::from_iter([1i8, 2, 3]);
assert_eq!(VectorStorage::I8(vec![1, 2, 3]), v);
v.push(Value::Number(1234.into()));
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 1234]), v);
v.push(Value::Number(12341234.into()));
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 1234, 12341234]), v);
v.push(Value::Number(1234123412341234i64.into()));
assert_eq!(
VectorStorage::I64(vec![1, 2, 3, 1234, 12341234, 1234123412341234]),
v
);
v.push(Value::String("a".into()));
assert_eq!(
VectorStorage::Any(vec![
Value::Number(1.into()),
Value::Number(2.into()),
Value::Number(3.into()),
Value::Number(1234.into()),
Value::Number(12341234.into()),
Value::Number(1234123412341234i64.into()),
Value::String("a".into())
]),
v
);
}
}
+206
View File
@@ -0,0 +1,206 @@
use std::{fs, path::Path, rc::Rc};
use lysp::{
error::MachineErrorAt,
vm::{env::Environment, machine::Machine, prelude, value::Value},
};
#[track_caller]
fn eval_str_in(code: &str, env: &Rc<Environment>) -> Result<Value, MachineErrorAt> {
let mut machine = Machine::default();
machine.evaluate_str(Default::default(), None, env, code)
}
#[track_caller]
fn eval_str(code: &str) -> Value {
let env = Rc::new(Environment::default());
prelude::load(&env);
match eval_str_in(code, &env) {
Ok(value) => value,
Err(error) => {
eprintln!("Couldn't evaluate expression:");
eprintln!();
eprintln!(" {code}");
eprintln!();
eprintln!(":: {error}");
panic!("TEST FAILED");
}
}
}
#[track_caller]
fn eval_str_err(code: &str) -> MachineErrorAt {
let env = Rc::new(Environment::default());
prelude::load(&env);
match eval_str_in(code, &env) {
Ok(value) => {
eprintln!("Expected the code to fail to evaluate, but it returned success:");
eprintln!();
eprintln!(" {code}");
eprintln!();
eprintln!("Returned");
eprintln!();
eprintln!(":: {value}");
panic!("TEST FAILED");
}
Err(error) => error,
}
}
#[track_caller]
fn eval_file<P: AsRef<Path>>(path: P) -> Value {
let code = fs::read_to_string(path).expect("file read failed");
eval_str(&code)
}
#[test]
fn test_math() {
// math
assert_eq!(eval_str("(+ 1 2 3)"), Value::Number(6.into()));
assert_eq!(eval_str("(- 3 2 1)"), Value::Number(0.into()));
assert_eq!(eval_str("(* 2 3 4)"), Value::Number(24.into()));
assert_eq!(eval_str("(/ 16 4 2)"), Value::Number(2.into()));
assert_eq!(eval_str("(% 35 16)"), Value::Number(3.into()));
// TODO
// assert_eq!(eval_str("(| 1 2 4)"), Value::Number(7.into()));
// assert_eq!(eval_str("(& 1 2 4)"), Value::Number(0.into()));
// assert_eq!(eval_str("(& 1 3 7)"), Value::Number(1.into()));
// assert_eq!(eval_str("(^ 1 3 8)"), Value::Number(10.into()));
// comparison
assert_eq!(eval_str("(< 1 2 3 4 5)"), Value::Boolean(true.into()));
assert_eq!(eval_str("(< 1 2 3 3 4 5)"), Value::Boolean(false.into()));
assert_eq!(eval_str("(> 1 2 3 4 5)"), Value::Boolean(false.into()));
assert_eq!(eval_str("(>= 5 5 4 3 2 1)"), Value::Boolean(true.into()));
assert_eq!(eval_str("(> 5 5 4 3 2 1)"), Value::Boolean(false.into()));
assert_eq!(eval_str("(<= 1 2 3 3 3 4)"), Value::Boolean(true.into()));
assert_eq!(eval_str("(/= 1 2 3 4)"), Value::Boolean(true.into()));
assert_eq!(eval_str("(/= 1 2 2 4)"), Value::Boolean(false.into()));
assert_eq!(eval_str("(= 1 2 3 4)"), Value::Boolean(false.into()));
assert_eq!(eval_str("(= 1 1 1 1)"), Value::Boolean(true.into()));
// logic
// TODO
// assert_eq!(eval_str("(&& 1 0)"), Value::Boolean(false.into()));
// assert_eq!(eval_str("(&& #t 1 \"yes\")"), Value::Boolean(true.into()));
// assert_eq!(
// eval_str("(|| #f NIL \"\" ())"),
// Value::Boolean(false.into())
// );
// assert_eq!(
// eval_str("(|| #f '(1 2 3) \"\" ())"),
// Value::Boolean(true.into())
// );
}
#[test]
fn test_abort() {
// let err = eval_str_err("(assert (= 1 (+ 1 1)))");
// let EvalError::Machine(MachineError {
// error: MachineErrorKind::Aborted(message),
// ..
// }) = err
// else {
// panic!("Invalid error returned")
// };
// assert_eq!(&message, "<undefined>: assertion failed: (= 1 (+ 1 1))");
// let err = eval_str_err("(abort 1234)");
// let EvalError::Machine(MachineError {
// error: MachineErrorKind::Aborted(message),
// ..
// }) = err
// else {
// panic!("Invalid error returned")
// };
// assert_eq!(&message, "1234");
}
#[test]
fn test_lambda() {
assert_eq!(
eval_str("((lambda (x) (+ x 1)) 1)"),
Value::Number(2.into())
);
assert_eq!(
eval_str("((lambda (x) (+ ((lambda () 2)) 1)) 1)"),
Value::Number(3.into())
);
}
#[test]
fn test_math_behaves_the_same_inside_apply() {
assert_eq!(eval_str("(apply + '(1 2 3 4))"), eval_str("(+ 1 2 3 4)"));
assert_eq!(eval_str("(apply * '(1 2 3 4))"), eval_str("(* 1 2 3 4)"));
assert_eq!(eval_str("(apply - '(1 2 3 4))"), eval_str("(- 1 2 3 4)"));
assert_eq!(eval_str("(apply / '(100 25 2))"), eval_str("(/ 100 25 2)"));
assert_eq!(eval_str("(apply % '(90 37))"), eval_str("(% 90 37)"));
// TODO
// assert_eq!(eval_str("(apply | '(1 2 4))"), eval_str("(| 1 2 4)"));
// assert_eq!(eval_str("(apply & '(1 3 7))"), eval_str("(& 1 3 7)"));
// assert_eq!(eval_str("(apply ^ '(1 3 7))"), eval_str("(^ 1 3 7)"));
// assert_eq!(eval_str("(apply < '(1 2 3 4))"), eval_str("(< 1 2 3 4)"));
// assert_eq!(eval_str("(apply > '(1 2 3 4))"), eval_str("(> 1 2 3 4)"));
}
#[test]
fn test_setq() {
assert_eq!(eval_str("(setq a 1234) a\n"), Value::Number(1234.into()));
}
#[test]
fn test_let() {
// Should fail
eval_str_err("(let (a 1234 b (+ a 1)) NIL)");
eval_str_err("(let (a 1234) (let (a 9999 b (+ a 4321)) b))");
assert_eq!(
eval_str("(let (a 1234 b 4321) (+ a b))"),
Value::Number(5555.into())
);
assert_eq!(
eval_str("(let* (a 1234 b (+ a 4321)) (+ b 4444))"),
Value::Number(9999.into())
);
// Nested let
assert_eq!(
eval_str("(let (a 1234) (let (b 4321) (+ a b)))"),
Value::Number(5555.into())
);
// Does shadow
assert_eq!(
eval_str("(let (a 1234) (let* (a 9999 b (+ a 4321)) b))"),
Value::Number(14320.into())
);
}
#[test]
fn test_macro() {
assert_eq!(
eval_str("(defmacro stringify (x) (cons 'list `,x)) (stringify (1 2 3 4))"),
Value::list_or_nil([
Value::Number(1.into()),
Value::Number(2.into()),
Value::Number(3.into()),
Value::Number(4.into())
])
);
}
#[test]
fn test_examples_work() {
const EXCLUDE: &[&str] = &["repl.lysp", "echo.lysp", "io.lysp", "import.lysp"];
// None of them should crash at least
for file in fs::read_dir("examples").unwrap() {
let entry = file.unwrap();
let filename = entry.file_name();
let filename = filename.to_str().unwrap();
if !filename.ends_with(".lysp") || EXCLUDE.contains(&filename) {
continue;
}
eprintln!("Eval {}", entry.path().display());
eval_file(entry.path());
}
}
+1
View File
@@ -0,0 +1 @@
/*.log
+614 -211
View File
File diff suppressed because it is too large Load Diff
+15 -6
View File
@@ -4,16 +4,25 @@ version = "0.1.0"
edition = "2024"
authors = ["Mark Poliakov <mark@alnyan.me>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libterm.workspace = true
cross.workspace = true
lysp.workspace = true
logsink.workspace = true
thiserror.workspace = true
log.workspace = true
unicode-width = "0.1.11"
regex = "1.12.3"
[target.'cfg(not(target_os = "yggdrasil"))'.dependencies]
libc = "0.2.150"
crossterm = "0.27.0"
syslog = "6.1.0"
[features]
default = []
runtime = []
[lints]
workspace = true
# [target.'cfg(not(target_os = "yggdrasil"))'.dependencies]
# libc = "0.2.150"
# syslog = "6.1.0"
+129
View File
@@ -0,0 +1,129 @@
(setq _red/command-table (hash/new))
;; Command text manipulation
(setq _red/current-command nil)
(defun red/command/append! (text)
(setq _red/current-command (+ (if _red/current-command _red/current-command "") text)))
(defun red/command/erase-backward! ()
(when _red/current-command
(setq _red/current-command (string/pop _red/current-command))))
(defun red/command/clear! () (setq _red/current-command nil))
;; Command macros
(defun _red/declare-command
(command handler)
(hash/put! _red/command-table command handler))
(defmacro declare-command
(command args &rest body)
(unless (cons? body)
(error "No body provided in declare-command"))
(unless (list? args)
(error "Argument list must either be a NIL or a list"))
(let*
(
args-has-rest (find (lambda (x) (= x '&rest)) args)
lambda-args (if args-has-rest
`(,@args)
`(,@args &rest _)
)
)
`(_red/declare-command ,command (lambda ,lambda-args ,@body))
)
)
;; Command handlers
(defun _red/editor-command-hook (command)
(when-let
(words (filter identity (string/split command)))
(let* (command (car words) args (cdr words) entry (hash/get _red/command-table command))
(if (nil? entry)
(red/message (+ "Unhandled command: :" command))
(apply entry args))
)))
(defun _red/shell-command-hook (command)
(setq command (string/trim command))
(unless command (return))
(let (words (filter identity (string/split command)))
(apply red/shell-command words)
)
)
;; Root command handler
(defun _red/root-command-hook (command)
(red/set-top-mode 'normal)
(setq command (string/trim command))
(unless command (return))
(let (shell-command (string/strip-prefix command "!"))
(cond
((nil? shell-command) (_red/editor-command-hook command))
(shell-command (_red/shell-command-hook shell-command))
)
)
)
;; Command definitions
(declare-command "q" () (red/quit))
(declare-command "q!" () (red/quit #t))
(declare-command
"w" (&optional filename)
(if filename
(red/buffer/write filename)
(red/buffer/write)
)
)
(declare-command
"wq" ()
(red/buffer/write)
(red/quit)
)
(declare-command
"e" (&optional filename)
(if filename
(red/buffer/open filename)
)
)
(declare-command
"e!" (&optional filename)
(if filename
(red/buffer/open filename #t)
)
)
(declare-command
"source" (filename)
(import filename)
)
;; TODO only allow some keys to be set
;; TODO at least check that the key is defined
(declare-command
"set" (key &rest value)
(let* (
key-symbol (symbol (+ "red/" key))
expression (string/join value)
eval-result (if (nil? value) '(ok #t) (read-eval expression))
)
(cond
((result/ok? eval-result) (set key-symbol (cadr eval-result)))
(&otherwise (red/message (+ "error: " (cadr eval-result))))
)
)
)
(declare-command
"unset" (key)
(let (key-symbol (symbol (+ "red/" key)))
(set key-symbol nil)
)
)
(declare-command
"eval" (&rest expressions)
(let* (
expression (string/join expressions)
eval-result (read-eval expression)
)
(cond
((result/ok? eval-result) (red/status (+ "=> " (cadr eval-result))))
(&otherwise (red/message (+ "error: " (cadr eval-result))))
)
)
)
+21
View File
@@ -0,0 +1,21 @@
;; External API
(import "util.lysp")
(import "message.lysp")
;; Bind the hooks
(import "render.lysp")
(red/bind-post-render-hook _red/root-post-render-hook)
;; Child modules
(import "keyboard.lysp")
(red/bind-key-hook _red/root-key-hook)
(import "command.lysp")
(import "highlight.lysp")
;; User configuration
(try-import "/etc/red/init.lysp")
(try-import (+ (fs/home-directory) "/.red.d/init.lysp"))
(_red/update-render-params)
+169
View File
@@ -0,0 +1,169 @@
;; TODO
(defun _map (f xs)
(if (nil? xs)
nil
(cons (f (car xs)) (_map f (cdr xs))))
)
(defun _red/syntax-make-keyword-rule (syntax pattern category prev-state next-state)
`(red/syntax/define-keyword-rule ,syntax ,prev-state ,pattern ,category ,next-state))
(defun _red/syntax-rule-keyword (syntax clause)
;; (:keyword "PATTERNS"... :category "CATEGORY" :prev-state N :next-state M)
(let (
prev-state 0
next-state nil
patterns nil
category nil
)
(while (not (nil? clause))
(cond
; :prev-state N
((= (car clause) ':prev-state)
(progn
(setq prev-state (cadr clause))
(setq clause (cdr clause))
)
)
; :next-state N
((= (car clause) ':next-state)
(progn
(setq next-state (cadr clause))
(setq clause (cdr clause))
)
)
; :category "CATEGORY"
((= (car clause) ':category)
(progn
(setq category (cadr clause))
(setq clause (cdr clause))
)
)
; "PATTERNS"...
(&otherwise (setq patterns (cons (car clause) patterns)))
)
(setq clause (cdr clause))
)
; (when (or (nil? category) (nil? patterns))
; (error "Invalid clause")
; )
(cons
'progn
(_map
(lambda (pattern) (_red/syntax-make-keyword-rule syntax pattern category prev-state next-state))
patterns
)
)
)
)
(defun _red/syntax-rule-regex (syntax clause)
;; (:regex "REGEX" "CATEGORY" :prev-state N :next-state M)
(let (
prev-state 0
next-state nil
regex (car clause)
category (cadr clause)
)
(setq clause (cdr (cdr clause)))
(while (not (nil? clause))
(cond
; :prev-state N
((= (car clause) ':prev-state)
(progn
(setq prev-state (cadr clause))
(setq clause (cdr clause))
)
)
; :next-state M
((= (car clause) ':next-state)
(progn
(setq next-state (cadr clause))
(setq clause (cdr clause))
)
)
)
(setq clause (cdr clause))
)
`(red/syntax/define-regex-rule ,syntax ,prev-state ,regex ,category ,next-state)
)
)
(defun _red/syntax-style-category (syntax clause)
;; ("CATEGORY" :foreground 'COLOR :background 'COLOR :bold)
(let (category (car clause) foreground nil background nil bold nil)
(setq clause (cdr clause))
(while (not (nil? clause))
(cond
; :foreground 'COLOR
((or (= (car clause) ':foreground) (= (car clause) ':fg))
(progn
(setq foreground (cadr clause))
(setq clause (cdr clause))
)
)
; :background 'COLOR
((or (= (car clause) ':background) (= (car clause) ':bg))
(progn
(setq background (cadr clause))
(setq clause (cdr clause))
)
)
; :bold
((= (car clause) ':bold) (setq bold #t))
)
(setq clause (cdr clause))
)
(when (not (or foreground background bold))
(error "Empty clause")
)
`(red/syntax/define-category-style ,syntax ,category ,foreground ,background ,bold)
)
)
(defun _red/define-syntax (syntax clauses)
(let (output nil current-state nil)
(while (cons? clauses)
(let (clause (car clauses))
(cond
((= clause ':styles) (setq current-state 'styles))
((= clause ':rules) (setq current-state 'rules))
;; ("style" ...) clauses
((= current-state 'styles) (setq output (cons (_red/syntax-style-category syntax clause) output)))
;; (:regex ...) clauses
((and
(= current-state 'rules)
(cons? clause)
(= (car clause) ':regex)
(cons? (cdr clause))
)
(setq output (cons (_red/syntax-rule-regex syntax (cdr clause)) output))
)
;; (:keyword ...) clauses
((and
(= current-state 'rules)
(cons? clause)
(= (car clause) ':keyword)
(cons? (cdr clause))
)
(setq output (cons (_red/syntax-rule-keyword syntax (cdr clause)) output))
)
)
)
(setq clauses (cdr clauses))
)
(if (nil? output)
nil
(cons 'progn output)
)
)
)
(defmacro define-syntax (syntax &rest clauses)
(_red/define-syntax syntax clauses)
)
;; Load syntax files
;; TODO glob
(import "syntax/lysp.lysp")
(import "syntax/rust.lysp")
(red/syntax/reset)
+158
View File
@@ -0,0 +1,158 @@
;; Key sequence handling
(setq _red/key-sequence nil)
(defun _red/push-key-seq
(key)
(setq _red/key-sequence (append _red/key-sequence (list key))))
(defun _red/reset-key-seq
()
(setq _red/key-sequence nil))
;; Key map handling
(setq _red/keymaps (hash/new))
(setq _red/key-fallbacks (hash/new))
(hash/put! _red/keymaps 'normal (red/keymap/new))
(hash/put! _red/keymaps 'insert (red/keymap/new))
(hash/put! _red/keymaps 'command (red/keymap/new))
(defun _red/declare-key
(mode seq action)
(when (not (hash/has? _red/keymaps mode))
(hash/put! _red/keymaps mode (red/keymap/new))
)
(hash/update!
(lambda (kmap)
(red/keymap/put! kmap seq action)
kmap)
_red/keymaps
mode
)
)
(defun _red/declare-key-fallback
(mode fallback)
(hash/put! _red/key-fallbacks mode fallback)
)
(defun _red/lookup-key
(mode seq)
(let (kmap (hash/get _red/keymaps mode))
(if (not (nil? kmap))
(red/keymap/get kmap seq)
)
)
)
(defmacro declare-keys
(buffer-mode &rest clauses)
;; (declare-keys
;; normal
;; ('k ...)
;; ('h ...)
;; :fallback
;; (keyseq)
;; ...
;; )
(let (output nil)
(while (not (nil? clauses))
(let (clause (car clauses))
(cond
((cons? clause)
(let (key-seq (car clause) key-action (cdr clause))
(when (nil? key-action)
(error "Expected at least one expression after the key sequence")
)
(setq output (cons
`(_red/declare-key (quote ,buffer-mode) ,key-seq (lambda () ,@key-action))
output
))
)
)
;; Rest of clauses are fallback args + body
((= clause ':fallback)
(progn
(setq
output
(cons
`(_red/declare-key-fallback (quote ,buffer-mode) (lambda ,@(cdr clauses)))
output)
)
(break)
)
)
(&otherwise (error "Unexpected clause:" clause))
)
)
(setq clauses (cdr clauses))
)
(when (nil? output)
(error "No key sequence and no fallback action defined in the declare-keys invocation")
)
(cons 'progn output)
)
)
;; Key input handling
(defun _red/root-mapped-key-hook
(mode key)
;; 1. Push key into current sequence
;; 2. Lookup current sequence in relevant key map
;; 3.1. If present and leaf, reset current sequence and invoke the handler
;; 3.2. If present and non-leaf, continue collecting the sequence
;; 3.3. If non-present, reset the sequence
(_red/push-key-seq key)
(let (node (_red/lookup-key mode _red/key-sequence))
(cond
;; TODO invoke general fallback handler for the buffer-mode
((nil? node)
(progn
(let (fallback (hash/get _red/key-fallbacks mode))
(unless (nil? fallback)
(fallback _red/key-sequence))
)
(_red/reset-key-seq)
))
((= (car node) 'prefix) nil)
((= (car node) 'leaf)
(progn
(_red/reset-key-seq)
((cadr node))
))
)
)
)
(defun _red/root-key-hook
(top-mode buffer-mode key)
(cond
((= top-mode 'command) (_red/root-mapped-key-hook 'command key))
((= top-mode 'normal) (_red/root-mapped-key-hook buffer-mode key))
(&otherwise (eprint "Unhandled" (list '_red/root-key-hook top-mode buffer-mode key)))
)
)
;; Helpers
(defun red/as-insertable-key-seq
(key-seq)
(if (nil? (cdr key-seq))
(let* (key (car key-seq) key-str (->string key))
(cond
((= key 'space) " ")
((= key 'tab) "\t")
((and
(= (string/length key-str) 1)
(or
(not (string/ascii? key-str))
(string/ascii-graphic? key-str)
)
)
key-str
)
)
)
)
)
(import "keymap/command.lysp")
(import "keymap/insert.lysp")
(import "keymap/normal.lysp")
@@ -0,0 +1,24 @@
(declare-keys
command
('escape (red/set-top-mode 'normal))
('backspace
(if _red/current-command
(red/command/erase-backward!)
(red/set-top-mode 'normal)
))
('newline
(let (command _red/current-command)
(red/command/clear!)
(_red/root-command-hook command)
)
)
:fallback
(key-seq)
(_red/command-mode-handler key-seq)
)
(defun _red/command-mode-handler
(key-seq)
(let (insertable (red/as-insertable-key-seq key-seq))
(unless (nil? insertable)
(red/command/append! insertable))))
@@ -0,0 +1,25 @@
(declare-keys
insert
('left (red/buffer/move 'prev-char))
('right (red/buffer/move 'next-char))
('up (red/buffer/move 'prev-line))
('down (red/buffer/move 'next-line))
('newline
(red/buffer/insert-line-after #T)
(red/buffer/move 'next-line)
(red/buffer/move 'line-start))
('escape (red/buffer/set-mode 'normal))
('backspace (red/buffer/erase-backward))
:fallback
(key-seq)
(_red/insert-mode-handler key-seq)
)
(defun _red/insert-mode-handler
(key-seq)
(let (insertable (red/as-insertable-key-seq key-seq))
(unless (nil? insertable)
(red/buffer/write-text insertable)
)
)
)
@@ -0,0 +1,58 @@
(declare-keys
normal
(':
(red/clear-status)
(red/clear-message)
(red/set-top-mode 'command)
)
('i (red/buffer/set-mode 'insert-before))
('a (red/buffer/set-mode 'insert-after))
('I
(red/buffer/move 'line-start)
(red/buffer/set-mode 'insert-before)
)
('A
(red/buffer/move 'line-end)
(red/buffer/set-mode 'insert-after)
)
('(g g) (red/buffer/move 'first-line))
('G (red/buffer/move 'last-line))
('h (red/buffer/move 'prev-char))
('l (red/buffer/move 'next-char))
('k (red/buffer/move 'prev-line))
('j (red/buffer/move 'next-line))
('K (red/buffer/move 'prev-page))
('J (red/buffer/move 'next-page))
('newline
(red/buffer/move 'next-line)
(red/buffer/move 'line-start))
('left (red/buffer/move 'prev-char))
('right (red/buffer/move 'next-char))
('up (red/buffer/move 'prev-line))
('down (red/buffer/move 'next-line))
('(F F F F F) nil)
('o
(red/buffer/insert-line-after)
(red/buffer/move 'next-line)
(red/buffer/set-mode 'insert-before)
)
('O
(red/buffer/insert-line-before)
(red/buffer/set-mode 'insert-before)
)
('(d d)
(red/buffer/kill-line)
(red/buffer/move 'prev-line))
('(z z)
(when (red/buffer/path)
(red/buffer/write)
)
(red/quit)
)
('(Z Z) (red/quit #T))
)
+15
View File
@@ -0,0 +1,15 @@
(setq _red/current-message nil)
(setq _red/current-status nil)
(defun _red/set-message (var args)
(let
(value (if (nil? args)
nil
(string/join (map ->string args))))
(set var value)))
(defun red/message (&rest args) (_red/set-message '_red/current-message args))
(defun red/status (&rest args) (_red/set-message '_red/current-status args))
(defun red/clear-message () (setq _red/current-message nil))
(defun red/clear-status () (setq _red/current-status nil))
+112
View File
@@ -0,0 +1,112 @@
;; Drawing options
(setq red/render/status-line #T)
(setq red/render/status-line/key-sequence #T)
(setq red/render/mode-line #T)
(setq red/render/mode-line/arrow #T)
;; Drawing functions
(defun _red/colorize-mode
(mode)
(cond
((= mode 'normal) '(black cyan))
((= mode 'insert) '(black yellow))
((= mode 'command) '(black green))
(&otherwise nil)))
(defun _red/render-mode-line
(row mode name modified)
(unless red/render/mode-line (return))
(term/set-cursor row 0)
(let (color (_red/colorize-mode mode))
(when color
(when (car color)
(term/fg-color (car color)))
(when (cadr color)
(term/bg-color (cadr color))
)
)
(term/write (+ " " (string/to-upper (->string mode)) " "))
;; Invert colors and draw the arrow
(when color
(when (and red/render/mode-line/arrow (cadr color))
(term/fg-color (cadr color))
(term/bg-color 'black)
(term/write "🭬"))
(term/reset)
)
)
;; Render bufer name
(when name
(term/write (+ " " name)))
(when modified
(term/write " [+]"))
)
(defun _red/render-status-line
(row)
(let (message (cond
(_red/current-message (list _red/current-message 'red))
(_red/current-status (list _red/current-status 'white)))
)
(unless message (return))
(term/set-cursor row 0)
(when (cadr message)
(term/fg-color (cadr message)))
(term/write (car message))))
(defun _red/render-key-line
(row width)
(let (keys (string/join (map ->string _red/key-sequence) "+"))
(unless keys (return))
(term/set-cursor row (- width (+ (string/length keys) 1)))
(term/write keys)
))
(defun _red/render-command (row command)
(term/set-cursor row 0)
(term/write ":")
(when command
(term/write command)))
(defun _red/render-bottom-bar
(width height)
(let* (mode (red/buffer/mode) top-mode (car mode) buffer-mode (cadr mode))
(when (= top-mode 'command) (setq buffer-mode 'command))
;; Display mode line
(_red/render-mode-line
(- height _red/mode-line-offset)
buffer-mode
(red/buffer/name)
(red/buffer/modified?))
(when (= top-mode 'normal)
;; Status text
(_red/render-status-line (- height _red/status-line-offset))
;; Current key sequence
(_red/render-key-line (- height _red/status-line-offset) width)
)
(when (= top-mode 'command)
;; Render command
(_red/render-command (- height _red/status-line-offset) _red/current-command))
)
)
(defun _red/root-post-render-hook (width height)
(_red/render-bottom-bar width height)
(term/reset)
;; If not in command mode, set cursor to buffer
(when (= 'normal (car (red/buffer/mode)))
(red/cursor-to-buffer))
)
;; command line + status line
(defun _red/update-render-params ()
(setq _red/status-line-offset 1)
(setq _red/mode-line-offset 2)
(setq red/bottom-margin (+ (and red/render/mode-line 1) 1)))
(_red/update-render-params)
@@ -0,0 +1,45 @@
;; Test expression for syntax highlight
(ignore (let print 1234 0x1234 "a string" empty-string "" after-empty-string #t #F))
(define-syntax
"lysp"
:styles
("keyword" :fg 'red)
("symbol" :fg 'cyan)
("comment" :fg 'white :bold)
("string" :fg 'yellow)
("custom-syntax" :fg 'red :bold)
("number" :fg 'cyan :bold)
("constant" :fg 'cyan :bold)
:rules
;; keywords
(:keyword
"let" "let*" "progn" "defun" "defmacro" "setq"
"when" "unless" "if" "while" "loop" "&optional"
"&rest" "&otherwise" "cond" "nil" "lambda" "ignore"
:category "keyword"
)
;; prelude functions
(:keyword
"car" "cdr" "caar" "cadr" "cdar" "cddr" "cadar"
"caddr" "list?" "nil?" "cons?" "string?" "symbol?"
"cons" "list" "print" "not" "or" "and" "=" ">="
"<=" "/=" "error" "import" "apply" "find"
:category "symbol"
)
(:keyword
"#t" "#T" "#f" "#F"
:category "constant"
)
;; strings
(:regex "." "string" :prev-state 1)
(:regex "[^\\\\]\"" "string" :prev-state 1 :next-state 0)
(:regex "\"" "string" :next-state 1)
(:regex "\"\"" "string")
;; :keywords
(:regex ":[\\-\\w]+" "custom-syntax")
;; numbers
(:regex "(0x|0o|0b)?\\d+" "number")
;; comments
(:regex ";.*$" "comment")
)
@@ -0,0 +1,37 @@
;; TODO very WIP
(define-syntax
"rust"
:styles
("keyword" :fg 'red)
("symbol" :fg 'cyan)
("comment" :fg 'white :bold)
("string" :fg 'yellow)
("number" :fg 'cyan :bold)
("constant" :fg 'cyan :bold)
:rules
;; Keywords
(:keyword
"use" "pub" "mod" "enum" "struct" "impl"
"for" "=>" "->" "match" "for" "while" "loop"
"if" "fn" "&mut" "&" "let" "where" "?"
:category "keyword"
)
;; Identifiers
(:keyword
"Rc" "RefCell" "Clone" "Copy" "PartialEq" "Eq"
"PartialOrd" "Ord" "Debug" "Result" "Option"
"bool" "i8" "i16" "i32" "i64" "i128" "u8" "u16"
"u32" "u64" "u128" "str" "&str"
"Self" "self" "&self" "&mut self" "Ok" "Err"
:category "symbol"
)
(:keyword "true" "false" :category "constant")
;; strings
(:regex "." "string" :prev-state 1)
(:regex "[^\\\\]*\"" "string" :prev-state 1 :next-state 0)
(:regex "\"" "string" :next-state 1)
;; numbers
(:regex "(0x|0o|0b)?\\d+" "number")
;; Comments
(:regex "//.*$" "comment")
)
+20
View File
@@ -0,0 +1,20 @@
;; TODO prelude
(defun string/join (xs &optional separator)
(when (nil? separator)
(setq separator " "))
(let (accumulator "")
(while (cons? xs)
(if accumulator
(setq accumulator (+ accumulator separator (car xs)))
(setq accumulator (car xs))
)
(setq xs (cdr xs))
)
accumulator
)
)
(defmacro ignore (&rest expressions) nil)
(defun try-import (path)
(when (fs/file? path) (import path) #t))
+104
View File
@@ -0,0 +1,104 @@
use std::{
fmt,
ops::Index,
slice::{self, SliceIndex},
};
use crate::{
highlight::LineHighlight,
text::{Span, Text, TextLike, TextLikeMut},
};
pub struct Line {
pub text: Text,
pub highlight: LineHighlight,
pub highlight_dirty: bool,
}
impl Line {
pub fn new() -> Self {
Self::from(Text::new())
}
#[inline]
pub fn len(&self) -> usize {
self.text.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
}
impl TextLike for Line {
type Iter<'a>
= slice::Iter<'a, char>
where
Self: 'a;
type Span<'a>
= Span<'a>
where
Self: 'a;
#[inline]
fn display_width(&self, tab_width: usize) -> usize {
self.text.display_width(tab_width)
}
#[inline]
fn iter(&self) -> Self::Iter<'_> {
self.text.iter()
}
#[inline]
fn span<R: SliceIndex<[char], Output = [char]>>(&self, range: R) -> Self::Span<'_> {
self.text.span(range)
}
#[inline]
fn skip_to_width(&self, offset: usize, tab_width: usize) -> (Self::Span<'_>, usize, usize) {
self.text.skip_to_width(offset, tab_width)
}
}
impl TextLikeMut for Line {
fn split_off(&mut self, at: usize) -> Self {
let tail = self.text.split_off(at);
self.highlight_dirty = true;
Self::from(tail)
}
fn extend(&mut self, other: Self) {
self.text.extend(other.text);
self.highlight_dirty = true;
}
fn remove(&mut self, at: usize) {
self.text.remove(at);
self.highlight_dirty = true;
}
fn insert(&mut self, at: usize, ch: char) {
self.text.insert(at, ch);
self.highlight_dirty = true;
}
}
impl From<Text> for Line {
fn from(text: Text) -> Self {
Self {
text,
highlight: LineHighlight::default(),
highlight_dirty: true,
}
}
}
impl Index<usize> for Line {
type Output = char;
fn index(&self, index: usize) -> &Self::Output {
&self.text[index]
}
}
impl fmt::Display for Line {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.text, f)
}
}
+127 -32
View File
@@ -4,17 +4,23 @@ use std::{
fs::File,
io::{self, BufRead, BufReader, BufWriter, Write as IoWrite},
path::{Path, PathBuf},
rc::Rc,
};
use libterm::{Color, CursorStyle, Term};
use unicode_width::UnicodeWidthChar;
use crate::{
config::Config,
buffer::{line::Line, style::Style},
config::EditorConfig,
error::Error,
line::{Line, TextLike},
highlight::Highlighter,
text::{Text, TextLike, TextLikeMut},
};
pub mod line;
pub mod style;
#[derive(Default)]
pub struct View {
cursor_column: usize,
@@ -47,6 +53,7 @@ pub struct Buffer {
name: Option<String>,
path: Option<PathBuf>,
modified: bool,
filetype: Rc<str>,
}
impl Mode {
@@ -69,7 +76,7 @@ impl View {
}
}
pub fn set_column(&mut self, config: &Config, col: usize, line: Option<&Line>) {
pub fn set_column(&mut self, config: &EditorConfig, col: usize, line: Option<&Line>) {
let Some(line) = line else {
self.column_offset = 0;
self.cursor_column = 0;
@@ -100,14 +107,6 @@ impl View {
self.cursor_row = 0;
self.cursor_column = 0;
}
// pub fn resize(&mut self, width: usize, height: usize) {
// self.width = width;
// self.height = height;
// self.column_offset = 0;
// self.row_offset = 0;
// }
}
impl Buffer {
@@ -121,6 +120,7 @@ impl Buffer {
name: None,
path: None,
modified: false,
filetype: "text".into(),
}
}
@@ -129,7 +129,8 @@ impl Buffer {
let lines = input.lines().collect::<Result<Vec<_>, _>>()?;
let lines = lines
.into_iter()
.map(|line| Line::from_str(line.trim_end_matches('\n')))
.map(|line| Text::from_str(line.trim_end_matches('\n')))
.map(Line::from)
.collect();
Ok(lines)
}
@@ -143,7 +144,7 @@ impl Buffer {
vec![]
};
Ok(Self {
let mut this = Self {
lines,
name,
path: Some(path.into()),
@@ -152,7 +153,13 @@ impl Buffer {
mode_dirty: true,
view: View::default(),
modified: false,
})
filetype: "text".into(),
};
this.update_filetype();
this.reset_highlight();
Ok(this)
}
pub fn reopen<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
@@ -173,10 +180,34 @@ impl Buffer {
self.path = Some(path.into());
self.name = name;
self.update_filetype();
self.reset_highlight();
Ok(())
}
pub fn update_filetype(&mut self) {
let extension = self
.path
.as_ref()
.and_then(|path| path.extension())
.and_then(|ext| ext.to_str());
let filetype = match extension {
Some("lysp") => "lysp",
Some("rs") => "rust",
_ => "text",
};
self.filetype = filetype.into();
}
pub fn reset_highlight(&mut self) {
for line in self.lines.iter_mut() {
line.highlight_dirty = true;
}
}
pub fn save(&mut self) -> Result<(), Error> {
let path = self.path.as_ref().ok_or(Error::NoPath)?;
let mut writer = BufWriter::new(File::create(path).map_err(Error::WriteError)?);
@@ -201,7 +232,7 @@ impl Buffer {
self.name = name;
}
pub fn set_mode(&mut self, config: &Config, mode: SetMode) {
pub fn set_mode(&mut self, config: &EditorConfig, mode: SetMode) {
let dst_mode = match mode {
SetMode::Normal => Mode::Normal,
SetMode::InsertAfter | SetMode::InsertBefore => Mode::Insert,
@@ -220,6 +251,10 @@ impl Buffer {
}
}
pub fn filetype(&self) -> &str {
&self.filetype
}
pub fn mode(&self) -> Mode {
self.mode
}
@@ -248,7 +283,7 @@ impl Buffer {
self.view.cursor_row
}
pub fn set_position(&mut self, config: &Config, px: usize, py: usize) {
pub fn set_position(&mut self, config: &EditorConfig, px: usize, py: usize) {
self.dirty = true;
if self.lines.is_empty() {
@@ -275,7 +310,7 @@ impl Buffer {
}
}
pub fn to_line_end(&mut self, config: &Config) {
pub fn to_line_end(&mut self, config: &EditorConfig) {
let len = self
.lines
.get(self.view.cursor_row)
@@ -285,7 +320,7 @@ impl Buffer {
self.set_position(config, len, self.view.cursor_row);
}
pub fn to_first_line(&mut self, config: &Config) {
pub fn to_first_line(&mut self, config: &EditorConfig) {
let len = self
.lines
.get(self.view.cursor_row)
@@ -295,7 +330,7 @@ impl Buffer {
self.set_position(config, len, 0);
}
pub fn to_last_line(&mut self, config: &Config) {
pub fn to_last_line(&mut self, config: &EditorConfig) {
if self.lines.is_empty() {
return;
}
@@ -309,18 +344,18 @@ impl Buffer {
self.set_position(config, len, self.lines.len() - 1);
}
pub fn set_column(&mut self, config: &Config, x: usize) {
pub fn set_column(&mut self, config: &EditorConfig, x: usize) {
self.set_position(config, x, self.view.cursor_row);
}
pub fn move_cursor(&mut self, config: &Config, dx: isize, dy: isize) {
pub fn move_cursor(&mut self, config: &EditorConfig, dx: isize, dy: isize) {
let px = (self.view.cursor_column as isize + dx).max(0) as usize;
let py = (self.view.cursor_row as isize + dy).max(0) as usize;
self.set_position(config, px, py);
}
pub fn resize(&mut self, config: &Config, offset_x: usize, width: usize, height: usize) {
pub fn resize(&mut self, config: &EditorConfig, offset_x: usize, width: usize, height: usize) {
self.dirty = true;
self.view.height = height;
self.view.width = width;
@@ -334,7 +369,7 @@ impl Buffer {
);
}
pub fn display_cursor(&self, config: &Config) -> (usize, usize) {
pub fn display_cursor(&self, config: &EditorConfig) -> (usize, usize) {
if self.lines.is_empty() {
return (0, 0);
}
@@ -374,23 +409,56 @@ impl Buffer {
fn display_line(
&self,
config: &Config,
config: &EditorConfig,
current_style: &mut Style,
term: &mut Term,
row: usize,
line: &Line,
hi: &Highlighter,
) -> Result<(), Error> {
let mut pos = 0;
term.set_cursor_position(row, self.view.offset_x)
.map_err(Error::TerminalError)?;
let span = line.skip_to_width(self.view.column_offset, config.tab_width);
let (span, _skipped_display_cells, skipped_characters) =
line.skip_to_width(self.view.column_offset, config.tab_width);
let long_line = span.display_width(config.tab_width) > self.view.width;
for &ch in span.iter() {
let mut current_category: Option<&str> = None;
// Reset style
let reset_style = Style::default();
reset_style
.apply_delta(current_style, term)
.map_err(Error::TerminalError)?;
*current_style = reset_style;
for (index, &ch) in span.iter().enumerate() {
if pos >= self.view.width {
break;
}
let character_index = skipped_characters + index;
let matching_token = line
.highlight
.tokens
.iter()
.find(|t| t.range.contains(&character_index));
let token_category = matching_token.map(|t| t.category.as_ref());
if token_category != current_category {
current_category = token_category;
let style = token_category
.and_then(|cat| hi.stylize(&self.filetype, cat, current_style))
.unwrap_or_else(Style::default);
style
.apply_delta(current_style, term)
.map_err(Error::TerminalError)?;
*current_style = style;
}
if ch == '\t' {
let old_pos = pos;
let new_pos = (pos + config.tab_width) & !(config.tab_width - 1);
@@ -406,6 +474,7 @@ impl Buffer {
term.write_char('>').map_err(Error::TerminalFmtError)?;
term.set_foreground(Color::Default)
.map_err(Error::TerminalError)?;
current_style.foreground = Color::Default;
} else {
term.write_char(' ').map_err(Error::TerminalFmtError)?;
}
@@ -433,7 +502,14 @@ impl Buffer {
Ok(())
}
pub fn display(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
pub fn display(
&mut self,
config: &EditorConfig,
term: &mut Term,
hi: &Highlighter,
) -> Result<(), Error> {
hi.rehighlight(&mut self.lines, &self.filetype);
match self.mode {
Mode::Normal => {
term.set_cursor_style(CursorStyle::Default)
@@ -445,6 +521,8 @@ impl Buffer {
}
}
let mut current_style = Style::default();
for (row, line) in self
.lines
.iter()
@@ -452,13 +530,27 @@ impl Buffer {
.take(self.view.height)
.enumerate()
{
self.display_line(config, term, row, line)?;
self.display_line(config, &mut current_style, term, row, line, hi)?;
}
// Reset to default style
Style::DEFAULT
.apply_delta(&current_style, term)
.map_err(Error::TerminalError)?;
Ok(())
}
pub fn set_terminal_cursor(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
pub fn get_terminal_cursor(&self, config: &EditorConfig) -> (usize, usize) {
let (x, y) = self.display_cursor(config);
(x + self.view.offset_x, y)
}
pub fn set_terminal_cursor(
&mut self,
config: &EditorConfig,
term: &mut Term,
) -> Result<(), Error> {
let (x, y) = self.display_cursor(config);
if self.mode_dirty {
match self.mode {
@@ -497,7 +589,7 @@ impl Buffer {
self.lines.insert(self.view.cursor_row + 1, newline);
}
pub fn insert(&mut self, config: &Config, ch: char) {
pub fn insert(&mut self, config: &EditorConfig, ch: char) {
if self.lines.is_empty() {
assert_eq!(self.view.cursor_row, 0);
self.lines.push(Line::new());
@@ -509,7 +601,7 @@ impl Buffer {
self.modified = true;
}
pub fn erase_backward(&mut self, config: &Config) {
pub fn erase_backward(&mut self, config: &EditorConfig) {
if self.lines.is_empty() {
return;
}
@@ -547,12 +639,15 @@ impl Buffer {
self.modified = true;
}
pub fn kill_line(&mut self, config: &Config) {
pub fn kill_line(&mut self, config: &EditorConfig) {
if self.lines.is_empty() {
return;
}
self.lines.remove(self.view.cursor_row);
if self.view.cursor_row < self.lines.len() {
self.lines[self.view.cursor_row].highlight_dirty = true;
}
self.move_cursor(config, 0, 1);
self.modified = true;
}
+45
View File
@@ -0,0 +1,45 @@
use std::io;
use libterm::{Color, Term};
#[derive(Clone, Copy)]
pub struct Style {
pub foreground: Color,
pub background: Color,
pub bold: bool,
}
impl Default for Style {
fn default() -> Self {
Self::DEFAULT
}
}
impl Style {
pub const DEFAULT: Self = Self {
foreground: Color::White,
background: Color::Black,
bold: false,
};
pub fn apply(&self, term: &mut Term) -> Result<(), io::Error> {
self.apply_delta(&Self::DEFAULT, term)
}
pub fn apply_delta(&self, original: &Self, term: &mut Term) -> Result<(), io::Error> {
// TODO libterm Color PartialEq
if self.foreground as u32 != original.foreground as u32 {
// Apply foreground
term.set_foreground(self.foreground)?;
}
if self.background as u32 != original.background as u32 {
// Apply background
term.set_background(self.background)?;
}
if self.bold != original.bold {
// Apply bold
term.set_bright(self.bold)?;
}
Ok(())
}
}
-144
View File
@@ -1,144 +0,0 @@
use std::ops::RangeInclusive;
use crate::{
State,
buffer::{Buffer, SetMode},
config::Config,
error::Error,
};
pub type CommandFn = fn(&mut State, &[&str]) -> Result<(), Error>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action {
// Editing
EraseBackward,
InsertBefore,
InsertAfter,
NewlineBefore,
NewlineAfter,
BreakLine,
KillLine,
// Movement
MoveFirstLine,
MoveLastLine,
MoveCharPrev,
MoveCharNext,
MoveLineBack(usize),
MoveLineForward(usize),
MoveLineStart,
MoveLineEnd,
}
static COMMANDS: &[(&str, RangeInclusive<usize>, CommandFn)] = &[
("w", 0..=1, cmd_write),
("w!", 0..=1, cmd_force_write),
("q", 0..=0, cmd_exit),
("q!", 0..=0, cmd_force_exit),
("e", 1..=1, cmd_edit),
("e!", 0..=1, cmd_force_edit),
];
// Commands
fn cmd_write(state: &mut State, args: &[&str]) -> Result<(), Error> {
if args.len() == 1 && state.buffer().is_modified() && state.buffer().path().is_some() {
return Err(Error::UnsavedBuffer(
"Use :w! FILE to force write to another file",
));
}
cmd_force_write(state, args)
}
fn cmd_force_write(state: &mut State, args: &[&str]) -> Result<(), Error> {
let buffer = state.buffer_mut();
if let Some(&path) = args.first() {
buffer.set_path(path);
}
buffer.save()?;
if let Some(name) = buffer.name() {
let status = format!("{:?} written", name);
state.set_status(status);
}
Ok(())
}
fn cmd_edit(state: &mut State, args: &[&str]) -> Result<(), Error> {
if state.buffer().is_modified() {
return Err(Error::UnsavedBuffer("Use :e! [FILE] to open another file"));
}
cmd_force_edit(state, args)
}
fn cmd_force_edit(state: &mut State, args: &[&str]) -> Result<(), Error> {
if let Some(&path) = args.first() {
state.buffer_mut().reopen(path).map_err(Error::OpenError)
} else if let Some(path) = state.buffer().path().cloned() {
state.buffer_mut().reopen(path).map_err(Error::OpenError)
} else {
Err(Error::NoPath)
}
}
fn cmd_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> {
let buffer = state.buffer();
if buffer.is_modified() {
return Err(Error::UnsavedBuffer("Use :q! to force exit"));
}
state.exit();
Ok(())
}
fn cmd_force_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> {
state.exit();
Ok(())
}
pub fn execute(state: &mut State, command: String) -> Result<(), Error> {
let words = command.split(' ').collect::<Vec<_>>();
let Some((&cmd, args)) = words.split_first() else {
return Ok(());
};
for (name, nargs, f) in COMMANDS.iter() {
if *name == cmd {
if !nargs.contains(&args.len()) {
todo!();
}
return f(state, args);
}
}
Err(Error::UnknownCommand(cmd.into()))
}
pub fn perform(buffer: &mut Buffer, config: &Config, action: Action) -> Result<(), Error> {
match action {
// Editing
Action::EraseBackward => buffer.erase_backward(config),
Action::InsertBefore => buffer.set_mode(config, SetMode::InsertBefore),
Action::InsertAfter => buffer.set_mode(config, SetMode::InsertAfter),
Action::NewlineBefore => buffer.newline_before(),
Action::NewlineAfter => buffer.newline_after(false),
Action::BreakLine => buffer.newline_after(true),
Action::KillLine => buffer.kill_line(config),
// Movement
Action::MoveCharPrev => buffer.move_cursor(config, -1, 0),
Action::MoveCharNext => buffer.move_cursor(config, 1, 0),
Action::MoveLineBack(count) => buffer.move_cursor(config, 0, -(count as isize)),
Action::MoveLineForward(count) => buffer.move_cursor(config, 0, count as isize),
Action::MoveLineStart => buffer.set_column(config, 0),
Action::MoveLineEnd => buffer.to_line_end(config),
Action::MoveFirstLine => buffer.to_first_line(config),
Action::MoveLastLine => buffer.to_last_line(config),
}
Ok(())
}
+115 -66
View File
@@ -1,80 +1,129 @@
use libterm::TermKey;
use crate::{
buffer::Mode,
command::Action,
keymap::{KeyMap, KeySeq, PrefixNode, bind1, bindn},
use std::{
mem,
ops::{Deref, DerefMut},
};
pub struct Config {
// TODO must be a power of 2, lol
pub struct EditorConfig {
pub tab_width: usize,
pub number: bool,
pub nmap: KeyMap,
pub imap: KeyMap,
pub number: Dirty<bool>,
pub bottom_margin: Dirty<usize>,
}
impl Default for Config {
pub struct Dirty<T>(T, bool);
impl Default for EditorConfig {
fn default() -> Self {
use Action::*;
let nmap = KeyMap::from_iter([
bind1('i', [InsertBefore]),
bind1('a', [InsertAfter]),
bind1('h', [MoveCharPrev]),
bind1('l', [MoveCharNext]),
bind1('j', [MoveLineForward(1)]),
bind1('J', [MoveLineForward(25)]),
bind1('k', [MoveLineBack(1)]),
bind1('K', [MoveLineBack(25)]),
bind1(TermKey::Left, [MoveCharPrev]),
bind1(TermKey::Right, [MoveCharNext]),
bind1(TermKey::Up, [MoveLineBack(1)]),
bind1(TermKey::Down, [MoveLineForward(1)]),
bind1(TermKey::Home, [MoveLineStart]),
bind1(TermKey::End, [MoveLineEnd]),
bindn(['g', 'g'], [MoveFirstLine]),
bind1('G', [MoveLastLine]),
bind1('I', [MoveLineStart, InsertBefore]),
bind1('A', [MoveLineEnd, InsertAfter]),
bind1('o', [NewlineAfter, MoveLineForward(1), InsertBefore]),
bind1('O', [NewlineBefore, MoveLineBack(1), InsertBefore]),
bindn(['d', 'd'], [KillLine]),
]);
let imap = KeyMap::from_iter([
bind1('\x7F', [EraseBackward]),
bind1(TermKey::Left, [MoveCharPrev]),
bind1(TermKey::Right, [MoveCharNext]),
bind1(TermKey::Up, [MoveLineBack(1)]),
bind1(TermKey::Down, [MoveLineForward(1)]),
bind1(TermKey::Home, [MoveLineStart]),
bind1(TermKey::End, [MoveLineEnd]),
bind1(
'\n',
[BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
),
bind1(
'\x0D',
[BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
),
]);
Self {
tab_width: 4,
number: true,
nmap,
imap,
number: Dirty::new(false),
bottom_margin: Dirty::new(0),
}
}
}
impl Config {
pub fn key_seq(&self, mode: Mode, seq: &KeySeq) -> Option<&PrefixNode<KeySeq, Vec<Action>>> {
match mode {
Mode::Normal => self.nmap.get(seq),
Mode::Insert => self.imap.get(seq),
}
impl<T> Dirty<T> {
pub const fn new(value: T) -> Self {
Self(value, true)
}
pub fn clean(&mut self) -> bool {
mem::replace(&mut self.1, false)
}
}
impl<T> Deref for Dirty<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Dirty<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.1 = true;
&mut self.0
}
}
//
// use libterm::TermKey;
//
// use crate::{
// buffer::Mode,
// command::Action,
// keymap::{KeyMap, KeySeq, PrefixNode, bind1, bindn},
// };
//
// pub struct Config {
// // TODO must be a power of 2, lol
// pub tab_width: usize,
// pub number: bool,
//
// pub nmap: KeyMap,
// pub imap: KeyMap,
// }
//
// impl Default for Config {
// fn default() -> Self {
// use Action::*;
//
// let nmap = KeyMap::from_iter([
// bind1('i', [InsertBefore]),
// bind1('a', [InsertAfter]),
// bind1('h', [MoveCharPrev]),
// bind1('l', [MoveCharNext]),
// bind1('j', [MoveLineForward(1)]),
// bind1('J', [MoveLineForward(25)]),
// bind1('k', [MoveLineBack(1)]),
// bind1('K', [MoveLineBack(25)]),
// bind1(TermKey::Left, [MoveCharPrev]),
// bind1(TermKey::Right, [MoveCharNext]),
// bind1(TermKey::Up, [MoveLineBack(1)]),
// bind1(TermKey::Down, [MoveLineForward(1)]),
// bind1(TermKey::Home, [MoveLineStart]),
// bind1(TermKey::End, [MoveLineEnd]),
// bindn(['g', 'g'], [MoveFirstLine]),
// bind1('G', [MoveLastLine]),
// bind1('I', [MoveLineStart, InsertBefore]),
// bind1('A', [MoveLineEnd, InsertAfter]),
// bind1('o', [NewlineAfter, MoveLineForward(1), InsertBefore]),
// bind1('O', [NewlineBefore, MoveLineBack(1), InsertBefore]),
// bindn(['d', 'd'], [KillLine]),
// ]);
//
// let imap = KeyMap::from_iter([
// bind1('\x7F', [EraseBackward]),
// bind1(TermKey::Left, [MoveCharPrev]),
// bind1(TermKey::Right, [MoveCharNext]),
// bind1(TermKey::Up, [MoveLineBack(1)]),
// bind1(TermKey::Down, [MoveLineForward(1)]),
// bind1(TermKey::Home, [MoveLineStart]),
// bind1(TermKey::End, [MoveLineEnd]),
// bind1(
// '\n',
// [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
// ),
// bind1(
// '\x0D',
// [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
// ),
// ]);
//
// Self {
// tab_width: 4,
// number: true,
// nmap,
// imap,
// }
// }
// }
//
// impl Config {
// pub fn key_seq(&self, mode: Mode, seq: &KeySeq) -> Option<&PrefixNode<KeySeq, Vec<Action>>> {
// match mode {
// Mode::Normal => self.nmap.get(seq),
// Mode::Insert => self.imap.get(seq),
// }
// }
// }
+15
View File
@@ -1,5 +1,7 @@
use std::{fmt, io};
use lysp::error::{MachineError, ValueConversionError};
#[derive(Debug, thiserror::Error)]
pub enum Error {
// I/O errors
@@ -19,4 +21,17 @@ pub enum Error {
TerminalError(io::Error),
#[error("Terminal error: {0:?}")]
TerminalFmtError(fmt::Error),
#[error("Command error: {0}")]
Command(io::Error),
#[error("Command error: {0:?}")]
CommandStatus(Option<i32>),
#[error("Scripting error: {0:?}")]
Script(#[from] MachineError),
}
impl From<ValueConversionError> for Error {
fn from(value: ValueConversionError) -> Self {
Self::Script(value.into())
}
}

Some files were not shown because too many files have changed in this diff Show More