Add CLI builtins: args, env, http_post, http_get, str ops, json_get, cwd; fix block tail expr and wildcard match codegen; add run-file command
This commit is contained in:
Generated
+304
@@ -233,6 +233,32 @@ version = "0.4.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -296,6 +322,8 @@ dependencies = [
|
|||||||
"el-seal",
|
"el-seal",
|
||||||
"el-test",
|
"el-test",
|
||||||
"el-types",
|
"el-types",
|
||||||
|
"reqwest",
|
||||||
|
"serde_json",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
@@ -408,6 +436,15 @@ dependencies = [
|
|||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs"
|
||||||
|
version = "0.8.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "engram-crypto"
|
name = "engram-crypto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -450,12 +487,33 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -472,6 +530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -480,6 +539,18 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -493,7 +564,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
@@ -558,6 +632,25 @@ dependencies = [
|
|||||||
"polyval",
|
"polyval",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"http",
|
||||||
|
"indexmap",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -628,6 +721,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"h2",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
@@ -654,6 +748,22 @@ dependencies = [
|
|||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
@@ -672,9 +782,11 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"windows-registry",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -889,6 +1001,12 @@ version = "2.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -900,6 +1018,23 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -918,6 +1053,50 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.78"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.114"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -930,6 +1109,12 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polyval"
|
name = "polyval"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -1122,15 +1307,22 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
|
"hyper-tls",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn",
|
"quinn",
|
||||||
@@ -1141,6 +1333,7 @@ dependencies = [
|
|||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -1232,6 +1425,38 @@ version = "1.0.23"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "3.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
@@ -1393,6 +1618,27 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.27.0"
|
||||||
@@ -1498,6 +1744,16 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.4"
|
version = "0.26.4"
|
||||||
@@ -1508,6 +1764,19 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.23"
|
version = "0.8.23"
|
||||||
@@ -1689,6 +1958,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -1852,6 +2127,35 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-registry"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|||||||
@@ -42,3 +42,4 @@ blake3 = "1"
|
|||||||
aes-gcm = "0.10"
|
aes-gcm = "0.10"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
|
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
||||||
|
|||||||
@@ -22,3 +22,5 @@ el-test = { workspace = true }
|
|||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
reqwest = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|||||||
+402
-12
@@ -201,6 +201,15 @@ enum Command {
|
|||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Compile and run a single .el source file (no el.toml required).
|
||||||
|
RunFile {
|
||||||
|
/// Source file (*.el).
|
||||||
|
file: PathBuf,
|
||||||
|
/// Arguments passed to the program (available via the args() builtin).
|
||||||
|
#[arg(trailing_var_arg = true)]
|
||||||
|
args: Vec<String>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Seal an existing release artifact.
|
/// Seal an existing release artifact.
|
||||||
Seal {
|
Seal {
|
||||||
artifact: PathBuf,
|
artifact: PathBuf,
|
||||||
@@ -431,6 +440,21 @@ async fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
// ── Low-level / single-file ───────────────────────────────────────────
|
// ── Low-level / single-file ───────────────────────────────────────────
|
||||||
|
|
||||||
|
Command::RunFile { file, args } => {
|
||||||
|
let source = std::fs::read_to_string(&file)
|
||||||
|
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
|
||||||
|
|
||||||
|
let opts = CompilerOptions {
|
||||||
|
target: Target::Debug,
|
||||||
|
source_path: file.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let compiled = Compiler::compile(&source, opts)?;
|
||||||
|
let instructions = el_compiler::Bytecode::deserialize_all(&compiled.artifact)
|
||||||
|
.unwrap_or_default();
|
||||||
|
run_interpreter_with_args(&instructions, &args);
|
||||||
|
}
|
||||||
|
|
||||||
Command::BuildFile { file, target, output } => {
|
Command::BuildFile { file, target, output } => {
|
||||||
let source = std::fs::read_to_string(&file)
|
let source = std::fs::read_to_string(&file)
|
||||||
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
|
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
|
||||||
@@ -747,13 +771,49 @@ fn run_tests_from_source(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimal interpreter for demonstration.
|
/// Minimal interpreter for demonstration (no program args).
|
||||||
fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
||||||
|
run_interpreter_with_args(instructions, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interpreter with program args — the args() builtin returns these.
|
||||||
|
fn run_interpreter_with_args(instructions: &[el_compiler::Bytecode], program_args: &[String]) {
|
||||||
use el_compiler::{Bytecode, Value};
|
use el_compiler::{Bytecode, Value};
|
||||||
let mut stack: Vec<Value> = Vec::new();
|
let mut stack: Vec<Value> = Vec::new();
|
||||||
let mut locals: std::collections::HashMap<String, Value> = std::collections::HashMap::new();
|
let mut locals: std::collections::HashMap<String, Value> = std::collections::HashMap::new();
|
||||||
let mut ip = 0usize;
|
let mut ip = 0usize;
|
||||||
|
|
||||||
|
// Build a call table: fn name → bytecode offset.
|
||||||
|
// We populate this by scanning for __fn_<name> stores first.
|
||||||
|
let mut fn_table: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
|
||||||
|
// We need a two-pass approach: scan for function entry points then execute.
|
||||||
|
// The codegen emits: Jump(skip) [body...] Push(Int(entry)) StoreLocal(__fn_name)
|
||||||
|
// We pre-scan to build the table.
|
||||||
|
{
|
||||||
|
let mut scan_ip = 0usize;
|
||||||
|
let mut scan_locals: std::collections::HashMap<String, i64> = std::collections::HashMap::new();
|
||||||
|
while scan_ip < instructions.len() {
|
||||||
|
match &instructions[scan_ip] {
|
||||||
|
Bytecode::Push(Value::Int(n)) => {
|
||||||
|
// could be a function entry point — remember it
|
||||||
|
if scan_ip + 1 < instructions.len() {
|
||||||
|
if let Bytecode::StoreLocal(name) = &instructions[scan_ip + 1] {
|
||||||
|
if let Some(fn_name) = name.strip_prefix("__fn_") {
|
||||||
|
fn_table.insert(fn_name.to_string(), *n as usize);
|
||||||
|
}
|
||||||
|
scan_locals.insert(name.clone(), *n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
scan_ip += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call stack for user-defined function calls: (return_ip, saved_locals)
|
||||||
|
let mut call_stack: Vec<(usize, std::collections::HashMap<String, Value>)> = Vec::new();
|
||||||
|
|
||||||
while ip < instructions.len() {
|
while ip < instructions.len() {
|
||||||
match &instructions[ip] {
|
match &instructions[ip] {
|
||||||
Bytecode::Push(v) => stack.push(v.clone()),
|
Bytecode::Push(v) => stack.push(v.clone()),
|
||||||
@@ -798,6 +858,54 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
|||||||
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
stack.push(Value::Bool(a == b));
|
stack.push(Value::Bool(a == b));
|
||||||
}
|
}
|
||||||
|
Bytecode::NotEq => {
|
||||||
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
|
stack.push(Value::Bool(a != b));
|
||||||
|
}
|
||||||
|
Bytecode::Lt => {
|
||||||
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
|
stack.push(match (a, b) {
|
||||||
|
(Value::Int(x), Value::Int(y)) => Value::Bool(x < y),
|
||||||
|
(Value::Float(x), Value::Float(y)) => Value::Bool(x < y),
|
||||||
|
_ => Value::Bool(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Bytecode::Gt => {
|
||||||
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
|
stack.push(match (a, b) {
|
||||||
|
(Value::Int(x), Value::Int(y)) => Value::Bool(x > y),
|
||||||
|
(Value::Float(x), Value::Float(y)) => Value::Bool(x > y),
|
||||||
|
_ => Value::Bool(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Bytecode::LtEq => {
|
||||||
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
|
stack.push(match (a, b) {
|
||||||
|
(Value::Int(x), Value::Int(y)) => Value::Bool(x <= y),
|
||||||
|
(Value::Float(x), Value::Float(y)) => Value::Bool(x <= y),
|
||||||
|
_ => Value::Bool(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Bytecode::GtEq => {
|
||||||
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
|
stack.push(match (a, b) {
|
||||||
|
(Value::Int(x), Value::Int(y)) => Value::Bool(x >= y),
|
||||||
|
(Value::Float(x), Value::Float(y)) => Value::Bool(x >= y),
|
||||||
|
_ => Value::Bool(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Bytecode::And => {
|
||||||
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
|
stack.push(Value::Bool(
|
||||||
|
matches!(a, Value::Bool(true)) && matches!(b, Value::Bool(true))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Bytecode::Or => {
|
||||||
|
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
|
||||||
|
stack.push(Value::Bool(
|
||||||
|
matches!(a, Value::Bool(true)) || matches!(b, Value::Bool(true))
|
||||||
|
));
|
||||||
|
}
|
||||||
Bytecode::Not => {
|
Bytecode::Not => {
|
||||||
let v = stack.pop().unwrap_or(Value::Nil);
|
let v = stack.pop().unwrap_or(Value::Nil);
|
||||||
stack.push(Value::Bool(!matches!(v, Value::Bool(true))));
|
stack.push(Value::Bool(!matches!(v, Value::Bool(true))));
|
||||||
@@ -810,11 +918,49 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
|||||||
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
|
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
|
||||||
stack.push(v);
|
stack.push(v);
|
||||||
}
|
}
|
||||||
Bytecode::Call { name, .. } => {
|
Bytecode::Call { name, arity } => {
|
||||||
if name == "print" || name == "println" {
|
let result = dispatch_builtin(name, *arity, &mut stack, program_args);
|
||||||
let v = stack.pop().unwrap_or(Value::Nil);
|
match result {
|
||||||
println!("{v}");
|
BuiltinResult::Handled => {}
|
||||||
stack.push(Value::Nil);
|
BuiltinResult::Exit(code) => std::process::exit(code),
|
||||||
|
BuiltinResult::NotBuiltin => {
|
||||||
|
// Try user-defined function
|
||||||
|
if let Some(&entry) = fn_table.get(name.as_str()) {
|
||||||
|
// Save current locals and return address
|
||||||
|
let saved = locals.clone();
|
||||||
|
call_stack.push((ip + 1, saved));
|
||||||
|
ip = entry;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Unknown — push Nil
|
||||||
|
stack.push(Value::Nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bytecode::GetField(field) => {
|
||||||
|
// Pop the object; for now just push Nil (structs not fully implemented)
|
||||||
|
stack.pop();
|
||||||
|
stack.push(Value::Str(format!("<field:{field}>")));
|
||||||
|
}
|
||||||
|
Bytecode::GetIndex => {
|
||||||
|
let idx = stack.pop().unwrap_or(Value::Nil);
|
||||||
|
let obj = stack.pop().unwrap_or(Value::Nil);
|
||||||
|
match (obj, idx) {
|
||||||
|
(Value::List(items), Value::Int(i)) => {
|
||||||
|
let v = if i >= 0 && (i as usize) < items.len() {
|
||||||
|
items[i as usize].clone()
|
||||||
|
} else {
|
||||||
|
Value::Nil
|
||||||
|
};
|
||||||
|
stack.push(v);
|
||||||
|
}
|
||||||
|
(Value::Str(s), Value::Int(i)) => {
|
||||||
|
let c = s.chars().nth(i as usize)
|
||||||
|
.map(|c| Value::Str(c.to_string()))
|
||||||
|
.unwrap_or(Value::Nil);
|
||||||
|
stack.push(c);
|
||||||
|
}
|
||||||
|
_ => stack.push(Value::Nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Bytecode::Activate { type_name, query } => {
|
Bytecode::Activate { type_name, query } => {
|
||||||
@@ -842,7 +988,16 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Bytecode::Return => break,
|
Bytecode::Return => {
|
||||||
|
// Return from a user function call
|
||||||
|
if let Some((ret_ip, saved_locals)) = call_stack.pop() {
|
||||||
|
locals = saved_locals;
|
||||||
|
ip = ret_ip;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
Bytecode::Halt => break,
|
Bytecode::Halt => break,
|
||||||
Bytecode::SealedBegin => eprintln!("[sealed section begin]"),
|
Bytecode::SealedBegin => eprintln!("[sealed section begin]"),
|
||||||
Bytecode::SealedEnd => eprintln!("[sealed section end]"),
|
Bytecode::SealedEnd => eprintln!("[sealed section end]"),
|
||||||
@@ -852,12 +1007,247 @@ fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BuiltinResult {
|
||||||
|
Handled,
|
||||||
|
Exit(i32),
|
||||||
|
NotBuiltin,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_builtin(
|
||||||
|
name: &str,
|
||||||
|
_arity: u32,
|
||||||
|
stack: &mut Vec<el_compiler::Value>,
|
||||||
|
program_args: &[String],
|
||||||
|
) -> BuiltinResult {
|
||||||
|
use el_compiler::Value;
|
||||||
|
match name {
|
||||||
|
"print" => {
|
||||||
|
let v = stack.pop().unwrap_or(Value::Nil);
|
||||||
|
print!("{v}");
|
||||||
|
stack.push(Value::Nil);
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"println" => {
|
||||||
|
let v = stack.pop().unwrap_or(Value::Nil);
|
||||||
|
println!("{v}");
|
||||||
|
stack.push(Value::Nil);
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"print_err" => {
|
||||||
|
let v = stack.pop().unwrap_or(Value::Nil);
|
||||||
|
eprintln!("{v}");
|
||||||
|
stack.push(Value::Nil);
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"args" => {
|
||||||
|
let list = program_args.iter()
|
||||||
|
.map(|s| Value::Str(s.clone()))
|
||||||
|
.collect();
|
||||||
|
stack.push(Value::List(list));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"cwd" => {
|
||||||
|
let path = std::env::current_dir()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|_| ".".to_string());
|
||||||
|
stack.push(Value::Str(path));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"env" => {
|
||||||
|
let key = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let val = std::env::var(&key)
|
||||||
|
.map(Value::Str)
|
||||||
|
.unwrap_or(Value::Nil);
|
||||||
|
stack.push(val);
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"http_post" => {
|
||||||
|
let body = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let url = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let result = reqwest::blocking::Client::new()
|
||||||
|
.post(&url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.and_then(|r| r.text())
|
||||||
|
.unwrap_or_else(|e| format!("{{\"error\":\"{e}\"}}"));
|
||||||
|
stack.push(Value::Str(result));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"http_get" => {
|
||||||
|
let url = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let result = reqwest::blocking::get(&url)
|
||||||
|
.and_then(|r| r.text())
|
||||||
|
.unwrap_or_else(|e| format!("{{\"error\":\"{e}\"}}"));
|
||||||
|
stack.push(Value::Str(result));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"exit" => {
|
||||||
|
let code = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Int(n) => n as i32,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
BuiltinResult::Exit(code)
|
||||||
|
}
|
||||||
|
"str_len" => {
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Int(s.len() as i64));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"str_contains" => {
|
||||||
|
let needle = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let haystack = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Bool(haystack.contains(needle.as_str())));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"str_starts_with" => {
|
||||||
|
let prefix = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Bool(s.starts_with(prefix.as_str())));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"str_split" => {
|
||||||
|
let delim = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let s = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let parts: Vec<Value> = s.split(delim.as_str())
|
||||||
|
.map(|p| Value::Str(p.to_string()))
|
||||||
|
.collect();
|
||||||
|
stack.push(Value::List(parts));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"list_len" => {
|
||||||
|
let list = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::List(l) => l,
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
stack.push(Value::Int(list.len() as i64));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"list_get" => {
|
||||||
|
let idx = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Int(n) => n,
|
||||||
|
_ => -1,
|
||||||
|
};
|
||||||
|
let list = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::List(l) => l,
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
let v = if idx >= 0 && (idx as usize) < list.len() {
|
||||||
|
list[idx as usize].clone()
|
||||||
|
} else {
|
||||||
|
Value::Nil
|
||||||
|
};
|
||||||
|
stack.push(v);
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"int_to_str" => {
|
||||||
|
let n = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Int(n) => n,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
stack.push(Value::Str(n.to_string()));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"str_eq" => {
|
||||||
|
let b = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let a = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
stack.push(Value::Bool(a == b));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"json_get" => {
|
||||||
|
let key = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let json_str = match stack.pop().unwrap_or(Value::Nil) {
|
||||||
|
Value::Str(s) => s,
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let val = serde_json::from_str::<serde_json::Value>(&json_str)
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.get(&key).cloned())
|
||||||
|
.map(|v| match v {
|
||||||
|
serde_json::Value::String(s) => Value::Str(s),
|
||||||
|
serde_json::Value::Number(n) => {
|
||||||
|
if let Some(i) = n.as_i64() {
|
||||||
|
Value::Int(i)
|
||||||
|
} else {
|
||||||
|
Value::Str(n.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::Bool(b) => Value::Bool(b),
|
||||||
|
serde_json::Value::Null => Value::Nil,
|
||||||
|
other => Value::Str(other.to_string()),
|
||||||
|
})
|
||||||
|
.unwrap_or(Value::Nil);
|
||||||
|
stack.push(val);
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
"__build_list__" => {
|
||||||
|
// Already handled inline by codegen for array literals — no-op here
|
||||||
|
// The arity items are on the stack; we collect them into a list.
|
||||||
|
// But arity is already popped. We push Nil as fallback.
|
||||||
|
// In practice, array literals push items then call __build_list__(arity).
|
||||||
|
// We need to pop `arity` items and build a list.
|
||||||
|
// arity was passed but we only have `_arity` here — use it.
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for _ in 0.._arity {
|
||||||
|
items.push(stack.pop().unwrap_or(Value::Nil));
|
||||||
|
}
|
||||||
|
items.reverse();
|
||||||
|
stack.push(Value::List(items));
|
||||||
|
BuiltinResult::Handled
|
||||||
|
}
|
||||||
|
_ => BuiltinResult::NotBuiltin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Interpreter with debugger support — emits DebugEvents as it runs.
|
/// Interpreter with debugger support — emits DebugEvents as it runs.
|
||||||
fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut el_compiler::Debugger) {
|
fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut el_compiler::Debugger) {
|
||||||
use el_compiler::{Bytecode, Value};
|
use el_compiler::{Bytecode, Value};
|
||||||
let mut stack: Vec<Value> = Vec::new();
|
let mut stack: Vec<Value> = Vec::new();
|
||||||
let mut locals: std::collections::HashMap<String, Value> = std::collections::HashMap::new();
|
let mut locals: std::collections::HashMap<String, Value> = std::collections::HashMap::new();
|
||||||
let mut ip = 0usize;
|
let mut ip = 0usize;
|
||||||
|
let program_args: Vec<String> = std::env::args().skip(2).collect(); // skip 'el' and 'debug'
|
||||||
|
|
||||||
while ip < instructions.len() {
|
while ip < instructions.len() {
|
||||||
// Check if we should pause here
|
// Check if we should pause here
|
||||||
@@ -899,11 +1289,11 @@ fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut
|
|||||||
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
|
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
|
||||||
stack.push(v);
|
stack.push(v);
|
||||||
}
|
}
|
||||||
Bytecode::Call { name, .. } => {
|
Bytecode::Call { name, arity } => {
|
||||||
if name == "print" || name == "println" {
|
let result = dispatch_builtin(name, *arity, &mut stack, &program_args);
|
||||||
let v = stack.pop().unwrap_or(Value::Nil);
|
match result {
|
||||||
println!("{v}");
|
BuiltinResult::Handled | BuiltinResult::NotBuiltin => {}
|
||||||
stack.push(Value::Nil);
|
BuiltinResult::Exit(code) => std::process::exit(code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Bytecode::Jump(offset) => {
|
Bytecode::Jump(offset) => {
|
||||||
|
|||||||
@@ -65,6 +65,27 @@ impl Codegen {
|
|||||||
|
|
||||||
// ── Statement code generation ─────────────────────────────────────────────
|
// ── Statement code generation ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Generate a statement in tail position (the last stmt of a block).
|
||||||
|
/// Expression statements leave their value on the stack instead of popping it.
|
||||||
|
fn gen_stmt_tail(&mut self, stmt: &Stmt) -> CompileResult<()> {
|
||||||
|
match stmt {
|
||||||
|
Stmt::Expr(expr, _) => {
|
||||||
|
// In tail position, leave the value on the stack.
|
||||||
|
self.gen_expr(expr)?;
|
||||||
|
}
|
||||||
|
Stmt::Return(expr, _) => {
|
||||||
|
self.gen_expr(expr)?;
|
||||||
|
self.emit(Bytecode::Return);
|
||||||
|
}
|
||||||
|
// All other statement kinds behave the same as non-tail; push Nil as block value.
|
||||||
|
other => {
|
||||||
|
self.gen_stmt(other)?;
|
||||||
|
self.emit(Bytecode::Push(Value::Nil));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn gen_stmt(&mut self, stmt: &Stmt) -> CompileResult<()> {
|
fn gen_stmt(&mut self, stmt: &Stmt) -> CompileResult<()> {
|
||||||
// Record the source span for this statement in the source map
|
// Record the source span for this statement in the source map
|
||||||
if self.emit_source_map {
|
if self.emit_source_map {
|
||||||
@@ -187,20 +208,18 @@ impl Codegen {
|
|||||||
self.emit(Bytecode::Call { name: fn_name, arity: args.len() as u32 });
|
self.emit(Bytecode::Call { name: fn_name, arity: args.len() as u32 });
|
||||||
}
|
}
|
||||||
Expr::Block(stmts) => {
|
Expr::Block(stmts) => {
|
||||||
for (i, s) in stmts.iter().enumerate() {
|
|
||||||
self.gen_stmt(s)?;
|
|
||||||
// The last expression statement is the block's value
|
|
||||||
if i == stmts.len() - 1 {
|
|
||||||
if let Stmt::Expr(_, _) = s {
|
|
||||||
// Already on stack from gen_stmt (before the Pop)
|
|
||||||
// We need to not pop it — handled by gen_stmt not
|
|
||||||
// popping Block results; but we already did Pop.
|
|
||||||
// Push nil as fallback for empty/void blocks.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stmts.is_empty() {
|
if stmts.is_empty() {
|
||||||
self.emit(Bytecode::Push(Value::Nil));
|
self.emit(Bytecode::Push(Value::Nil));
|
||||||
|
} else {
|
||||||
|
for (i, s) in stmts.iter().enumerate() {
|
||||||
|
let is_last = i == stmts.len() - 1;
|
||||||
|
if is_last {
|
||||||
|
// Last statement: emit as a tail expression (leave value on stack).
|
||||||
|
self.gen_stmt_tail(s)?;
|
||||||
|
} else {
|
||||||
|
self.gen_stmt(s)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::If { cond, then, else_ } => {
|
Expr::If { cond, then, else_ } => {
|
||||||
@@ -225,48 +244,68 @@ impl Codegen {
|
|||||||
// compare, branch. A full implementation would use a jump table.
|
// compare, branch. A full implementation would use a jump table.
|
||||||
let mut end_jumps = Vec::new();
|
let mut end_jumps = Vec::new();
|
||||||
for arm in arms {
|
for arm in arms {
|
||||||
self.emit(Bytecode::Dup);
|
|
||||||
// Push pattern value
|
|
||||||
match &arm.pattern {
|
match &arm.pattern {
|
||||||
el_parser::Pattern::Literal(lit) => {
|
el_parser::Pattern::Wildcard => {
|
||||||
let v = match lit {
|
// Wildcard always matches — pop subject and run body directly.
|
||||||
Literal::Int(n) => Value::Int(*n),
|
self.emit(Bytecode::Pop);
|
||||||
Literal::Str(s) => Value::Str(s.clone()),
|
self.gen_expr(&arm.body)?;
|
||||||
Literal::Bool(b) => Value::Bool(*b),
|
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
||||||
Literal::Float(f) => Value::Float(*f),
|
// No jump_no_match needed — wildcard always matches.
|
||||||
};
|
// But we still need to patch the end jumps at the end.
|
||||||
self.emit(Bytecode::Push(v));
|
// Nothing else to do; break out of the loop since wildcard
|
||||||
}
|
// is a catch-all and subsequent arms are unreachable.
|
||||||
el_parser::Pattern::EnumVariant { variant, payload, .. } => {
|
break;
|
||||||
// Push the variant name as a string for comparison
|
|
||||||
self.emit(Bytecode::Push(Value::Str(variant.clone())));
|
|
||||||
if let Some(bind) = payload {
|
|
||||||
// Store the payload in a local (simplified: store subject as payload)
|
|
||||||
self.emit(Bytecode::StoreLocal(bind.clone()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
el_parser::Pattern::Binding(name) => {
|
el_parser::Pattern::Binding(name) => {
|
||||||
// Bind and always match — push duplicate and store
|
// Bind and always match.
|
||||||
self.emit(Bytecode::Dup);
|
self.emit(Bytecode::Dup);
|
||||||
self.emit(Bytecode::StoreLocal(name.clone()));
|
self.emit(Bytecode::StoreLocal(name.clone()));
|
||||||
// Fall through — will compare to itself (always true)
|
// Dup'd subject is still on stack; compare to itself.
|
||||||
|
self.emit(Bytecode::Dup);
|
||||||
|
self.emit(Bytecode::Eq);
|
||||||
|
let jump_no_match = self.emit(Bytecode::JumpIfNot(0));
|
||||||
|
self.emit(Bytecode::Pop);
|
||||||
|
self.gen_expr(&arm.body)?;
|
||||||
|
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
||||||
|
let next_arm = self.current_idx();
|
||||||
|
self.patch_jump(jump_no_match, next_arm);
|
||||||
}
|
}
|
||||||
el_parser::Pattern::Wildcard => {
|
_ => {
|
||||||
// Wildcard — push nil (always "matches")
|
self.emit(Bytecode::Dup);
|
||||||
self.emit(Bytecode::Push(Value::Nil));
|
// Push pattern value
|
||||||
|
match &arm.pattern {
|
||||||
|
el_parser::Pattern::Literal(lit) => {
|
||||||
|
let v = match lit {
|
||||||
|
Literal::Int(n) => Value::Int(*n),
|
||||||
|
Literal::Str(s) => Value::Str(s.clone()),
|
||||||
|
Literal::Bool(b) => Value::Bool(*b),
|
||||||
|
Literal::Float(f) => Value::Float(*f),
|
||||||
|
};
|
||||||
|
self.emit(Bytecode::Push(v));
|
||||||
|
}
|
||||||
|
el_parser::Pattern::EnumVariant { variant, payload, .. } => {
|
||||||
|
// Push the variant name as a string for comparison
|
||||||
|
self.emit(Bytecode::Push(Value::Str(variant.clone())));
|
||||||
|
if let Some(bind) = payload {
|
||||||
|
// Store the subject (simplified: payload = subject)
|
||||||
|
self.emit(Bytecode::StoreLocal(bind.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("wildcard and binding handled above"),
|
||||||
|
}
|
||||||
|
self.emit(Bytecode::Eq);
|
||||||
|
let jump_no_match = self.emit(Bytecode::JumpIfNot(0));
|
||||||
|
// Pop subject from stack
|
||||||
|
self.emit(Bytecode::Pop);
|
||||||
|
// Generate arm body
|
||||||
|
self.gen_expr(&arm.body)?;
|
||||||
|
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
||||||
|
let next_arm = self.current_idx();
|
||||||
|
self.patch_jump(jump_no_match, next_arm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.emit(Bytecode::Eq);
|
|
||||||
let jump_no_match = self.emit(Bytecode::JumpIfNot(0));
|
|
||||||
// Pop subject from stack
|
|
||||||
self.emit(Bytecode::Pop);
|
|
||||||
// Generate arm body
|
|
||||||
self.gen_expr(&arm.body)?;
|
|
||||||
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
|
||||||
let next_arm = self.current_idx();
|
|
||||||
self.patch_jump(jump_no_match, next_arm);
|
|
||||||
}
|
}
|
||||||
// Default: pop subject, push nil
|
// Default fallthrough: pop subject, push nil
|
||||||
self.emit(Bytecode::Pop);
|
self.emit(Bytecode::Pop);
|
||||||
self.emit(Bytecode::Push(Value::Nil));
|
self.emit(Bytecode::Push(Value::Nil));
|
||||||
let end = self.current_idx();
|
let end = self.current_idx();
|
||||||
|
|||||||
Reference in New Issue
Block a user