From 5e35da49ef4ee5cd7c1de4af9adc133d5ff5eefe Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sat, 3 May 2025 12:49:48 +0800 Subject: [PATCH] feat: init --- .envrc | 1 + .gitignore | 3 + Cargo.lock | 656 ++++++++++++++++++++++++++++++++++ Cargo.toml | 19 + flake.lock | 66 ++++ flake.nix | 32 ++ src/builtins/mod.rs | 86 +++++ src/bytecode.rs | 230 ++++++++++++ src/compile/compile.rs | 390 ++++++++++++++++++++ src/compile/env.rs | 19 + src/compile/ir.rs | 772 ++++++++++++++++++++++++++++++++++++++++ src/compile/mod.rs | 10 + src/compile/symtable.rs | 35 ++ src/downcast.rs | 8 + src/lib.rs | 9 + src/slice.rs | 2 + src/value/mod.rs | 108 ++++++ src/vm/env.rs | 47 +++ src/vm/mod.rs | 12 + src/vm/stack.rs | 93 +++++ src/vm/test.rs | 169 +++++++++ src/vm/value/attrset.rs | 53 +++ src/vm/value/list.rs | 36 ++ src/vm/value/mod.rs | 251 +++++++++++++ src/vm/value/string.rs | 30 ++ src/vm/vm.rs | 188 ++++++++++ src/vm/vmthunk.rs | 65 ++++ 27 files changed, 3390 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/builtins/mod.rs create mode 100644 src/bytecode.rs create mode 100644 src/compile/compile.rs create mode 100644 src/compile/env.rs create mode 100644 src/compile/ir.rs create mode 100644 src/compile/mod.rs create mode 100644 src/compile/symtable.rs create mode 100644 src/downcast.rs create mode 100644 src/lib.rs create mode 100644 src/slice.rs create mode 100644 src/value/mod.rs create mode 100644 src/vm/env.rs create mode 100644 src/vm/mod.rs create mode 100644 src/vm/stack.rs create mode 100644 src/vm/test.rs create mode 100644 src/vm/value/attrset.rs create mode 100644 src/vm/value/list.rs create mode 100644 src/vm/value/mod.rs create mode 100644 src/vm/value/string.rs create mode 100644 src/vm/vm.rs create mode 100644 src/vm/vmthunk.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8a4d91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ + +/.direnv/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9a86ed5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,656 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "archery" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8967cd1cc9e9e1954f644e14fbd6042fe9a37da96c52a67e44a2ac18261f8561" +dependencies = [ + "static_assertions", + "triomphe", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "ecow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54bfbb1708988623190a6c4dbedaeaf0f53c20c6395abd6a01feb327b3146f4b" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "crossbeam-channel", + "derive_more", + "ecow", + "itertools", + "num_cpus", + "once_cell", + "rayon", + "rnix", + "rpds", + "tokio", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rnix" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb35cedbeb70e0ccabef2a31bcff0aebd114f19566086300b8f42c725fc2cb5f" +dependencies = [ + "rowan", +] + +[[package]] +name = "rowan" +version = "0.15.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49" +dependencies = [ + "countme", + "hashbrown", + "memoffset", + "rustc-hash", + "text-size", +] + +[[package]] +name = "rpds" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e15515d3ce3313324d842629ea4905c25a13f81953eadb88f85516f59290a4" +dependencies = [ + "archery", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + +[[package]] +name = "tokio" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0bd824e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "nix-rs" +version = "0.1.0" +edition = "2024" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rnix = "0.11" +anyhow = "1.0" +itertools = "0.12" +rayon = "1.10" +tokio = { version = "1.38", features = [ "full" ] } +rpds = "1.1" +derive_more = "0.99" +crossbeam-channel = "0.5" +num_cpus = "1.0" +ecow = "0.2" +once_cell = "1.19" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..582448b --- /dev/null +++ b/flake.lock @@ -0,0 +1,66 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1746167999, + "narHash": "sha256-18XGHsjk/5H8F0OGUCG56CeeW1u6qQ7tAfQK3azlwWg=", + "owner": "nix-community", + "repo": "fenix", + "rev": "bcbc23a4f3391c1c3657f1847cb693aaea3aed76", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1746141548, + "narHash": "sha256-IgBWhX7A2oJmZFIrpRuMnw5RAufVnfvOgHWgIdds+hc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "f02fddb8acef29a8b32f10a335d44828d7825b78", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1746093169, + "narHash": "sha256-3gmUmzIzfzlgF/b4HXvtoBIP4bKofVeEubX7LcPBYLo=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "298fa81aacda7b06de4db55c377b1aa081906bc9", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..1cfffd9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,32 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + fenix.url = "github:nix-community/fenix"; + fenix.inputs.nixpkgs.follows = "nixpkgs"; + }; + outputs = { nixpkgs, fenix, ... }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + in + { + formatter.${system} = pkgs.nixpkgs-fmt; + devShells.${system}.default = pkgs.mkShell { + packages = with pkgs; [ + zsh + (fenix.packages.${system}.complete.withComponents [ + "cargo" + "clippy" + "rust-src" + "rustc" + "rustfmt" + "rust-analyzer" + ]) + ]; + shellHook = '' + zsh & disown + exit + ''; + }; + }; +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs new file mode 100644 index 0000000..2bdaede --- /dev/null +++ b/src/builtins/mod.rs @@ -0,0 +1,86 @@ +use derive_more::Constructor; + +use super::vm::Env; +use super::vm::Symbol; +use super::vm::Value; +use crate::bytecode::Const; + +pub fn env() -> Env { + let mut env = Env::empty(); + env.insert(Symbol::from("true"), Value::Const(Const::Bool(true))); + env.insert(Symbol::from("false"), Value::Const(Const::Bool(false))); + + env.insert(Symbol::from("__add"), Value::PrimOp(PrimOp::new(2, |args| { + let [first, second]: [Value; 2] = args.try_into().unwrap(); + first.add(second) + }))); + env.insert(Symbol::from("__sub"), Value::PrimOp(PrimOp::new(2, |args| { + let [first, second]: [Value; 2] = args.try_into().unwrap(); + first.add(second.neg()) + }))); + env.insert(Symbol::from("__mul"), Value::PrimOp(PrimOp::new(2, |args| { + let [first, second]: [Value; 2] = args.try_into().unwrap(); + first.mul(second) + }))); + env.insert(Symbol::from("__div"), Value::PrimOp(PrimOp::new(2, |args| { + let [first, second]: [Value; 2] = args.try_into().unwrap(); + first.div(second) + }))); + env.insert(Symbol::from("__lessThan"), Value::PrimOp(PrimOp::new(2, |args| { + let [first, second]: [Value; 2] = args.try_into().unwrap(); + first.lt(second) + }))); + + env +} + +#[derive(Debug, Clone, Constructor)] +pub struct PrimOp { + arity: u8, + func: fn(Vec) -> Value, +} + +impl PartialEq for PrimOp { + fn eq(&self, _: &Self) -> bool { + false + } +} + +impl PrimOp { + pub fn call(self, args: Vec) -> Value { + if (args.len() as u8) < self.arity { + Value::PartialPrimOp(PartialPrimOp { arity: self.arity - args.len() as u8, args, func: self.func }) + } else if args.len() as u8 == self.arity { + (self.func)(args) + } else { + unimplemented!() + } + } +} + +#[derive(Debug, Clone)] +pub struct PartialPrimOp { + arity: u8, + args: Vec, + func: fn(Vec) -> Value +} + +impl PartialEq for PartialPrimOp { + fn eq(&self, _: &Self) -> bool { + false + } +} + +impl PartialPrimOp { + pub fn call(mut self, args: Vec) -> Value { + let len = args.len() as u8; + self.args.extend(args); + if len < self.arity { + Value::PartialPrimOp(PartialPrimOp { arity: self.arity - len, args: self.args, func: self.func }) + } else if len == self.arity { + (self.func)(self.args) + } else { + unimplemented!() + } + } +} diff --git a/src/bytecode.rs b/src/bytecode.rs new file mode 100644 index 0000000..5e2a659 --- /dev/null +++ b/src/bytecode.rs @@ -0,0 +1,230 @@ +use std::hash::{Hash, Hasher}; + +use ecow::EcoString; +use anyhow::Error; +use derive_more::{IsVariant, Unwrap}; + +use crate::slice::Slice; +use crate::value::Func; + +pub type ThunkIdx = usize; +pub type ConstIdx = usize; +pub type SymIdx = usize; +pub type OpCodes = Slice; +pub type Consts = Slice; +pub type Thunks = Slice; +pub type Args = Slice; + +#[derive(Debug, Clone)] +pub struct Thunk { + pub opcodes: OpCodes, +} + +#[derive(Debug, Clone, Hash)] +pub enum Arg {} + +#[derive(Debug, Clone, IsVariant, Unwrap)] +pub enum Const { + Bool(bool), + Int(i64), + Float(f64), + String(EcoString), + Func(Func), +} + +impl From for Const { + fn from(value: bool) -> Self { + Const::Bool(value) + } +} + +impl From for Const { + fn from(value: i64) -> Self { + Const::Int(value) + } +} + +impl From for Const { + fn from(value: f64) -> Self { + Const::Float(value) + } +} + +impl From for Const { + fn from(value: EcoString) -> Self { + Const::String(value) + } +} + +impl From for Const { + fn from(value: String) -> Self { + Const::String(value.into()) + } +} + +impl From<&str> for Const { + fn from(value: &str) -> Self { + Const::String(value.into()) + } +} + +impl<'a> TryFrom<&'a Const> for &'a bool { + type Error = Error; + + fn try_from(value: &'a Const) -> Result { + match value { + Const::Bool(b) => Ok(b), + _ => panic!(), + } + } +} +impl<'a> TryFrom<&'a Const> for &'a i64 { + type Error = Error; + + fn try_from(value: &'a Const) -> Result { + match value { + Const::Int(int) => Ok(int), + _ => panic!(), + } + } +} + +impl<'a> TryFrom<&'a Const> for &'a f64 { + type Error = Error; + + fn try_from(value: &'a Const) -> Result { + match value { + Const::Float(float) => Ok(float), + _ => panic!(), + } + } +} + +impl<'a> TryFrom<&'a Const> for &'a str { + type Error = Error; + + fn try_from(value: &'a Const) -> Result { + match value { + Const::String(string) => Ok(string), + _ => panic!(), + } + } +} + +impl PartialEq for Const { + fn eq(&self, other: &Self) -> bool { + use Const::*; + match (self, other) { + (Bool(a), Bool(b)) => a == b, + (Int(a), Int(b)) => a == b, + (Float(a), Float(b)) => a == b, + (String(a), String(b)) => a == b, + _ => false, + } + } +} + +impl Eq for Const {} + +impl Hash for Const { + fn hash(&self, state: &mut H) { + use Const::*; + match self { + Bool(b) => b.hash(state), + Int(int) => int.hash(state), + Float(float) => float.to_bits().hash(state), + String(string) => string.hash(state), + Func(func) => func.hash(state), + } + } +} + +#[derive(Debug, Clone, Hash)] +pub enum OpCode { + /// load a constant onto stack + Const { value: Const }, + /// load a dynamic var onto stack + LookUp { sym: EcoString }, + /// load a thunk lazily onto stack + LoadThunk { idx: ThunkIdx }, + /// load a thunk onto stack and force its value + LoadValue { idx: ThunkIdx }, + /// force TOS to value + ForceValue, + /// [ ... func, args @ .. ] call func with `arity` numbers of arg + Call { arity: usize }, + /// assert TOS is true then consume it + Assert, + /// jump forward + Jmp { step: usize }, + /// [ ... cond ] if (cond) is true, then jump forward + JmpIfTrue { step: usize }, + /// [ ... cond ] if (cond) is false, then jump forward + JmpIfFalse { step: usize }, + /// push an empty attribute set onto stack + AttrSet, + /// push an empty recursive attribute set onto stack + RecAttrSet, + /// [ ... set, value ] push the static kv pair (name, (value)) into (set) + PushStaticAttr { name: EcoString }, + /// [ ... set, name, value ] push the dynamic kv pair ((name), (value)) in to (set) + PushDynamicAttr, + /// push an empty list onto stack + List, + /// [ ... list, elem ] push (elem) into (list) + PushElem, + /// [ ... a, b ] perform a binary operation ((a) `op` (b)) + BinOp { op: BinOp }, + /// [ ... a ] perform a unary operation (`op` (a)) + UnOp { op: UnOp }, + /// TODO: + ConcatString, + /// TODO: + HasAttr { sym: EcoString }, + /// TODO: + HasDynamicAttr, + // HasAttr { arity: usize }, + /// TODO: + Select { sym: EcoString }, + // Select { arity: usize }, + /// TODO: + SelectDynamic, + // SelectDynamic { arity: usize }, + /// TODO: + SelectOrEmpty { sym: EcoString }, + /// TODO: + SelectDynamicOrEmpty, + /// TODO: + SelectWithDefault { sym: EcoString }, + /// TODO: + SelectDynamicWithDefault, + /// enter the environment of the attribute set at TOS + EnterEnv, + /// exit the envrironment + LeaveEnv, + /// return a value + Ret, + /// no-op + NoOp, +} + +#[derive(Debug, Clone, Copy, Hash)] +pub enum BinOp { + Add, + And, + Or, + Eq, + Con, + Upd, +} + +#[derive(Debug, Clone, Copy, Hash)] +pub enum UnOp { + Not, +} + +#[derive(Debug)] +pub struct Program { + pub top_level: OpCodes, + pub thunks: Thunks, +} diff --git a/src/compile/compile.rs b/src/compile/compile.rs new file mode 100644 index 0000000..bc58178 --- /dev/null +++ b/src/compile/compile.rs @@ -0,0 +1,390 @@ +use crate::bytecode::*; + +use super::ir; + +pub struct Compiler { + opcodes: Vec, +} + +pub fn compile(downgraded: ir::Downgraded) -> Program { + Program { + top_level: Compiler::new().compile(downgraded.top_level), + thunks: downgraded + .thunks + .into_iter() + .map(|thunk| Thunk { + opcodes: Compiler::new().compile(thunk), + }) + .collect(), + } +} + +impl Compiler { + fn new() -> Self { + Self { + opcodes: Vec::new(), + } + } + + fn compile(mut self, ir: ir::Ir) -> OpCodes { + ir.compile(&mut self); + self.opcodes() + } + + fn push(&mut self, code: OpCode) { + self.opcodes.push(code); + } + + fn idx(&self) -> usize { + self.opcodes.len() + } + + fn modify(&mut self, idx: usize, code: OpCode) { + self.opcodes[idx] = code; + } + + fn last(&self) -> Option { + self.opcodes.last().cloned() + } + + fn pop(&mut self) -> Option { + self.opcodes.pop() + } + + fn opcodes(self) -> OpCodes { + self.opcodes.into() + } +} + +pub trait Compile { + fn compile(self, comp: &mut Compiler); +} + +pub trait CompileWithLength { + fn compile_with_length(self, comp: &mut Compiler) -> usize; +} + +impl CompileWithLength for T { + fn compile_with_length(self, comp: &mut Compiler) -> usize { + let start = comp.idx(); + self.compile(comp); + let end = comp.idx(); + end - start + } +} + +impl Compile for ir::Const { + fn compile(self, comp: &mut Compiler) { + comp.push(OpCode::Const { value: self.value }); + } +} + +impl Compile for ir::Var { + fn compile(self, comp: &mut Compiler) { + comp.push(OpCode::LookUp { sym: self.sym }); + } +} + +impl Compile for ir::Thunk { + fn compile(self, comp: &mut Compiler) { + comp.push(OpCode::LoadThunk { idx: self.idx }); + } +} + +impl Compile for ir::Attrs { + fn compile(self, comp: &mut Compiler) { + comp.push(OpCode::AttrSet); + for stc in self.stcs { + stc.1.compile(comp); + comp.push(OpCode::PushStaticAttr { name: stc.0 }); + } + for dynamic in self.dyns { + dynamic.0.compile(comp); + dynamic.1.compile(comp); + comp.push(OpCode::PushDynamicAttr) + } + } +} + +impl Compile for ir::RecAttrs { + fn compile(self, comp: &mut Compiler) { + comp.push(OpCode::AttrSet); + for dynamic in self.dyns.clone() { + dynamic.0.compile(comp); + dynamic.1.compile(comp); + comp.push(OpCode::PushDynamicAttr) + } + comp.push(OpCode::EnterEnv); + comp.push(OpCode::AttrSet); + for stc in self.stcs { + stc.1.compile(comp); + comp.push(OpCode::PushStaticAttr { name: stc.0 }); + } + for dynamic in self.dyns { + dynamic.0.compile(comp); + dynamic.1.compile(comp); + comp.push(OpCode::PushDynamicAttr) + } + comp.push(OpCode::LeaveEnv); + } +} + +impl Compile for ir::List { + fn compile(self, comp: &mut Compiler) { + comp.push(OpCode::List); + for item in self.items { + item.compile(comp); + comp.push(OpCode::PushElem); + } + } +} + +impl Compile for ir::UnOp { + fn compile(self, comp: &mut Compiler) { + use ir::UnOpKind::*; + match self.kind { + Neg => { + comp.push(OpCode::LookUp { sym: "__sub".into() }); + comp.push(OpCode::Const { value: Const::Int(0) }); + self.rhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + } + Not => { + self.rhs.compile(comp); + comp.push(OpCode::UnOp { op: UnOp::Not }); + } + } + } +} + +impl Compile for ir::BinOp { + fn compile(self, comp: &mut Compiler) { + use ir::BinOpKind::*; + match self.kind { + Add => { + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::Add }); + } + Mul => { + comp.push(OpCode::LookUp { sym: "__mul".into() }); + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + } + Div => { + comp.push(OpCode::LookUp { sym: "__div".into() }); + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + } + And => { + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::And }); + } + Or => { + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::Or }); + } + Eq => { + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::Eq }); + } + Lt => { + comp.push(OpCode::LookUp { sym: "__lessThan".into() }); + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + } + Con => { + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::Con }); + } + Upd => { + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::Upd }); + } + + Sub => { + comp.push(OpCode::LookUp { sym: "__sub".into() }); + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + } + Impl => { + self.lhs.compile(comp); + comp.push(OpCode::UnOp { op: UnOp::Not }); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::Or }); + } + Neq => { + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::BinOp { op: BinOp::Eq }); + comp.push(OpCode::UnOp { op: UnOp::Not }); + } + Gt => { + comp.push(OpCode::LookUp { sym: "__lessThan".into() }); + self.rhs.compile(comp); + self.lhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + } + Leq => { + comp.push(OpCode::LookUp { sym: "__lessThan".into() }); + self.rhs.compile(comp); + self.lhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + comp.push(OpCode::UnOp { op: UnOp::Not }); + } + Geq => { + comp.push(OpCode::LookUp { sym: "__lessThan".into() }); + self.lhs.compile(comp); + self.rhs.compile(comp); + comp.push(OpCode::Call { arity: 2 }); + comp.push(OpCode::UnOp { op: UnOp::Not }); + } + } + } +} + +impl Compile for ir::HasAttr { + fn compile(self, comp: &mut Compiler) { + self.lhs.compile(comp); + for attr in self.rhs { + match attr { + ir::Attr::Str(sym) => { + comp.push(OpCode::SelectOrEmpty { sym }); + } + ir::Attr::Dynamic(dynamic) => { + dynamic.compile(comp); + comp.push(OpCode::SelectDynamicOrEmpty); + } + ir::Attr::Strs(string) => { + string.compile(comp); + comp.push(OpCode::SelectDynamicOrEmpty); + } + } + } + match comp.pop().unwrap() { + OpCode::SelectOrEmpty { sym } => comp.push(OpCode::HasAttr { sym }), + OpCode::SelectDynamicOrEmpty => comp.push(OpCode::HasDynamicAttr), + _ => unreachable!(), + } + } +} + +impl Compile for ir::Select { + fn compile(self, comp: &mut Compiler) { + self.expr.compile(comp); + for attr in self.attrpath { + match attr { + ir::Attr::Str(sym) => comp.push(OpCode::SelectOrEmpty { sym }), + ir::Attr::Dynamic(dynamic) => { + dynamic.compile(comp); + comp.push(OpCode::SelectDynamicOrEmpty); + } + ir::Attr::Strs(string) => { + string.compile(comp); + comp.push(OpCode::SelectDynamicOrEmpty); + } + } + } + match self.default { + Some(default) => { + let last = comp.pop().unwrap(); + default.compile(comp); + match last { + OpCode::SelectOrEmpty { sym } => comp.push(OpCode::SelectWithDefault { sym }), + OpCode::SelectDynamicOrEmpty => comp.push(OpCode::SelectDynamicWithDefault), + _ => unreachable!(), + } + } + None => match comp.pop().unwrap() { + OpCode::SelectOrEmpty { sym } => comp.push(OpCode::Select { sym }), + OpCode::SelectDynamicOrEmpty => comp.push(OpCode::SelectDynamic), + _ => unreachable!(), + }, + } + } +} + +impl Compile for ir::ConcatStrings { + fn compile(self, comp: &mut Compiler) { + let mut iter = self.parts.into_iter(); + iter.next().unwrap().compile(comp); + for item in iter { + item.compile(comp); + comp.push(OpCode::ConcatString); + } + } +} + +impl Compile for ir::If { + fn compile(self, comp: &mut Compiler) { + self.cond.compile(comp); + + let idx_jmp_if_false = comp.idx(); + // place holder + comp.push(OpCode::NoOp); + + let consq_length = self.consq.compile_with_length(comp); + + let idx_jmp = comp.idx(); + // place holder + comp.push(OpCode::NoOp); + + let alter_length = self.alter.compile_with_length(comp); + + comp.modify(idx_jmp_if_false, OpCode::JmpIfFalse { step: consq_length }); + comp.modify(idx_jmp, OpCode::Jmp { step: alter_length }); + } +} + +impl Compile for ir::Let { + fn compile(self, comp: &mut Compiler) { + self.attrs.compile(comp); + comp.push(OpCode::EnterEnv); + self.expr.compile(comp); + comp.push(OpCode::LeaveEnv); + } +} + +impl Compile for ir::With { + fn compile(self, comp: &mut Compiler) { + self.namespace.compile(comp); + comp.push(OpCode::EnterEnv); + self.expr.compile(comp); + comp.push(OpCode::LeaveEnv); + } +} + +impl Compile for ir::Assert { + fn compile(self, comp: &mut Compiler) { + self.assertion.compile(comp); + comp.push(OpCode::Assert); + self.expr.compile(comp); + } +} + +impl Compile for ir::Func { + fn compile(self, comp: &mut Compiler) { + todo!() + } +} + +impl Compile for ir::Call { + fn compile(self, comp: &mut Compiler) { + todo!() + } +} + +impl Compile for ir::Path { + fn compile(self, comp: &mut Compiler) { + todo!() + } +} diff --git a/src/compile/env.rs b/src/compile/env.rs new file mode 100644 index 0000000..5cef2bf --- /dev/null +++ b/src/compile/env.rs @@ -0,0 +1,19 @@ +use std::collections::HashMap; + +use crate::bytecode::SymIdx; + +use super::ir::Ir; + +pub struct IrEnv { + pub stcs: HashMap, + pub dyns: Vec<(Ir, Ir)>, +} + +impl IrEnv { + pub fn new() -> IrEnv { + IrEnv { + stcs: HashMap::new(), + dyns: Vec::new(), + } + } +} diff --git a/src/compile/ir.rs b/src/compile/ir.rs new file mode 100644 index 0000000..de13c62 --- /dev/null +++ b/src/compile/ir.rs @@ -0,0 +1,772 @@ +// TODO: Error Handling + +use std::collections::HashMap; + +use anyhow::{anyhow, Result}; +use rnix::ast::{self, Expr}; +use ecow::EcoString; + +use crate::bytecode::{Const as ByteCodeConst, ConstIdx, Consts, ThunkIdx}; +use crate::slice::Slice; + +use super::compile::*; +use super::env::IrEnv; +use super::symtable::*; + +pub fn downgrade(expr: Expr) -> Result { + let mut state = DowngradeState::new(); + let ir = expr.downgrade(&mut state)?; + Ok(Downgraded { + top_level: ir, + consts: state.consts.into(), + thunks: state.thunks.into(), + }) +} + +#[macro_export] +macro_rules! ir { + ( + $( + $(#[$($x:tt)*])* + $ty:ident + => + {$($name:ident : $elemtype:ty),*$(,)?} + ) + ,*$(,)? + ) => { + use crate::downcast::Downcast; + + #[derive(Clone, Debug)] + pub enum Ir { + $( + $ty($ty), + )* + } + + impl Ir { + fn boxed(self) -> Box { + Box::new(self) + } + fn ok(self) -> Result { + Ok(self) + } + } + + impl Compile for Ir { + fn compile(self, state: &mut Compiler) { + match self { + $(Ir::$ty(ir) => ir.compile(state),)* + } + } + } + + $( + $( + #[$($x)*] + )* + #[derive(Clone, Debug)] + pub struct $ty { + $( + pub $name : $elemtype, + )* + } + + impl $ty { + pub fn ir(self) -> Ir { + Ir::$ty(self) + } + } + + impl TryFrom for $ty { + type Error = anyhow::Error; + fn try_from(value: Ir) -> Result { + match value { + Ir::$ty(value) => Ok(value), + _ => Err(anyhow!("")), + } + } + } + + impl Downcast<$ty> for Ir { + fn downcast_ref(&self) -> Option<&$ty> { + match self { + Ir::$ty(value) => Some(value), + _ => None, + } + } + fn downcast_mut(&mut self) -> Option<&mut $ty> { + match self { + Ir::$ty(value) => Some(value), + _ => None, + } + } + fn downcast(self) -> core::result::Result<$ty, Self> { + match self { + Ir::$ty(value) => Ok(value), + _ => Err(self), + } + } + } + )* + } +} + +ir! { + Attrs => { stcs: HashMap, dyns: Vec }, + RecAttrs => { stcs: HashMap, dyns: Vec }, + List => { items: Vec }, + HasAttr => { lhs: Box, rhs: Vec }, + BinOp => { lhs: Box, rhs: Box, kind: BinOpKind }, + UnOp => { rhs: Box, kind: UnOpKind }, + Select => { expr: Box, attrpath: Vec, default: Option> }, + If => { cond: Box, consq: Box, alter: Box }, + Func => { args: Vec, body: Box }, + Call => { func: Box, args: Vec }, + + Let => { attrs: Attrs, expr: Box }, + With => { namespace: Box, expr: Box }, + Assert => { assertion: Box, expr: Box }, + ConcatStrings => { parts: Vec }, + Const => { value: ByteCodeConst }, + Var => { sym: EcoString }, + #[derive(Copy)] + Thunk => { idx: ThunkIdx }, + Path => { expr: Box }, +} + +#[derive(Clone, Debug)] +pub struct DynamicAttrPair(pub Ir, pub Ir); + +enum Env { + Env(IrEnv), + With, +} + +impl Env { + fn env(&self) -> &IrEnv { + match self { + Env::Env(env) => env, + _ => panic!(), + } + } + + fn env_mut(&mut self) -> &mut IrEnv { + match self { + Env::Env(env) => env, + _ => panic!(), + } + } +} + +#[derive(Debug)] +pub struct DowngradeError { + errno: u16, + text: String, +} + +pub struct DowngradeState { + sym_table: SymTable, + envs: Vec, + thunks: Vec, + consts: Vec, + consts_table: HashMap, +} + +pub struct Downgraded { + pub top_level: Ir, + pub consts: Consts, + pub thunks: Slice, +} + +impl DowngradeState { + fn new() -> DowngradeState { + DowngradeState { + sym_table: SymTable::new(), + envs: Vec::new(), + thunks: Vec::new(), + consts: Vec::new(), + consts_table: HashMap::new(), + } + } + + fn new_thunk(&mut self, thunk: Ir) -> Thunk { + let idx = self.thunks.len(); + self.thunks.push(thunk); + Thunk { idx } + } + + fn lookup_thunk(&self, idx: ThunkIdx) -> &Ir { + self.thunks.get(idx).unwrap() + } +} + +impl Attrs { + fn _insert(&mut self, mut path: std::vec::IntoIter, name: Attr, value: Ir) -> Result<()> { + if let Some(attr) = path.next() { + match attr { + Attr::Str(ident) => { + if self.stcs.get(&ident).is_some() { + self.stcs + .get_mut(&ident) + .unwrap() + .downcast_mut() + .ok_or(anyhow!(r#""{ident}" already exsists in this set"#)) + .and_then(|attrs: &mut Attrs| attrs._insert(path, name, value)) + } else { + let mut attrs = Attrs { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + attrs._insert(path, name, value)?; + assert!(self.stcs.insert(ident, attrs.ir()).is_none()); + Ok(()) + } + } + Attr::Strs(string) => { + let mut attrs = Attrs { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + attrs._insert(path, name, value)?; + self.dyns.push(DynamicAttrPair(string.ir(), attrs.ir())); + Ok(()) + } + Attr::Dynamic(dynamic) => { + let mut attrs = Attrs { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + attrs._insert(path, name, value)?; + self.dyns.push(DynamicAttrPair(dynamic, attrs.ir())); + Ok(()) + } + } + } else { + match name { + Attr::Str(ident) => { + if self.stcs.get(&ident).is_some() { + return Err(anyhow!(r#""{ident}" already exsists in this set"#)); + } + self.stcs.insert(ident, value); + } + Attr::Strs(string) => { + self.dyns.push(DynamicAttrPair(string.ir(), value)); + } + Attr::Dynamic(dynamic) => { + self.dyns.push(DynamicAttrPair(dynamic, value)); + } + } + Ok(()) + } + } + + pub fn insert(&mut self, path: Vec, value: Ir) -> Result<()> { + let mut path = path.into_iter(); + let name = path.next_back().unwrap(); + self._insert(path, name, value) + } + + fn _has_attr(&self, mut path: std::slice::Iter, name: Attr) -> Option { + match path.next() { + Some(Attr::Str(ident)) => self + .stcs + .get(ident.as_str()) + .and_then(|attrs| attrs.downcast_ref()) + .map_or(Some(false), |attrs: &Attrs| attrs._has_attr(path, name)), + None => match name { + Attr::Str(ident) => Some(self.stcs.get(&ident).is_some()), + _ => None, + }, + _ => None, + } + } + + pub fn has_attr(&self, path: &[Attr]) -> Option { + let mut path = path.iter(); + let name = path.next_back().unwrap().clone(); + self._has_attr(path, name) + } +} + +#[derive(Clone, Debug)] +pub enum Attr { + Dynamic(Ir), + Strs(ConcatStrings), + Str(EcoString), +} + +#[derive(Clone, Debug)] +pub enum BinOpKind { + Add, + Sub, + Div, + Mul, + Eq, + Neq, + Lt, + Gt, + Leq, + Geq, + And, + Or, + Impl, + + Con, + Upd, +} + +impl From for BinOpKind { + fn from(op: ast::BinOpKind) -> Self { + use ast::BinOpKind as astkind; + use BinOpKind::*; + match op { + astkind::Concat => Con, + astkind::Update => Upd, + astkind::Add => Add, + astkind::Sub => Sub, + astkind::Mul => Mul, + astkind::Div => Div, + astkind::And => And, + astkind::Equal => Eq, + astkind::Implication => Impl, + astkind::Less => Lt, + astkind::LessOrEq => Leq, + astkind::More => Gt, + astkind::MoreOrEq => Geq, + astkind::NotEqual => Neq, + astkind::Or => Or, + } + } +} + +#[derive(Clone, Debug)] +pub enum UnOpKind { + Neg, + Not, +} + +impl From for UnOpKind { + fn from(value: ast::UnaryOpKind) -> Self { + match value { + ast::UnaryOpKind::Invert => UnOpKind::Not, + ast::UnaryOpKind::Negate => UnOpKind::Neg, + } + } +} + +#[derive(Clone, Debug)] +pub enum Param { + Ident(EcoString), + Formals { + formals: Vec<(EcoString, Option)>, + ellipsis: bool, + alias: Option, + }, +} + +trait Downgrade +where + Self: Sized +{ + fn downgrade(self, state: &mut DowngradeState) -> Result; +} + +impl Downgrade for Expr { + fn downgrade(self, state: &mut DowngradeState) -> Result { + match self { + Expr::Apply(apply) => apply.downgrade(state), + Expr::Assert(assert) => assert.downgrade(state), + Expr::Error(error) => return Err(anyhow!(error.to_string())), + Expr::IfElse(ifelse) => ifelse.downgrade(state), + Expr::Select(select) => select.downgrade(state), + Expr::Str(str) => str.downgrade(state), + Expr::Path(path) => path.downgrade(state), + Expr::Literal(lit) => lit.downgrade(state), + Expr::Lambda(lambda) => lambda.downgrade(state), + Expr::LegacyLet(let_) => let_.downgrade(state), + Expr::LetIn(letin) => letin.downgrade(state), + Expr::List(list) => list.downgrade(state), + Expr::BinOp(op) => op.downgrade(state), + Expr::Paren(paren) => paren.expr().unwrap().downgrade(state), + Expr::Root(root) => root.expr().unwrap().downgrade(state), + Expr::AttrSet(attrs) => attrs.downgrade(state), + Expr::UnaryOp(op) => op.downgrade(state), + Expr::Ident(ident) => ident.downgrade(state), + Expr::With(with) => with.downgrade(state), + Expr::HasAttr(has) => has.downgrade(state), + } + } +} + +impl Downgrade for ast::Assert { + fn downgrade(self, state: &mut DowngradeState) -> Result { + Assert { + assertion: self.condition().unwrap().downgrade(state)?.boxed(), + expr: self.body().unwrap().downgrade(state)?.boxed(), + } + .ir() + .ok() + } +} + +impl Downgrade for ast::IfElse { + fn downgrade(self, state: &mut DowngradeState) -> Result { + If { + cond: self.condition().unwrap().downgrade(state)?.boxed(), + consq: self.body().unwrap().downgrade(state)?.boxed(), + alter: self.else_body().unwrap().downgrade(state)?.boxed(), + } + .ir() + .ok() + } +} + +impl Downgrade for ast::Path { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let parts = self + .parts() + .into_iter() + .map(|part| match part { + ast::InterpolPart::Literal(lit) => Const { value: lit.to_string().into() } + .ir() + .ok(), + ast::InterpolPart::Interpolation(interpol) => { + interpol.expr().unwrap().downgrade(state) + } + }) + .collect::>>()?; + if parts.len() == 1 { + Path { + expr: parts.into_iter().next().unwrap().boxed(), + } + } else { + Path { + expr: ConcatStrings { parts }.ir().boxed(), + } + } + .ir() + .ok() + } +} + +impl Downgrade for ast::Str { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let parts = self + .normalized_parts() + .into_iter() + .map(|part| match part { + ast::InterpolPart::Literal(lit) => { + Const { value: lit.into() }.ir().ok() + } + ast::InterpolPart::Interpolation(interpol) => { + interpol.expr().unwrap().downgrade(state) + } + }) + .collect::>>()?; + if parts.len() == 1 { + Ok(parts.into_iter().next().unwrap()) + } else { + ConcatStrings { parts }.ir().ok() + } + } +} + +impl Downgrade for ast::Literal { + fn downgrade(self, state: &mut DowngradeState) -> Result { + match self.kind() { + ast::LiteralKind::Integer(int) => Const { value: int.value().unwrap().into() }, + ast::LiteralKind::Float(float) => Const { value: float.value().unwrap().into() }, + ast::LiteralKind::Uri(uri) => Const { value: uri.to_string().into() }, + } + .ir() + .ok() + } +} + +impl Downgrade for ast::Ident { + fn downgrade(self, _state: &mut DowngradeState) -> Result { + Var { + sym: self.to_string().into() + } + .ir() + .ok() + } +} + +impl Downgrade for ast::AttrSet { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let rec = self.rec_token().is_some(); + downgrade_has_entry(self, rec, state).map(|attrs| attrs.ir()) + } +} + +impl Downgrade for ast::List { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let mut items = Vec::with_capacity(self.items().size_hint().0); + for item in self.items() { + items.push(item.downgrade(state)?) + } + List { items }.ir().ok() + } +} + +impl Downgrade for ast::BinOp { + fn downgrade(self, state: &mut DowngradeState) -> Result { + BinOp { + lhs: self.lhs().unwrap().downgrade(state)?.boxed(), + rhs: self.rhs().unwrap().downgrade(state)?.boxed(), + kind: self.operator().unwrap().into(), + } + .ir() + .ok() + } +} + +impl Downgrade for ast::HasAttr { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let attrs = self.expr().unwrap().downgrade(state)?; + let path = downgrade_attrpath(self.attrpath().unwrap(), state)?; + if let Some(attrs) = Downcast::::downcast_ref(&attrs) { + if let Some(res) = attrs.has_attr(&path) { + return Const { value: res.into() }.ir().ok(); + } + } + HasAttr { + lhs: attrs.boxed(), + rhs: path, + } + .ir() + .ok() + } +} + +impl Downgrade for ast::UnaryOp { + fn downgrade(self, state: &mut DowngradeState) -> Result { + UnOp { + rhs: self.expr().unwrap().downgrade(state)?.boxed(), + kind: self.operator().unwrap().into(), + } + .ir() + .ok() + } +} + +impl Downgrade for ast::Select { + fn downgrade(self, state: &mut DowngradeState) -> Result { + Select { + expr: self.expr().unwrap().downgrade(state)?.boxed(), + attrpath: downgrade_attrpath(self.attrpath().unwrap(), state)?, + default: match self.default_expr() { + Some(default) => Some(default.downgrade(state)?.boxed()), + None => None, + }, + } + .ir() + .ok() + } +} + +impl Downgrade for ast::LegacyLet { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let attrs = downgrade_has_entry(self, true, state)?; + Select { + expr: attrs.ir().boxed(), + attrpath: vec![Attr::Str("body".to_string().into())], + default: None, + } + .ir() + .ok() + } +} + +impl Downgrade for ast::LetIn { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let body = self.body().unwrap(); + let attrs = downgrade_has_entry(self, true, state)?; + let expr = body.downgrade(state)?.boxed(); + Let { + attrs, + expr + } + .ir() + .ok() + } +} + +impl Downgrade for ast::With { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let namespace = self.namespace().unwrap().downgrade(state)?; + if let Ir::Attrs(attrs) = namespace { + let expr = self.body().unwrap().downgrade(state)?.boxed(); + Let { attrs, expr }.ir().ok() + } else { + let namespace = namespace.boxed(); + let expr = self.body().unwrap().downgrade(state)?.boxed(); + With { namespace, expr }.ir().ok() + } + } +} + +impl Downgrade for ast::Lambda { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let mut body = self.body().unwrap(); + let mut args = vec![downgrade_param(self.param().unwrap(), state)?]; + while let ast::Expr::Lambda(func) = body { + body = func.body().unwrap(); + args.push(downgrade_param(func.param().unwrap(), state)?); + } + let body = body.downgrade(state)?.boxed(); + Func { args, body }.ir().ok() + } +} + +impl Downgrade for ast::Apply { + fn downgrade(self, state: &mut DowngradeState) -> Result { + let mut args = vec![self.argument().unwrap().downgrade(state)?]; + let mut func = self.lambda().unwrap(); + while let ast::Expr::Apply(call) = func { + func = call.lambda().unwrap(); + args.push(call.argument().unwrap().downgrade(state)?); + } + let func = func.downgrade(state)?.boxed(); + args.reverse(); + Call { func, args }.ir().ok() + } +} + +fn downgrade_param(param: ast::Param, state: &mut DowngradeState) -> Result { + match param { + ast::Param::IdentParam(ident) => { + Ok(Param::Ident(ident.to_string().into())) + } + ast::Param::Pattern(pattern) => downgrade_pattern(pattern, state), + } +} + +fn downgrade_pattern(pattern: ast::Pattern, state: &mut DowngradeState) -> Result { + let formals = pattern + .pat_entries() + .map(|entry| { + let ident = entry.ident().unwrap().to_string().into(); + if entry.default().is_none() { + Ok((ident, None)) + } else { + entry + .default() + .unwrap() + .downgrade(state) + .map(|ok| (ident, Some(ok))) + } + }) + .collect::>>()?; + let ellipsis = pattern.ellipsis_token().is_some(); + let alias = pattern + .pat_bind() + .map(|alias| alias.ident().unwrap().to_string().into()); + Ok(Param::Formals { + formals, + ellipsis, + alias, + }) +} + +fn downgrade_has_entry( + has_entry: impl ast::HasEntry, + rec: bool, + state: &mut DowngradeState, +) -> Result { + let entires = has_entry.entries(); + let mut attrs = Attrs { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + + for entry in entires { + match entry { + ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, state)?, + ast::Entry::AttrpathValue(value) => { + downgrade_attrpathvalue(value, &mut attrs, state)? + } + } + } + + Ok(attrs) +} + +fn downgrade_inherit( + inherit: ast::Inherit, + stcs: &mut HashMap, + state: &mut DowngradeState, +) -> Result<()> { + let from = if let Some(from) = inherit.from() { + let from = from.expr().unwrap().downgrade(state)?; + Some(state.new_thunk(from)) + } else { + None + }; + for attr in inherit.attrs() { + let ident: EcoString = match downgrade_attr(attr, state)? { + Attr::Str(ident) => ident.to_string().into(), + _ => return Err(anyhow!("dynamic attributes not allowed in inherit")), + }; + let expr = from.map_or_else( + || Var { sym: ident.clone() }.ir().ok(), + |from| { + Ok(Select { + expr: from.ir().boxed(), + attrpath: vec![Attr::Str(ident.clone())], + default: None, + } + .ir()) + }, + )?; + stcs.insert(ident, expr).unwrap(); + } + Ok(()) +} + +fn downgrade_attr(attr: ast::Attr, state: &mut DowngradeState) -> Result { + match attr { + ast::Attr::Ident(ident) => Ok(Attr::Str(ident.to_string().into())), + ast::Attr::Str(string) => { + let parts = string.normalized_parts(); + if parts.len() == 1 { + let ast::InterpolPart::Literal(ident) = parts.into_iter().next().unwrap() else { + unreachable!() + }; + Ok(Attr::Str(ident.into())) + } else { + let parts = parts + .into_iter() + .map(|part| match part { + ast::InterpolPart::Literal(lit) => { + Const { value: lit.into() }.ir().ok() + } + ast::InterpolPart::Interpolation(interpol) => { + interpol.expr().unwrap().downgrade(state) + } + }) + .collect::>>()?; + Ok(Attr::Strs(ConcatStrings { parts })) + } + } + ast::Attr::Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(state)?)), + } +} + +fn downgrade_attrpath(attrpath: ast::Attrpath, state: &mut DowngradeState) -> Result> { + attrpath + .attrs() + .map(|attr| downgrade_attr(attr, state)) + .collect::>>() +} + +fn downgrade_attrpathvalue( + value: ast::AttrpathValue, + attrs: &mut Attrs, + state: &mut DowngradeState, +) -> Result<()> { + let path = downgrade_attrpath(value.attrpath().unwrap(), state)?; + let value = value.value().unwrap().downgrade(state)?; + attrs.insert(path, value) +} diff --git a/src/compile/mod.rs b/src/compile/mod.rs new file mode 100644 index 0000000..76b8ec7 --- /dev/null +++ b/src/compile/mod.rs @@ -0,0 +1,10 @@ +mod compile; +mod env; +mod ir; +mod symtable; + +pub fn compile(expr: &str) -> anyhow::Result { + let expr = rnix::Root::parse(expr).tree().expr().unwrap(); + let ir = ir::downgrade(expr)?; + Ok(compile::compile(ir)) +} diff --git a/src/compile/symtable.rs b/src/compile/symtable.rs new file mode 100644 index 0000000..ec6b921 --- /dev/null +++ b/src/compile/symtable.rs @@ -0,0 +1,35 @@ +use std::collections::HashMap; + +use crate::bytecode::SymIdx; +use crate::slice::Slice; + +// FIXME: don't store syms twice to make it more memory efficient? + +pub struct SymTable { + syms: Vec, + syms_table: HashMap, +} + +impl SymTable { + pub fn new() -> SymTable { + SymTable { + syms: Vec::new(), + syms_table: HashMap::new(), + } + } + + pub fn lookup(&mut self, name: String) -> SymIdx { + if let Some(sym) = self.syms_table.get(&name) { + *sym + } else { + let sym = self.syms.len(); + self.syms.push(name.clone()); + self.syms_table.insert(name, sym); + sym + } + } + + pub fn syms(self) -> Slice { + self.syms.into() + } +} diff --git a/src/downcast.rs b/src/downcast.rs new file mode 100644 index 0000000..9a87556 --- /dev/null +++ b/src/downcast.rs @@ -0,0 +1,8 @@ +pub trait Downcast +where + Self: Sized, +{ + fn downcast_ref(&self) -> Option<&T>; + fn downcast_mut(&mut self) -> Option<&mut T>; + fn downcast(self) -> Result; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9506c12 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(dead_code)] + +mod builtins; +mod bytecode; +mod compile; +mod downcast; +mod slice; +mod value; +mod vm; diff --git a/src/slice.rs b/src/slice.rs new file mode 100644 index 0000000..acd9037 --- /dev/null +++ b/src/slice.rs @@ -0,0 +1,2 @@ +pub type Slice = Box<[T]>; + diff --git a/src/value/mod.rs b/src/value/mod.rs new file mode 100644 index 0000000..e39d9c7 --- /dev/null +++ b/src/value/mod.rs @@ -0,0 +1,108 @@ +use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use std::ops::Deref; +use std::sync::Arc; + +use derive_more::{Constructor, IsVariant, Unwrap}; +use ecow::EcoString; +use rpds::{HashTrieMapSync, VectorSync}; + +use crate::bytecode::{Args, Const, OpCodes}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Constructor)] +pub struct Symbol(EcoString); + +impl> From for Symbol { + fn from(value: T) -> Self { + Symbol(value.into()) + } +} + +impl Deref for Symbol { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone, Hash)] +pub struct Func { + pub args: Args, + pub opcodes: OpCodes, +} + +impl PartialEq for Func { + fn eq(&self, _: &Self) -> bool { + false + } +} + +/* #[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)] +pub enum Const { + Int(i64), + Float(f64), + Bool(bool), + String(EcoString), + Func(Arc), +} + +impl Display for Const { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "Const::")?; + match self { + Const::Int(int) => write!(f, "Int@{}", int), + Const::Float(float) => write!(f, "Float@{}", float), + Const::Bool(bool) => write!(f, "Bool@{}", bool), + Const::String(string) => write!(f, r#"String@"{}""#, string.as_ref()), + Const::Func(func) => write!(f, "Func@{:?}", func.as_ref() as *const Func), + } + } +} + +impl From for Const { + fn from(value: ByteCodeConst) -> Self { + use ByteCodeConst::*; + match value { + Int(int) => Const::Int(int), + Float(float) => Const::Float(float), + Bool(bool) => Const::Bool(bool), + String(string) => Const::String(EcoString::from(string)), + Func(func) => Const::Func(Arc::new(func)), + } + } +} */ + +#[derive(Constructor, Clone, PartialEq)] +pub struct AttrSet { + data: HashTrieMapSync, +} + +impl Debug for AttrSet { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{{ ")?; + for (k, v) in self.data.iter() { + write!(f, "{k:?} = {v:?}; ")?; + } + write!(f, "}}") + } +} + +#[derive(Constructor, Clone, Debug, PartialEq)] +pub struct List { + data: VectorSync, +} + +#[derive(Clone, Debug, PartialEq, Constructor)] +pub struct Catchable { + msg: Option +} + +#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)] +pub enum Value { + Const(Const), + AttrSet(AttrSet), + List(List), + Catchable(Catchable), + Thunk, + PrimOp, + PartialPrimOp, +} diff --git a/src/vm/env.rs b/src/vm/env.rs new file mode 100644 index 0000000..787f195 --- /dev/null +++ b/src/vm/env.rs @@ -0,0 +1,47 @@ +use rpds::HashTrieMapSync; + +use super::value::{Symbol, VmValue}; + +pub struct Env { + last: Option<*mut Env>, + map: HashTrieMapSync, +} + +impl Env { + pub fn empty() -> Env { + Env { + last: None, + map: HashTrieMapSync::new_sync(), + } + } + + pub fn lookup(&self, symbol: Symbol) -> VmValue { + if let Some(value) = self.map.get(&symbol) { + value.clone() + } else { + let last = unsafe { &*self.last.unwrap() }; + last.lookup(symbol) + } + } + + pub fn insert(&mut self, symbol: Symbol, value: VmValue) { + self.map.insert_mut(symbol, value); + } + + pub fn enter(&mut self, map: HashTrieMapSync) { + let last = std::mem::replace( + self, + Env { + last: None, + map, + }, + ); + self.last = Some(Box::leak(Box::new(last)) as *mut Env); + } + + pub fn leave(&mut self) { + let last = unsafe { &*self.last.unwrap() }; + self.last = last.last; + self.map = last.map.clone(); + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs new file mode 100644 index 0000000..070abe0 --- /dev/null +++ b/src/vm/mod.rs @@ -0,0 +1,12 @@ +mod env; +mod stack; +mod value; +mod vm; +mod vmthunk; + +#[cfg(test)] +mod test; + +pub use env::Env; +pub use value::Symbol; +pub use value::VmValue as Value; diff --git a/src/vm/stack.rs b/src/vm/stack.rs new file mode 100644 index 0000000..3b9dbd2 --- /dev/null +++ b/src/vm/stack.rs @@ -0,0 +1,93 @@ +use std::mem::{size_of, transmute, MaybeUninit}; +use std::ops::Deref; + +use anyhow::{anyhow, Result}; + +use super::value::VmValue; + +pub const STACK_SIZE: usize = 8 * 1024 / size_of::(); + +pub struct Stack { + items: Box<[MaybeUninit; CAP]>, + top: usize, +} + +impl Stack { + pub fn new() -> Self { + Stack { + items: (0..CAP) + .map(|_| MaybeUninit::uninit()) + .collect::>() + .try_into() + .unwrap(), + top: 0, + } + } + + pub fn push(&mut self, item: VmValue) -> Result<()> { + self.items + .get_mut(self.top) + .map_or(Err(anyhow!("stack overflow")), |ok| Ok(ok))? + .write(item); + self.top += 1; + Ok(()) + } + + pub fn pop(&mut self) -> Result { + self.top -= 1; + let item = self + .items + .get_mut(self.top) + .map_or(Err(anyhow!("stack empty")), |ok| Ok(ok))?; + unsafe { Ok(std::mem::replace(item, MaybeUninit::uninit()).assume_init()) } + } + + pub fn tos(&self) -> Result<&VmValue> { + if self.top == 0 { + Err(anyhow!("")) + } else { + unsafe { Ok(transmute(self.items.get(self.top - 1).unwrap())) } + } + } + + pub fn tos_mut(&mut self) -> Result<&mut VmValue> { + if self.top == 0 { + Err(anyhow!("")) + } else { + unsafe { Ok(transmute(self.items.get_mut(self.top - 1).unwrap())) } + } + } + + pub fn with_tos(&self, func: impl Fn(&VmValue)) -> Result<()> { + if self.top != 0 { + Err(anyhow!("")) + } else { + unsafe { func(transmute(self.items.get(self.top - 1).unwrap())) } + Ok(()) + } + } + pub fn with_tos_mut(&mut self, func: impl Fn(&mut VmValue)) -> Result<()> { + if self.top != 0 { + Err(anyhow!("")) + } else { + unsafe { func(transmute(self.items.get_mut(self.top - 1).unwrap())) } + Ok(()) + } + } +} + +impl Deref for Stack { + type Target = [VmValue]; + fn deref(&self) -> &Self::Target { + unsafe { transmute(&self.items[0..self.top]) } + } +} + +impl Drop for Stack { + fn drop(&mut self) { + self.items.as_mut_slice()[0..self.top] + .iter_mut() + .map(|item| unsafe { item.assume_init_drop() }) + .for_each(drop) + } +} diff --git a/src/vm/test.rs b/src/vm/test.rs new file mode 100644 index 0000000..8908822 --- /dev/null +++ b/src/vm/test.rs @@ -0,0 +1,169 @@ +use ecow::EcoString; +use rpds::{ht_map_sync, vector_sync}; + +use crate::compile::compile; +use crate::value::*; +use crate::bytecode::Const; + +use super::vm::run; + +#[inline] +fn test_expr(expr: &str, expected: Value) { + let prog = compile(expr).unwrap(); + dbg!(&prog); + assert_eq!(run(prog).unwrap(), expected); +} + +macro_rules! int { + ($e:expr) => { + Value::Const(Const::Int($e)) + }; +} + +macro_rules! float { + ($e:expr) => { + Value::Const(Const::Float($e as f64)) + }; +} + +macro_rules! boolean { + ($e:expr) => { + Value::Const(Const::Bool($e)) + }; +} + +macro_rules! string { + ($e:expr) => { + Value::Const(Const::String(EcoString::from($e))) + }; +} + +macro_rules! symbol { + ($e:expr) => { + Symbol::from($e.to_string()) + }; +} + +macro_rules! list { + ($($x:tt)*) => ( + Value::List(List::new(vector_sync![$($x)*])) + ); +} + +macro_rules! attrs { + ($($x:tt)*) => ( + Value::AttrSet(AttrSet::new(ht_map_sync!{$($x)*})) + ) +} + +#[test] +fn test_arith() { + test_expr("1", int!(1)); + test_expr("1.", float!(1)); + test_expr("-1", int!(-1)); + test_expr("-1.", float!(-1)); + test_expr("1 + 1", int!(2)); + test_expr("1 + 1.", float!(2)); + test_expr("1. + 1", float!(2)); + test_expr("1. + 1.", float!(2)); + test_expr("1 - 1", int!(0)); + test_expr("1 - 1.", float!(0)); + test_expr("1. - 1", float!(0)); + test_expr("1. - 1.", float!(0)); + test_expr("1 * 1", int!(1)); + test_expr("1 * 1.", float!(1)); + test_expr("1. * 1", float!(1)); + test_expr("1. * 1.", float!(1)); + test_expr("1 / 1", int!(1)); + test_expr("1 / 1.", float!(1)); + test_expr("1. / 1", float!(1)); + test_expr("1. / 1.", float!(1)); +} + +#[test] +fn test_cmp() { + test_expr("1 < 2", boolean!(true)); + test_expr("1 < 1", boolean!(false)); + test_expr("1 > 0", boolean!(true)); + test_expr("1 > 1", boolean!(false)); + test_expr("1 <= 1", boolean!(true)); + test_expr("1 <= 0", boolean!(false)); + test_expr("1 >= 1", boolean!(true)); + test_expr("1 >= 2", boolean!(false)); +} + +#[test] +fn test_string() { + test_expr(r#""test""#, string!("test")); + test_expr(r#""hello" + " world""#, string!("hello world")); +} + +#[test] +fn test_bool() { + test_expr("true", boolean!(true)); + test_expr("false", boolean!(false)); + test_expr("!false", boolean!(true)); + test_expr("true && false", boolean!(false)); + test_expr("true || false", boolean!(true)); + test_expr("true -> false", boolean!(false)); +} + +#[test] +fn test_list() { + test_expr( + "[ 1 2 3 true ]", + list![int!(1), int!(2), int!(3), boolean!(true)], + ); + test_expr( + "[ 1 2 ] ++ [ 3 4 ]", + list![int!(1), int!(2), int!(3), int!(4)], + ); +} + +#[test] +fn test_attrs() { + test_expr( + "{ a = 1; }", + attrs! { + symbol!("a") => int!(1) + }, + ); + test_expr("{ a = 1; }.a", int!(1)); + test_expr("{ a = 1; }.b or 1", int!(1)); + test_expr( + "{ a = { a = 1; }; }.a", + attrs! { + symbol!("a") => int!(1) + }, + ); + test_expr("{ a.b = 1; }.a.b", int!(1)); + test_expr( + "{ a.b = 1; a.c = 2; }", + attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("c") => int!(2) } }, + ); + test_expr("{ a.b = 1; } ? a.b", boolean!(true)); + test_expr( + "{ a.b = 1; } // { a.c = 2 }", + attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("c") => int!(2) } }, + ); +} + +#[test] +fn test_if() { + test_expr("if true || false then 1 else 2", int!(1)); +} + +#[test] +fn test_with() { + test_expr(r#"with { a = 1; }; a"#, int!(1)); +} + +#[test] +fn test_let() { + test_expr(r#"let a = 1; in a"#, int!(1)); + test_expr(r#"let a = { a = 1; }; b = "a"; in a.${b}"#, int!(1)); + test_expr( + r#"let b = "c"; in { a.b = 1; } // { a."a${b}" = 2 }"}"#, + attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("ac") => int!(2) } }, + ); +} diff --git a/src/vm/value/attrset.rs b/src/vm/value/attrset.rs new file mode 100644 index 0000000..c2b7002 --- /dev/null +++ b/src/vm/value/attrset.rs @@ -0,0 +1,53 @@ +use derive_more::Constructor; +use rpds::HashTrieMapSync; + +use crate::value::{self, Value}; + +use super::super::vm::VM; +use super::{Symbol, ToValue, VmValue}; + +#[derive(Debug, Constructor, Clone, PartialEq)] +pub struct AttrSet { + data: HashTrieMapSync, +} + +impl AttrSet { + pub fn push_attr(&mut self, sym: Symbol, val: VmValue) { + self.data.insert_mut(sym, val); + } + + pub fn select(&self, sym: Symbol) -> Option { + self.data.get(&sym).cloned() + } + + pub fn has_attr(&self, sym: Symbol) -> bool { + self.data.get(&sym).is_some() + } + + pub fn update(mut self, other: AttrSet) -> AttrSet { + for (k, v) in other.data.iter() { + if let Some(attr) = self.data.get(k) { + let new_attr = attr.clone().update(v.clone()); + self.data.insert_mut(k.clone(), new_attr); + } else { + self.push_attr(k.clone(), v.clone()) + } + } + self + } + + pub fn to_data(self) -> HashTrieMapSync { + self.data + } +} + +impl ToValue for AttrSet { + fn to_value(self, vm: &VM) -> Value { + Value::AttrSet(value::AttrSet::new( + self.data + .iter() + .map(|(sym, value)| (value::Symbol::new(sym.0.clone()), value.clone().to_value(vm))) + .collect(), + )) + } +} diff --git a/src/vm/value/list.rs b/src/vm/value/list.rs new file mode 100644 index 0000000..5ba9cae --- /dev/null +++ b/src/vm/value/list.rs @@ -0,0 +1,36 @@ +use derive_more::Constructor; +use rpds::VectorSync; + +use crate::value::{self, Value}; + +use super::super::vm::VM; +use super::{ToValue, VmValue}; + +#[derive(Debug, Constructor, Clone, PartialEq)] +pub struct List { + data: VectorSync, +} + +impl List { + pub fn push(&mut self, elem: VmValue) { + self.data.push_back_mut(elem); + } + + pub fn concat(mut self, other: List) -> List { + for elem in other.data.iter() { + self.data.push_back_mut(elem.clone()); + } + self + } +} + +impl ToValue for List { + fn to_value(self, vm: &VM) -> Value { + Value::List(value::List::new( + self.data + .iter() + .map(|value| value.clone().to_value(vm)) + .collect(), + )) + } +} diff --git a/src/vm/value/mod.rs b/src/vm/value/mod.rs new file mode 100644 index 0000000..e6d139d --- /dev/null +++ b/src/vm/value/mod.rs @@ -0,0 +1,251 @@ +use derive_more::{Constructor, IsVariant, Unwrap}; +use anyhow::Result; +use ecow::EcoString; + +use crate::value::*; +use crate::bytecode::Const; + +use super::vm::VM; +use super::env::Env; + +mod attrset; +mod list; +mod string; + +pub use attrset::AttrSet; +pub use list::List; +pub use string::ContextfulString; + +pub trait ToValue { + fn to_value(self, vm: &VM) -> Value; +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Constructor)] +pub struct Symbol(EcoString); + +impl> From for Symbol { + fn from(value: T) -> Self { + Symbol(value.into()) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Constructor)] +pub struct Thunk(usize); + +#[derive(Debug, IsVariant, Unwrap, Clone, PartialEq)] +pub enum VmValue { + Const(Const), + Thunk(Thunk), + AttrSet(AttrSet), + List(List), + Catchable(crate::value::Catchable), + PrimOp(crate::builtins::PrimOp), + PartialPrimOp(crate::builtins::PartialPrimOp) +} + +use VmValue::Const as VmConst; +impl VmValue { + pub fn call(self, args: Vec) -> VmValue { + match self { + VmValue::PrimOp(func) => func.call(args), + VmValue::PartialPrimOp(func) => func.call(args), + _ => todo!() + } + } + + pub fn not(self) -> VmValue { + use Const::*; + match self { + VmConst(Bool(bool)) => VmConst(Bool(!bool)), + _ => todo!(), + } + } + + pub fn and(self, other: VmValue) -> VmValue { + use Const::*; + match (self, other) { + (VmConst(Bool(a)), VmConst(Bool(b))) => VmConst(Bool(a && b)), + _ => todo!(), + } + } + + pub fn or(self, other: VmValue) -> VmValue { + use Const::*; + match (self, other) { + (VmConst(Bool(a)), VmConst(Bool(b))) => VmConst(Bool(a || b)), + _ => todo!(), + } + } + + pub fn eq(self, other: VmValue) -> VmValue { + use Const::Bool; + VmConst(Bool(self == other)) + } + + pub fn lt(self, other: VmValue) -> VmValue { + use Const::*; + VmConst(Bool(match (self, other) { + (VmConst(Int(a)), VmConst(Int(b))) => a < b, + (VmConst(Int(a)), VmConst(Float(b))) => (a as f64) < b, + (VmConst(Float(a)), VmConst(Int(b))) => a < b as f64, + (VmConst(Float(a)), VmConst(Float(b))) => a < b, + (VmConst(String(a)), VmConst(String(b))) => a < b, + _ => todo!() + })) + } + + pub fn neg(self) -> VmValue { + use Const::*; + match self { + VmConst(Int(int)) => VmConst(Int(-int)), + VmConst(Float(float)) => VmConst(Float(-float)), + _ => todo!(), + } + } + + pub fn add(self, other: VmValue) -> VmValue { + use Const::*; + match (self, other) { + (VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a + b)), + (VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 + b)), + (VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a + b as f64)), + (VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a + b)), + (VmConst(String(a)), VmConst(String(b))) => { + let mut string = a.clone(); + string.push_str(b.as_str()); + VmConst(String(string)) + } + _ => todo!(), + } + } + + pub fn mul(self, other: VmValue) -> VmValue { + use Const::*; + match (self, other) { + (VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a * b)), + (VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 * b)), + (VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a * b as f64)), + (VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a * b)), + _ => todo!(), + } + } + + pub fn div(self, other: VmValue) -> VmValue { + use Const::*; + match (self, other) { + (_, VmConst(Int(0))) => todo!(), + (_, VmConst(Float(0.))) => todo!(), + (VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a / b)), + (VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 / b)), + (VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a / b as f64)), + (VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a / b)), + _ => todo!(), + } + } + + pub fn concat_string(&mut self, mut other: VmValue) -> &mut Self { + if let (VmConst(Const::String(a)), VmConst(Const::String(b))) = (self.coerce_to_string(), other.coerce_to_string()) { + a.push_str(b.as_str()); + } else { + todo!() + } + self + } + + pub fn push(&mut self, elem: VmValue) -> &mut Self { + if let VmValue::List(list) = self { + list.push(elem); + } else { + todo!() + } + self + } + + pub fn concat(self, other: VmValue) -> VmValue { + if let (VmValue::List(a), VmValue::List(b)) = (self, other) { + VmValue::List(a.concat(b)) + } else { + todo!() + } + } + + pub fn push_attr(&mut self, sym: Symbol, val: VmValue) -> &mut Self { + if let VmValue::AttrSet(attrs) = self { + attrs.push_attr(sym, val) + } else { + todo!() + } + self + } + + pub fn update(self, other: VmValue) -> VmValue { + if let (VmValue::AttrSet(a), VmValue::AttrSet(b)) = (self, other) { + VmValue::AttrSet(a.update(b)) + } else { + todo!() + } + } + + pub fn select(&mut self, sym: Symbol) -> &mut Self { + if let VmValue::AttrSet(attrs) = self { + let val = attrs + .select(sym.clone()) + .unwrap_or(VmValue::Catchable(Catchable::new(Some(format!("{sym:?} not found"))))); + *self = val; + } else { + todo!() + } + self + } + + pub fn select_with_default(&mut self, sym: Symbol, default: VmValue) -> &mut Self { + if let VmValue::AttrSet(attrs) = self { + let val = attrs.select(sym).unwrap_or(default); + *self = val; + } else { + todo!() + } + self + } + + pub fn has_attr(&mut self, sym: Symbol) -> &mut Self { + if let VmValue::AttrSet(attrs) = self { + let val = VmConst(Const::Bool(attrs.has_attr(sym))); + *self = val; + } else { + *self = VmConst(Const::Bool(false)); + } + self + } + + pub fn coerce_to_string(&mut self) -> &mut Self { + if let VmConst(Const::String(_)) = self { + () + } else { + todo!() + } + self + } + + pub fn force(&mut self, vm: &VM, env: &mut Env) -> Result<&mut Self> { + if let VmValue::Thunk(thunk) = self { + let value = vm.get_thunk_value(thunk.0, env)?; + *self = value + } + Ok(self) + } +} + +impl ToValue for VmValue { + fn to_value(self, vm: &VM) -> Value { + match self { + VmValue::AttrSet(attrs) => attrs.to_value(vm), + VmValue::List(list) => list.to_value(vm), + VmValue::Catchable(catchable) => Value::Catchable(catchable), + VmValue::Const(cnst) => Value::Const(cnst), + VmValue::Thunk(_) => Value::Thunk, + VmValue::PrimOp(_) => Value::PrimOp, + VmValue::PartialPrimOp(_) => Value::PartialPrimOp, + } + } +} diff --git a/src/vm/value/string.rs b/src/vm/value/string.rs new file mode 100644 index 0000000..c2e829a --- /dev/null +++ b/src/vm/value/string.rs @@ -0,0 +1,30 @@ +// TODO: Contextful String + +use ecow::EcoString; +use rpds::List; + +pub struct StringContext { + context: List<()>, +} + +impl StringContext { + pub fn new() -> StringContext { + StringContext { + context: List::new(), + } + } +} + +pub struct ContextfulString { + string: EcoString, + context: StringContext, +} + +impl ContextfulString { + pub fn new(string: EcoString) -> ContextfulString { + ContextfulString { + string, + context: StringContext::new(), + } + } +} diff --git a/src/vm/vm.rs b/src/vm/vm.rs new file mode 100644 index 0000000..5f8bdc9 --- /dev/null +++ b/src/vm/vm.rs @@ -0,0 +1,188 @@ +use anyhow::{anyhow, Result}; +use rpds::{HashTrieMap, HashTrieMapSync, Vector}; + +use crate::bytecode::{self, *}; +use crate::slice::*; +use crate::value::{self, Value}; +use crate::builtins::env; + +use super::env::Env; +use super::stack::{Stack, STACK_SIZE}; +use super::value::{self as vmValue, *}; +use super::vmthunk::*; + +pub fn run(prog: Program) -> Result { + let vm = VM::new(prog.thunks); + Ok(vm.eval(prog.top_level, &mut env())?.to_value(&vm)) +} + +pub struct VM { + thunks: Slice, +} + +impl VM { + fn new(thunks: Thunks) -> Self { + let thunks = thunks + .into_iter() + .map(|bytecode::Thunk { opcodes }| VmThunk::new(opcodes)) + .collect(); + VM { + thunks, + } + } + + pub fn get_thunk_value(&self, idx: usize, env: &mut Env) -> Result { + self.thunks.get(idx).unwrap().force(self, env) + } + + pub fn eval(&self, opcodes: OpCodes, env: &mut Env) -> Result { + let mut stack = Stack::::new(); + let mut iter = opcodes.into_iter(); + while let Some(opcode) = iter.next() { + let jmp = self.single_op(opcode, &mut stack, env)?; + for _ in 0..jmp { + iter.next().unwrap(); + } + } + assert_eq!(stack.len(), 1); + stack.pop() + } + + #[inline] + fn single_op( + &self, + opcode: OpCode, + stack: &mut Stack, + env: &mut Env, + ) -> Result { + match opcode { + OpCode::NoOp => (), + OpCode::Const { value } => stack.push(VmValue::Const(value))?, + OpCode::LoadThunk { idx } => stack.push(VmValue::Thunk(vmValue::Thunk::new(idx)))?, + OpCode::LoadValue { idx } => { + stack.push(self.get_thunk_value(idx, env)?)?; + } + OpCode::ForceValue => { + stack.tos_mut()?.force(self, env)?; + } + OpCode::Jmp { step } => return Ok(step), + OpCode::JmpIfTrue { step } => { + if let VmValue::Const(Const::Bool(true)) = stack.pop()? { + return Ok(step); + } + } + OpCode::JmpIfFalse { step } => { + if let VmValue::Const(Const::Bool(false)) = stack.pop()? { + return Ok(step); + } + } + OpCode::Call { arity } => { + let mut args = Vec::with_capacity(arity); + for _ in 0..arity { + args.insert(0, stack.pop()?); + } + let func = stack.pop()?; + stack.push(func.call(args))?; + } + OpCode::UnOp { op } => { + use UnOp::*; + let value = stack.pop()?; + stack.push(match op { + Not => value.not(), + })?; + } + OpCode::BinOp { op } => { + use BinOp::*; + let rhs = stack.pop()?; + let lhs = stack.pop()?; + stack.push(match op { + Add => lhs.add(rhs), + And => lhs.and(rhs), + Or => lhs.or(rhs), + Eq => lhs.eq(rhs), + Con => lhs.concat(rhs), + Upd => lhs.update(rhs), + })?; + } + OpCode::ConcatString => { + let rhs = stack.pop()?; + stack.tos_mut()?.concat_string(rhs); + } + OpCode::List => { + stack.push(VmValue::List(List::new(Vector::new_sync())))?; + } + OpCode::PushElem => { + let elem = stack.pop()?; + stack.tos_mut()?.push(elem); + } + OpCode::AttrSet => { + stack.push(VmValue::AttrSet(AttrSet::new(HashTrieMap::new_sync())))?; + } + OpCode::PushStaticAttr { name } => { + let val = stack.pop()?; + stack.tos_mut()?.push_attr(Symbol::new(name), val); + } + OpCode::PushDynamicAttr => { + let val = stack.pop()?; + let mut sym = stack.pop().unwrap(); + sym.coerce_to_string(); + let sym = sym.unwrap_const().unwrap_string().into(); + stack.tos_mut()?.push_attr(sym, val); + } + OpCode::Select { sym } => { + stack.tos_mut()?.select(Symbol::new(sym)).force(self, env)?; + } + OpCode::SelectWithDefault { sym } => { + let default = stack.pop()?; + stack + .tos_mut()? + .select_with_default(Symbol::new(sym), default.clone()); + } + OpCode::SelectOrEmpty { sym } => { + stack + .tos_mut()? + .select_with_default(Symbol::new(sym), VmValue::AttrSet(AttrSet::new(HashTrieMapSync::new_sync()))); + } + OpCode::SelectDynamic => { + let mut val = stack.pop().unwrap(); + val.coerce_to_string(); + let sym = val.unwrap_const().unwrap_string().into(); + stack.tos_mut()?.select(sym); + } + OpCode::SelectDynamicWithDefault => { + let mut val = stack.pop().unwrap(); + val.coerce_to_string(); + let sym = val.unwrap_const().unwrap_string().into(); + let default = stack.pop()?; + stack.tos_mut()?.select_with_default(sym, default.clone()); + } + OpCode::SelectDynamicOrEmpty => { + let mut val = stack.pop().unwrap(); + val.coerce_to_string(); + let sym = val.unwrap_const().unwrap_string().into(); + stack.tos_mut()?.select_with_default(sym, VmValue::AttrSet(AttrSet::new(HashTrieMapSync::new_sync()))); + } + OpCode::HasAttr { sym } => { + stack.tos_mut()?.has_attr(Symbol::new(sym)); + } + OpCode::HasDynamicAttr => { + let mut val = stack.pop().unwrap(); + val.coerce_to_string(); + let sym = val.unwrap_const().unwrap_string().into(); + stack.tos_mut()?.has_attr(sym); + } + OpCode::LookUp { sym } => { + stack.push(env.lookup(Symbol::new(sym)))?; + } + OpCode::EnterEnv => { + env.enter(stack.pop()?.unwrap_attr_set().to_data()); + } + OpCode::LeaveEnv => { + env.leave(); + } + _ => todo!(), + } + Ok(0) + } +} + diff --git a/src/vm/vmthunk.rs b/src/vm/vmthunk.rs new file mode 100644 index 0000000..c501529 --- /dev/null +++ b/src/vm/vmthunk.rs @@ -0,0 +1,65 @@ +use std::cell::RefCell; +use std::sync::RwLock; + +use anyhow::{anyhow, Result}; +use derive_more::{IsVariant, Unwrap}; + +use crate::bytecode::OpCodes; + +use super::vm::VM; +use super::env::Env; +use super::value::VmValue; + +pub struct VmThunk { + thunk: RefCell<_VmThunk>, + lock: RwLock<()> +} + +#[derive(IsVariant, Unwrap)] +enum _VmThunk { + Code(OpCodes), + SuspendedFrom(*const VmThunk), + Value(VmValue), +} + +impl VmThunk { + pub fn new(opcodes: OpCodes) -> VmThunk { + VmThunk { + thunk: RefCell::new(_VmThunk::Code(opcodes)), + lock: RwLock::new(()) + } + } + + pub fn force(&self, vm: &VM, env: &mut Env) -> Result { + { + let _guard = self.lock.read().unwrap(); + match &*self.thunk.borrow() { + _VmThunk::Value(value) => return Ok(value.clone()), + _VmThunk::SuspendedFrom(from) => { + return Err(anyhow!( + "already suspended from {from:p} (infinite recursion encountered)" + )) + } + _VmThunk::Code(_) => (), + } + } + { + let _guard = self.lock.write().unwrap(); + let opcodes = std::mem::replace( + &mut *self.thunk.borrow_mut(), + _VmThunk::SuspendedFrom(self as *const VmThunk), + ).unwrap_code(); + let value = vm.eval(opcodes, env).unwrap(); + let _ = std::mem::replace(&mut *self.thunk.borrow_mut(), _VmThunk::Value(value.clone())); + Ok(value) + } + } + + pub fn value(&self) -> Option { + let _guard = self.lock.read(); + match &*self.thunk.borrow() { + _VmThunk::Value(value) => Some(value.clone()), + _ => None, + } + } +}