diff --git a/_archive/rust-bootstrap/Cargo.lock b/_archive/rust-bootstrap/Cargo.lock deleted file mode 100644 index c2220b8..0000000 --- a/_archive/rust-bootstrap/Cargo.lock +++ /dev/null @@ -1,5088 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-activity" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" -dependencies = [ - "android-properties", - "bitflags 2.11.1", - "cc", - "cesu8", - "jni", - "jni-sys 0.3.1", - "libc", - "log", - "ndk 0.8.0", - "ndk-context", - "ndk-sys 0.5.0+25.2.9519653", - "num_enum", - "thiserror 1.0.69", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" - -[[package]] -name = "blake3" -version = "1.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures 0.3.0", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" -dependencies = [ - "objc-sys", -] - -[[package]] -name = "block2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" -dependencies = [ - "block-sys", - "objc2 0.4.1", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "calloop" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" -dependencies = [ - "bitflags 2.11.1", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "cc" -version = "1.2.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clap" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation 0.9.4", - "core-graphics", - "foreign-types 0.5.0", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.9.4", - "core-graphics-types", - "libc", - "objc", -] - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[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]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "ctor" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf" -dependencies = [ - "dtor", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "cursor-icon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "data-encoding" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dlib" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" -dependencies = [ - "libloading 0.8.9", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - -[[package]] -name = "drm" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" -dependencies = [ - "bitflags 2.11.1", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "rustix 0.38.44", -] - -[[package]] -name = "drm-ffi" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" -dependencies = [ - "drm-sys", - "rustix 0.38.44", -] - -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" -dependencies = [ - "libc", - "linux-raw-sys 0.6.5", -] - -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dtor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b" - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2", - "subtle", - "zeroize", -] - -[[package]] -name = "el" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "base64", - "blake3", - "clap", - "crossbeam-channel", - "ed25519-dalek", - "el-build", - "el-compiler", - "el-fmt", - "el-lexer", - "el-lint", - "el-manifest", - "el-parser", - "el-registry", - "el-seal", - "el-test", - "el-types", - "fontdue", - "hex", - "hmac", - "image", - "native-tls", - "rand 0.8.6", - "reqwest", - "serde", - "serde_json", - "sha2", - "softbuffer", - "thiserror 2.0.18", - "tiny-skia", - "tiny_http", - "tokio", - "tungstenite", - "uuid", - "walkdir", - "winit", -] - -[[package]] -name = "el-arch" -version = "0.1.0" -dependencies = [ - "el-lexer", - "el-parser", -] - -[[package]] -name = "el-build" -version = "0.1.0" -dependencies = [ - "blake3", - "el-compiler", - "el-lexer", - "el-manifest", - "el-parser", - "el-registry", - "el-seal", - "el-types", - "semver", - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "el-compiler" -version = "0.1.0" -dependencies = [ - "el-lexer", - "el-parser", - "el-seal", - "el-types", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "el-fmt" -version = "0.1.0" -dependencies = [ - "el-lexer", - "el-parser", - "thiserror 2.0.18", -] - -[[package]] -name = "el-integration" -version = "0.1.0" -dependencies = [ - "el-compiler", - "el-lexer", - "el-parser", - "el-seal", - "el-stdlib", - "el-types", - "serde_json", -] - -[[package]] -name = "el-lexer" -version = "0.1.0" -dependencies = [ - "thiserror 2.0.18", -] - -[[package]] -name = "el-lint" -version = "0.1.0" -dependencies = [ - "el-arch", - "el-fmt", - "el-lexer", - "el-parser", - "el-types", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "el-manifest" -version = "0.1.0" -dependencies = [ - "el-lexer", - "semver", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "el-parser" -version = "0.1.0" -dependencies = [ - "el-lexer", - "thiserror 2.0.18", -] - -[[package]] -name = "el-registry" -version = "0.1.0" -dependencies = [ - "blake3", - "el-manifest", - "reqwest", - "semver", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "el-seal" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "blake3", - "engram-crypto", - "rand 0.8.6", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "el-stdlib" -version = "0.1.0" -dependencies = [ - "el-parser", - "el-types", -] - -[[package]] -name = "el-test" -version = "0.1.0" -dependencies = [ - "el-compiler", - "el-lexer", - "el-parser", - "el-types", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "el-types" -version = "0.1.0" -dependencies = [ - "el-lexer", - "el-parser", - "thiserror 2.0.18", -] - -[[package]] -name = "el-vm" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "base64", - "blake3", - "crossbeam-channel", - "el-compiler", - "hex", - "hmac", - "rand 0.8.6", - "reqwest", - "serde_json", - "sha2", - "uuid", - "walkdir", -] - -[[package]] -name = "elvm" -version = "0.1.0" -dependencies = [ - "clap", - "dpi", - "el-compiler", - "el-vm", - "winit", - "wry", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "engram-crypto" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "base64", - "blake3", - "rand 0.8.6", - "serde", - "serde_json", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "fontdue" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9099a2f86b8e674b75d03ff154b3fe4c5208ed249ced8d69cc313a9fa40bb488" -dependencies = [ - "hashbrown 0.14.5", - "ttf-parser", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -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]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "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 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "html5ever" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" -dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa 1.0.18", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "itoa 1.0.18", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", - "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]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "icrate" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" -dependencies = [ - "block2 0.3.0", - "dispatch", - "objc2 0.4.1", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "moxcms", - "num-traits", - "png 0.18.1", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.0", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "kuchikiki" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 1.9.3", - "matches", - "selectors", -] - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libredox" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" -dependencies = [ - "bitflags 2.11.1", - "libc", - "plain", - "redox_syscall 0.7.4", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markup5ever" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" -dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memmap2" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", -] - -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[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]] -name = "ndk" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" -dependencies = [ - "bitflags 2.11.1", - "jni-sys 0.3.1", - "log", - "ndk-sys 0.5.0+25.2.9519653", - "num_enum", - "raw-window-handle 0.5.2", - "raw-window-handle 0.6.2", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.1", - "jni-sys 0.3.1", - "log", - "ndk-sys 0.6.0+11769913", - "num_enum", - "raw-window-handle 0.6.2", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" -dependencies = [ - "objc-sys", - "objc2-encode 3.0.0", -] - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode 4.1.0", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "libc", - "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation", -] - -[[package]] -name = "objc2-encode" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" -dependencies = [ - "bitflags 2.11.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl" -version = "0.10.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "foreign-types 0.3.2", - "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 2.0.117", -] - -[[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]] -name = "orbclient" -version = "0.3.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd" -dependencies = [ - "libc", - "libredox", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.6", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.6", -] - -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.2", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.11.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pxfm" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" - -[[package]] -name = "quick-xml" -version = "0.39.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" -dependencies = [ - "memchr", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases 0.2.1", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time 1.1.0", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.4", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time 1.1.0", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases 0.2.1", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" -dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.11.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.11.1", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" -dependencies = [ - "web-time 1.1.0", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[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 = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.11.1", - "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]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", - "thin-slice", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa 1.0.18", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa 1.0.18", - "ryu", - "serde", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "softbuffer" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f266ce2aa23eaaaa4e758ed44495d505d00fb79f359d46f6c1900cb053123b62" -dependencies = [ - "as-raw-xcb-connection", - "bytemuck", - "cfg_aliases 0.1.1", - "cocoa", - "core-graphics", - "drm", - "fastrand", - "foreign-types 0.5.0", - "js-sys", - "log", - "memmap2", - "objc", - "raw-window-handle 0.5.2", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "tiny-xlib", - "wasm-bindgen", - "wayland-backend", - "wayland-client", - "wayland-sys", - "web-sys", - "windows-sys 0.48.0", - "x11rb", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[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.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags 2.11.1", - "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]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa 1.0.18", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png 0.17.16", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tiny-xlib" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" -dependencies = [ - "as-raw-xcb-connection", - "ctor", - "libloading 0.8.9", - "pkg-config", - "tracing", -] - -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[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]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "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]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.11+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags 2.11.1", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "ttf-parser" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" - -[[package]] -name = "tungstenite" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.8.6", - "sha1", - "thiserror 1.0.69", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-segmentation" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.117", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - -[[package]] -name = "wayland-backend" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" -dependencies = [ - "cc", - "downcast-rs", - "rustix 1.1.4", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" -dependencies = [ - "bitflags 2.11.1", - "rustix 1.1.4", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "webview2-com-sys" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" -dependencies = [ - "thiserror 1.0.69", - "windows", - "windows-core", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-wsapoll" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eafc5f679c576995526e81635d0cf9695841736712b4e892f87abbe6fed3f28" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -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 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[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.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winit" -version = "0.29.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" -dependencies = [ - "android-activity", - "atomic-waker", - "bitflags 2.11.1", - "calloop", - "cfg_aliases 0.1.1", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "icrate", - "js-sys", - "libc", - "log", - "ndk 0.8.0", - "ndk-sys 0.5.0+25.2.9519653", - "objc2 0.4.1", - "once_cell", - "orbclient", - "raw-window-handle 0.5.2", - "raw-window-handle 0.6.2", - "redox_syscall 0.3.5", - "rustix 0.38.44", - "smol_str", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "web-time 0.2.4", - "windows-sys 0.48.0", - "xkbcommon-dl", -] - -[[package]] -name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "wry" -version = "0.47.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ce51277d65170f6379d8cda935c80e3c2d1f0ff712a123c8bddb11b31a4b73" -dependencies = [ - "base64", - "block2 0.5.1", - "cookie", - "crossbeam-channel", - "dpi", - "dunce", - "html5ever", - "http", - "jni", - "kuchikiki", - "libc", - "ndk 0.9.0", - "objc2 0.5.2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle 0.6.2", - "sha2", - "tao-macros", - "thiserror 1.0.69", - "url", - "webview2-com", - "windows", - "windows-core", - "windows-version", -] - -[[package]] -name = "x11rb" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading 0.7.4", - "nix", - "once_cell", - "winapi", - "winapi-wsapoll", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" -dependencies = [ - "nix", -] - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.11.1", - "dlib", - "log", - "once_cell", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "zerofrom" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/_archive/rust-bootstrap/Cargo.toml b/_archive/rust-bootstrap/Cargo.toml deleted file mode 100644 index 50384d0..0000000 --- a/_archive/rust-bootstrap/Cargo.toml +++ /dev/null @@ -1,72 +0,0 @@ -[workspace] -members = [ - "engrams/el-lexer", - "engrams/el-parser", - "engrams/el-types", - "engrams/el-compiler", - "engrams/el-seal", - "engrams/el-manifest", - "engrams/el-registry", - "engrams/el-build", - "engrams/el-test", - "engrams/el-stdlib", - "engrams/el-integration", - "engrams/el-fmt", - "engrams/el-lint", - "engrams/el-vm", - "bin/el", - "bin/elvm", -] -resolver = "2" - -[workspace.package] -version = "0.1.0" -edition = "2021" -license = "MIT" -authors = ["Neuron Technologies"] - -[workspace.dependencies] -# Internal engrams (Rust substrate) -el-lexer = { path = "engrams/el-lexer" } -el-parser = { path = "engrams/el-parser" } -el-types = { path = "engrams/el-types" } -el-compiler = { path = "engrams/el-compiler" } -el-seal = { path = "engrams/el-seal" } -el-manifest = { path = "engrams/el-manifest" } -el-registry = { path = "engrams/el-registry" } -el-build = { path = "engrams/el-build" } -el-test = { path = "engrams/el-test" } -el-stdlib = { path = "engrams/el-stdlib" } -el-integration = { path = "engrams/el-integration" } -el-fmt = { path = "engrams/el-fmt" } -el-lint = { path = "engrams/el-lint" } -el-vm = { path = "engrams/el-vm" } - -# Engram crypto (path dep — the sealed target depends on it) -engram-crypto = { path = "../engram/engrams/engram-crypto" } - -# External -serde = { version = "1", features = ["derive"] } -serde_json = "1" -thiserror = "2" -blake3 = "1" -aes-gcm = "0.10" -rand = "0.8" -clap = { version = "4", features = ["derive", "env"] } -reqwest = { version = "0.12", features = ["json", "blocking"] } -uuid = { version = "1", features = ["v4"] } -walkdir = "2" -tiny_http = "0.12" -hmac = "0.12" -sha2 = "0.10" -hex = "0.4" -base64 = "0.22" -winit = { version = "0.29", default-features = false, features = ["rwh_05"] } -softbuffer = "0.3" -tiny-skia = "0.11" -fontdue = "0.8" -image = { version = "0.25", default-features = false, features = ["png"] } -tungstenite = { version = "0.23", features = ["native-tls"] } -native-tls = "0.2" -crossbeam-channel = "0.5" -ed25519-dalek = { version = "2", features = ["rand_core"] } diff --git a/_archive/rust-bootstrap/bin/el/Cargo.toml b/_archive/rust-bootstrap/bin/el/Cargo.toml deleted file mode 100644 index 0ad6e21..0000000 --- a/_archive/rust-bootstrap/bin/el/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "el" -description = "Engram language CLI — el build / run / check / seal / unseal / new / add / publish / …" -version.workspace = true -edition.workspace = true -license.workspace = true - -[[bin]] -name = "el" -path = "src/main.rs" - -[dependencies] -el-lexer = { workspace = true } -el-parser = { workspace = true } -el-types = { workspace = true } -el-compiler = { workspace = true } -el-seal = { workspace = true } -el-manifest = { workspace = true } -el-registry = { workspace = true } -el-build = { workspace = true } -el-test = { workspace = true } -el-fmt = { workspace = true } -el-lint = { workspace = true } -clap = { workspace = true } -thiserror = { workspace = true } -serde_json = { workspace = true } -serde = { workspace = true } -tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } -reqwest = { workspace = true } -uuid = { workspace = true } -walkdir = { workspace = true } -tiny_http = { workspace = true } -blake3 = { workspace = true } -hmac = { workspace = true } -sha2 = { workspace = true } -hex = { workspace = true } -base64 = { workspace = true } -aes-gcm = { workspace = true } -rand = { workspace = true } -ed25519-dalek = { workspace = true } -crossbeam-channel = { workspace = true } -winit = { version = "0.29", default-features = false, features = ["rwh_05"] } -softbuffer = "0.3" -tiny-skia = "0.11" -fontdue = "0.8" -image = { workspace = true } -tungstenite = { workspace = true } -native-tls = { workspace = true } diff --git a/_archive/rust-bootstrap/bin/el/src/cache.rs b/_archive/rust-bootstrap/bin/el/src/cache.rs deleted file mode 100644 index 488991a..0000000 --- a/_archive/rust-bootstrap/bin/el/src/cache.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! In-memory LRU cache for HTTP responses and LLM outputs. -//! -//! Thread-safe global cache keyed by arbitrary strings. Entries expire after a -//! configurable TTL. Eviction uses a simple LRU strategy: when the entry limit -//! is reached the oldest entry (by insertion / last-access order) is dropped. -//! -//! This module only contains the cache data-structure and its helper -//! functions. The builtin dispatch arms that expose `cache_get`, `cache_set`, -//! `cache_invalidate`, and `cache_clear` live in `main.rs`. - -use std::collections::HashMap; -use std::sync::{Mutex, OnceLock}; -use std::time::{Duration, Instant}; - -const MAX_ENTRIES: usize = 1000; - -/// A single cached value together with its expiry time. -struct Entry { - value: String, - expires_at: Instant, - /// Monotonically increasing sequence number used to identify the LRU entry. - seq: u64, -} - -struct Cache { - entries: HashMap, - /// Counter incremented on every write; stored in Entry::seq. - seq: u64, -} - -impl Cache { - fn new() -> Self { - Cache { - entries: HashMap::new(), - seq: 0, - } - } - - /// Store `value` under `key`, expiring after `ttl_secs` seconds. - /// If the cache is full the LRU entry is evicted before insertion. - pub fn set(&mut self, key: String, value: String, ttl_secs: u64) { - // Evict expired entries first (cheap pass). - let now = Instant::now(); - self.entries.retain(|_, e| e.expires_at > now); - - // If still full, evict the entry with the lowest sequence number (LRU). - if self.entries.len() >= MAX_ENTRIES { - if let Some(lru_key) = self - .entries - .iter() - .min_by_key(|(_, e)| e.seq) - .map(|(k, _)| k.clone()) - { - self.entries.remove(&lru_key); - } - } - - self.seq += 1; - self.entries.insert( - key, - Entry { - value, - expires_at: now + Duration::from_secs(ttl_secs), - seq: self.seq, - }, - ); - } - - /// Return the cached value if it exists and has not expired, otherwise `None`. - pub fn get(&mut self, key: &str) -> Option { - let now = Instant::now(); - if let Some(e) = self.entries.get_mut(key) { - if e.expires_at > now { - // Bump sequence number to record this access (LRU). - self.seq += 1; - e.seq = self.seq; - return Some(e.value.clone()); - } - // Expired — remove it. - self.entries.remove(key); - } - None - } - - /// Remove a specific key. - pub fn invalidate(&mut self, key: &str) { - self.entries.remove(key); - } - - /// Remove all entries. - pub fn clear(&mut self) { - self.entries.clear(); - } -} - -// ── Global singleton ────────────────────────────────────────────────────────── - -static CACHE: OnceLock> = OnceLock::new(); - -fn global() -> &'static Mutex { - CACHE.get_or_init(|| Mutex::new(Cache::new())) -} - -// ── Public API ──────────────────────────────────────────────────────────────── - -/// Store an arbitrary string value under `key` with a TTL in seconds. -pub fn cache_set(key: String, value: String, ttl_secs: u64) { - if let Ok(mut c) = global().lock() { - c.set(key, value, ttl_secs); - } -} - -/// Return the cached value for `key`, or `None` if missing / expired. -pub fn cache_get(key: &str) -> Option { - global().lock().ok()?.get(key) -} - -/// Remove a specific key from the cache. -pub fn cache_invalidate(key: &str) { - if let Ok(mut c) = global().lock() { - c.invalidate(key); - } -} - -/// Remove all entries from the cache. -pub fn cache_clear() { - if let Ok(mut c) = global().lock() { - c.clear(); - } -} diff --git a/_archive/rust-bootstrap/bin/el/src/dashboard.html b/_archive/rust-bootstrap/bin/el/src/dashboard.html deleted file mode 100644 index 95b1b95..0000000 --- a/_archive/rust-bootstrap/bin/el/src/dashboard.html +++ /dev/null @@ -1,1914 +0,0 @@ - - - - - -Neuron Code - - - - - -
- - -
- -
-
Connecting to event stream…
-
- - - -
-
- -
- - - - - -
- - - -
- - - - - - - - - - - -

Hello. I'm Neuron.

-

Your AI-native development partner. Tell me where your code lives and I'll start understanding it.

- - -
- - -
- - -
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
Neuron
-
- - -
- Voice -
- - - - -
- -
- - - 1.0 -
-
- - - 0.95 -
-
- - - 1.0 -
- - -
-
Memory:
-
- - -
-
-
- - - -
-
-
-
- - -
-
-

Repositories

- -
-
- -
- -
-
- - -
-
-

Issues

-
- - -
- -
-
-
-
- -
- - -
-
-

Pull Requests

- -
-
-
- - -
-
-

Pipeline

- -
-
-
- - -
-
- -
History
-
-
-
- - -
-
-

Files

- -
- -
-
-
-
-
-
-
-
-
- - -
-
-

Search

-
-
-
- -
- - - -
- -
-
-
-
- - -
-
-

Wiki

- -
-
-
-
-
Select a page to read it.
-
-
-
- - -
-
-

Security Scan

-
- - -
-
-
Click Scan to run a security analysis on the active repo.
-
-
- - -
-
-

Live Events

- 0 events -
-
-
- - -
-
-

Insights

- -
-
-
Select an active repo and click Refresh.
-
-
- -
-
-
-
- - -
-
-

⚙ Settings

-
- - -
-
- -
-
-
-
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
- - -
- - - - - - - - - - - - - - - - - - - 🔇 Whisper -
- - -
-
- - -
- - - - diff --git a/_archive/rust-bootstrap/bin/el/src/main.rs b/_archive/rust-bootstrap/bin/el/src/main.rs deleted file mode 100644 index 9126381..0000000 --- a/_archive/rust-bootstrap/bin/el/src/main.rs +++ /dev/null @@ -1,9849 +0,0 @@ -//! el — The Engram language CLI. -//! -//! # Commands -//! -//! ## Project management -//! el new scaffold new project with manifest.el + src/main.el -//! el add [@ver] add a dependency to manifest.el -//! el remove remove a dependency from manifest.el -//! el update update all deps to latest compatible versions -//! -//! ## Build & run -//! el build [--target prod] build the project (reads manifest.el) -//! el build --cross build for all configured cross targets -//! el run build debug and run -//! el test run tests -//! el check type-check only -//! el fmt format source files -//! el clean clean build artifacts -//! -//! ## Registry -//! el publish publish to registry -//! el search search registry -//! el plugin add add compiler plugin -//! -//! ## Low-level -//! el build-file compile a single .el file (no manifest.el needed) -//! el seal seal an existing release artifact -//! el unseal unseal a sealed artifact - -mod cache; -mod net; -mod telemetry; - -use std::path::PathBuf; - -// ── App block context (parsed from `app "name" { ... }` at startup) ─────────── - -thread_local! { - /// Resolved environment: "dev" | "stage" | "prod" (from NEURON_ENV, default "dev") - static APP_ENV: std::cell::RefCell = std::cell::RefCell::new(String::new()); - /// App name from `app "name" { ... }` - static APP_SERVICE: std::cell::RefCell = std::cell::RefCell::new(String::new()); - /// App version from `version "x.y.z"` - static APP_VERSION: std::cell::RefCell = std::cell::RefCell::new(String::new()); - /// Stable UUID for this process lifetime (generated once) - static APP_INSTANCE: std::cell::RefCell = std::cell::RefCell::new(String::new()); - /// Resolved config key → value (after env overlay) - static APP_CONFIG: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - /// Resolved secrets key → value - static APP_SECRETS: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - /// Feature flags key → Bool value - static APP_FLAGS: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - /// Set of secret values (for redaction in traces/logs) - static SECRET_VALUES: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashSet::new()); -} - -// ── App block parser ────────────────────────────────────────────────────────── - -/// Parsed representation of an `app` block found in El source. -#[derive(Default, Debug)] -struct AppBlock { - name: String, - version: String, - /// Base config values: key → (value_str, type_hint) - config_base: std::collections::HashMap, - /// Env overlay values: env_name → (key → value_str) - config_overlays: std::collections::HashMap>, - /// Required secrets: key → type_hint - secrets: Vec<(String, String)>, - /// Feature flags: key → default_value - flags: std::collections::HashMap, -} - -/// Minimal tokenizer that splits El source into tokens, ignoring comments. -fn tokenize_simple(src: &str) -> Vec { - let mut tokens = Vec::new(); - let mut chars = src.chars().peekable(); - while let Some(&c) = chars.peek() { - if c.is_whitespace() { chars.next(); continue; } - // Line comment - if c == '/' { - chars.next(); - if chars.peek() == Some(&'/') { - // consume to end of line - while let Some(&nc) = chars.peek() { chars.next(); if nc == '\n' { break; } } - continue; - } - tokens.push("/".to_string()); - continue; - } - // String literal - if c == '"' { - chars.next(); - let mut s = String::new(); - while let Some(&nc) = chars.peek() { - chars.next(); - if nc == '"' { break; } - if nc == '\\' { if let Some(&esc) = chars.peek() { chars.next(); match esc { 'n'=>s.push('\n'), 't'=>s.push('\t'), _=>s.push(esc) } } } - else { s.push(nc); } - } - tokens.push(format!("\"{}\"", s)); - continue; - } - // Braces, colons, equals, semicolons - if "{}:;=".contains(c) { chars.next(); tokens.push(c.to_string()); continue; } - // Identifier or number - if c.is_alphanumeric() || c == '_' || c == '-' || c == '.' { - let mut w = String::new(); - while let Some(&nc) = chars.peek() { - if nc.is_alphanumeric() || nc == '_' || nc == '-' || nc == '.' { w.push(nc); chars.next(); } - else { break; } - } - tokens.push(w); - continue; - } - chars.next(); - } - tokens -} - -/// Extract and parse the first `app "name" { ... }` block from El source. -/// Returns None if no app block is present. -fn parse_app_block(source: &str) -> Option { - let tokens = tokenize_simple(source); - let mut i = 0usize; - // Find `app` - while i < tokens.len() { - if tokens[i] == "app" && i + 1 < tokens.len() && tokens[i+1].starts_with('"') { - break; - } - i += 1; - } - if i >= tokens.len() { return None; } - // tokens[i] = "app", tokens[i+1] = "\"name\"", tokens[i+2] = "{" - let raw_name = &tokens[i + 1]; - let name = raw_name.trim_matches('"').to_string(); - i += 2; - if i >= tokens.len() || tokens[i] != "{" { return None; } - i += 1; - - let mut block = AppBlock { name, ..Default::default() }; - let mut depth = 1usize; - - while i < tokens.len() && depth > 0 { - let tok = tokens[i].clone(); - match tok.as_str() { - "{" => { depth += 1; i += 1; } - "}" => { depth -= 1; i += 1; } - "version" if depth == 1 => { - i += 1; - if i < tokens.len() && tokens[i].starts_with('"') { - block.version = tokens[i].trim_matches('"').to_string(); - i += 1; - } - } - "config" if depth == 1 => { - i += 1; - if i < tokens.len() && tokens[i] == "{" { i += 1; } - // Parse config entries until matching } - let mut cfg_depth = 1usize; - while i < tokens.len() && cfg_depth > 0 { - let ct = tokens[i].clone(); - match ct.as_str() { - "{" => { cfg_depth += 1; i += 1; } - "}" => { cfg_depth -= 1; i += 1; } - _ => { - // Could be: KEY: Type = "value" or env_name { ... } - // Peek ahead to determine - if i + 1 < tokens.len() && tokens[i+1] == "{" { - // env overlay block e.g. `prod { KEY = "val" ... }` - let env_name = ct.clone(); - i += 2; // skip name and { - let mut env_entries: std::collections::HashMap = Default::default(); - while i < tokens.len() && tokens[i] != "}" { - // KEY = "value" - let key = tokens[i].clone(); - i += 1; - if i < tokens.len() && tokens[i] == "=" { i += 1; } - let val = if i < tokens.len() { - tokens[i].trim_matches('"').to_string() - } else { String::new() }; - i += 1; - env_entries.insert(key, val); - } - if i < tokens.len() { i += 1; } // skip } - block.config_overlays.insert(env_name, env_entries); - } else { - // KEY: Type = "default" OR KEY: Type (no default) - let key = ct.clone(); - i += 1; - let mut type_hint = "String".to_string(); - let mut default_val = String::new(); - if i < tokens.len() && tokens[i] == ":" { - i += 1; - if i < tokens.len() { type_hint = tokens[i].clone(); i += 1; } - } - if i < tokens.len() && tokens[i] == "=" { - i += 1; - if i < tokens.len() { default_val = tokens[i].trim_matches('"').to_string(); i += 1; } - } - block.config_base.insert(key, (default_val, type_hint)); - } - } - } - } - } - "secrets" if depth == 1 => { - i += 1; - if i < tokens.len() && tokens[i] == "{" { i += 1; } - while i < tokens.len() && tokens[i] != "}" { - let key = tokens[i].clone(); - i += 1; - let mut type_hint = "String".to_string(); - if i < tokens.len() && tokens[i] == ":" { - i += 1; - if i < tokens.len() { type_hint = tokens[i].clone(); i += 1; } - } - block.secrets.push((key, type_hint)); - } - if i < tokens.len() { i += 1; } // skip } - } - "flags" if depth == 1 => { - i += 1; - if i < tokens.len() && tokens[i] == "{" { i += 1; } - while i < tokens.len() && tokens[i] != "}" { - let key = tokens[i].clone(); - i += 1; - if i < tokens.len() && tokens[i] == ":" { - i += 1; - if i < tokens.len() { i += 1; } // skip type hint (Bool) - } - let mut default_val = false; - if i < tokens.len() && tokens[i] == "=" { - i += 1; - if i < tokens.len() { - default_val = tokens[i] == "true"; - i += 1; - } - } - block.flags.insert(key, default_val); - } - if i < tokens.len() { i += 1; } // skip } - } - _ => { i += 1; } - } - } - Some(block) -} - -/// Load secrets.json from ~/.neuron/secrets.json, return key→value map. -fn load_neuron_secrets_json() -> std::collections::HashMap { - let home = std::env::var("HOME").unwrap_or_default(); - let path = format!("{home}/.neuron/secrets.json"); - if let Ok(content) = std::fs::read_to_string(&path) { - if let Ok(serde_json::Value::Object(map)) = serde_json::from_str::(&content) { - return map.into_iter() - .filter_map(|(k, v)| v.as_str().map(|s| (k, s.to_string()))) - .collect(); - } - } - std::collections::HashMap::new() -} - -/// Resolve an app block against the current environment and load everything into thread-locals. -/// Returns Err with a descriptive message if a required secret is missing. -fn apply_app_block(block: AppBlock) -> Result<(), String> { - use el_compiler::Value; - - let env = std::env::var("NEURON_ENV").unwrap_or_else(|_| "dev".to_string()); - let instance = uuid::Uuid::new_v4().to_string(); - - // Resolve config: env var > env overlay > base default - let overlay = block.config_overlays.get(&env).cloned().unwrap_or_default(); - let mut config_map: std::collections::HashMap = std::collections::HashMap::new(); - for (key, (base_default, type_hint)) in &block.config_base { - // Check env var override first - let raw = std::env::var(key) - .ok() - .or_else(|| overlay.get(key).cloned()) - .unwrap_or_else(|| base_default.clone()); - let value = match type_hint.as_str() { - "Int" => raw.trim().parse::().map(Value::Int).unwrap_or(Value::Str(raw.clone())), - "Bool" => Value::Bool(raw.trim() == "true" || raw.trim() == "1"), - _ => Value::Str(raw.clone()), - }; - config_map.insert(key.clone(), value); - } - - // Resolve secrets - let secrets_file = load_neuron_secrets_json(); - let mut secrets_map: std::collections::HashMap = std::collections::HashMap::new(); - let mut secret_vals: std::collections::HashSet = std::collections::HashSet::new(); - for (key, _type_hint) in &block.secrets { - let val = std::env::var(key) - .ok() - .or_else(|| secrets_file.get(key).cloned()); - match val { - Some(v) => { - if !v.is_empty() { secret_vals.insert(v.clone()); } - secrets_map.insert(key.clone(), v); - } - None => { - return Err(format!( - "Error: required secret '{}' not found. Set environment variable or add to ~/.neuron/secrets.json", - key - )); - } - } - } - - // Store everything in thread-locals - APP_ENV.with(|e| *e.borrow_mut() = env); - APP_SERVICE.with(|s| *s.borrow_mut() = block.name); - APP_VERSION.with(|v| *v.borrow_mut() = block.version); - APP_INSTANCE.with(|id| *id.borrow_mut() = instance); - APP_CONFIG.with(|c| *c.borrow_mut() = config_map); - APP_SECRETS.with(|s| *s.borrow_mut() = secrets_map); - APP_FLAGS.with(|f| *f.borrow_mut() = block.flags); - SECRET_VALUES.with(|sv| *sv.borrow_mut() = secret_vals); - - Ok(()) -} - -/// Redact any known secret values from a string (best-effort). -#[allow(dead_code)] -fn redact_secrets(s: &str) -> String { - SECRET_VALUES.with(|sv| { - let sv = sv.borrow(); - let mut result = s.to_string(); - for secret in sv.iter() { - if !secret.is_empty() && result.contains(secret.as_str()) { - result = result.replace(secret.as_str(), "[REDACTED]"); - } - } - result - }) -} - -/// Strip the first `app "..." { ... }` block from El source so the compiler -/// doesn't see the declaration syntax (which it cannot parse as an expression). -/// The block is replaced with an equal number of blank lines to preserve line numbers. -/// Only matches `app "` at the start of a line (optional leading whitespace), to -/// avoid matching the keyword inside comments. -fn strip_app_block(source: &str) -> String { - // Find `app "` that appears at the start of a line (not inside a // comment). - let app_start = { - let mut found: Option = None; - for (i, _) in source.match_indices("app \"") { - // Walk backwards to start of line — if we hit '//' first, this is a comment. - let line_start = source[..i].rfind('\n').map(|p| p + 1).unwrap_or(0); - let prefix = &source[line_start..i]; - if prefix.contains("//") { - continue; // inside a comment - } - // Only accept if prefix is whitespace-only (app block declaration) - if prefix.chars().all(|c| c.is_whitespace()) { - found = Some(i); - break; - } - } - match found { - Some(pos) => pos, - None => return source.to_string(), - } - }; - - // Walk from app_start to find the opening brace (skip name token) - let after_app = &source[app_start..]; - let brace_pos = if let Some(p) = after_app.find('{') { p } else { return source.to_string() }; - - let mut depth = 0usize; - let mut end = app_start + brace_pos; - let chars: Vec = source[app_start + brace_pos..].chars().collect(); - for (i, &ch) in chars.iter().enumerate() { - match ch { - '{' => depth += 1, - '}' => { - depth -= 1; - if depth == 0 { - end = app_start + brace_pos + i + 1; - break; - } - } - _ => {} - } - } - - // Replace the block span with blank lines (preserve line count for error messages). - let removed = &source[app_start..end]; - let newlines = removed.chars().filter(|&c| c == '\n').count(); - let replacement = "\n".repeat(newlines); - format!("{}{}{}", &source[..app_start], replacement, &source[end..]) -} - -/// Try to parse and apply app block from source. If parsing succeeds, apply context. -/// Prints error and exits on secret resolution failure. -fn maybe_apply_app_block(source: &str) { - if let Some(block) = parse_app_block(source) { - if !block.name.is_empty() { - // Update telemetry service name from app block - telemetry::set_service_name(&block.name.clone()); - telemetry::init(&block.name); - if let Err(e) = apply_app_block(block) { - eprintln!("{e}"); - std::process::exit(1); - } - } - } -} - -use clap::{Parser, Subcommand}; -use el_build::BuildSystem; -use el_compiler::{Compiler, CompilerOptions, Target}; -use el_test; -use el_manifest::{BuildTarget, Manifest}; -use el_seal::{seal as seal_fn, unseal as unseal_fn, SealedArtifact, DeploymentBinding, SealAlgorithm, SealConfig}; - -// ── Global state (thread-local for simplicity) ──────────────────────────────── - -thread_local! { - static GLOBAL_STATE: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - - /// Callback set by the interpreter before http_serve blocks. - /// When a request arrives, http_serve calls this to invoke handle_request. - static HTTP_SERVE_CALL: std::cell::RefCell>> = - std::cell::RefCell::new(None); - - /// Shared bytecode instructions for sub-interpreter calls from http_serve. - static SERVE_INSTRUCTIONS: std::cell::RefCell>>> = - std::cell::RefCell::new(None); - - /// Shared fn_table for sub-interpreter calls. - static SERVE_FN_TABLE: std::cell::RefCell>>> = - std::cell::RefCell::new(None); -} - -// ── Canvas / native window state ───────────────────────────────────────────── - -#[derive(Default)] -struct CanvasState { - // Window configuration - title: String, - width: u32, - height: u32, - // Drawing surface (tiny-skia pixmap) - pixmap: Option, - // Font for text rendering (fontdue) - font: Option, - // Input state - mouse_x: i32, - mouse_y: i32, - mouse_buttons: u8, // bit 0 = left, bit 1 = right - // Events accumulated since last frame (JSON strings) - events: Vec, - // Clip rectangle stack: (x, y, w, h) - clips: Vec<(i32, i32, u32, u32)>, -} - -thread_local! { - static CANVAS: std::cell::RefCell = std::cell::RefCell::new(CanvasState { - title: String::new(), - width: 1200, - height: 800, - pixmap: None, - font: None, - mouse_x: 0, - mouse_y: 0, - mouse_buttons: 0, - events: Vec::new(), - clips: Vec::new(), - }); -} - -// ── Network socket state (thread-local, keyed by opaque handle string) ─────── - -thread_local! { - static TCP_STREAMS: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - - static TCP_LISTENERS: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - - static UDP_SOCKETS: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - - static WS_SOCKETS: std::cell::RefCell>>> = - std::cell::RefCell::new(std::collections::HashMap::new()); - - static NET_COUNTER: std::cell::Cell = std::cell::Cell::new(1); -} - -fn next_net_id(prefix: &str) -> String { - NET_COUNTER.with(|c| { - let id = c.get(); - c.set(id + 1); - format!("{}:{}", prefix, id) - }) -} - -// ── Telemetry — per-interpreter function span stack ─────────────────────────── - -thread_local! { - /// Active user-function spans. Each entry: (fn_name, start_ns, trace_id, span_id, parent_span_id) - static FN_SPAN_STACK: std::cell::RefCell)>> = - std::cell::RefCell::new(Vec::new()); - - /// Explicit span registry for trace_start / trace_end / trace_tag builtins. - /// Key: span_id → (trace_id, span_id, start_ns, name, attrs) - static EXPLICIT_SPANS: std::cell::RefCell) - >> = std::cell::RefCell::new(std::collections::HashMap::new()); -} - -// ── CLI definition ──────────────────────────────────────────────────────────── - -#[derive(Parser, Debug)] -#[command( - name = "el", - about = "The Engram programming language compiler and toolchain", - version -)] -struct Cli { - #[command(subcommand)] - command: Command, -} - -#[derive(Subcommand, Debug)] -enum Command { - // ── Project management ──────────────────────────────────────────────────── - - /// Scaffold a new Engram project with manifest.el and src/main.el. - New { - /// Project name. - name: String, - /// Directory to create the project in (default: /). - #[arg(long, short = 'd')] - dir: Option, - }, - - /// Add a dependency to manifest.el. - Add { - /// Package name, optionally with version: `engram-http@1.2`. - package: String, - /// Path dependency: `--path ../my-lib`. - #[arg(long)] - path: Option, - }, - - /// Remove a dependency from manifest.el. - Remove { - /// Package name. - package: String, - }, - - /// Update all dependencies to the latest compatible versions. - Update, - - // ── Build & run ─────────────────────────────────────────────────────────── - - /// Build the project (reads manifest.el). - Build { - /// Override the build target: debug | release | prod. - #[arg(long)] - target: Option, - /// Build for all configured cross-compilation targets. - #[arg(long)] - cross: bool, - /// Path to the project manifest (default: manifest.el in current directory). - #[arg(long)] - manifest: Option, - }, - - /// Build in debug mode and run the output. - Run { - /// Path to the project manifest (default: manifest.el). - #[arg(long)] - manifest: Option, - }, - - /// Run tests in the current project (reads manifest.el). - Test { - /// Only run tests matching this name (substring match). - filter: Option, - /// Also run e2e tests (requires ENGRAM_URL or ENGRAM_DB_PATH). - #[arg(long)] - e2e: bool, - /// Run unit AND e2e tests. - #[arg(long)] - all: bool, - /// Output format: human (default) | json | junit. - #[arg(long, default_value = "human")] - output: String, - /// Path to the project manifest (default: manifest.el). - #[arg(long)] - manifest: Option, - }, - - /// Run tests from a single .el file (no manifest.el required). - TestFile { - /// Source file containing test blocks (*.el). - file: PathBuf, - /// Only run tests matching this name (substring match). - filter: Option, - /// Also run e2e tests (requires ENGRAM_URL or ENGRAM_DB_PATH). - #[arg(long)] - e2e: bool, - /// Run unit AND e2e tests. - #[arg(long)] - all: bool, - /// Output format: human (default) | json | junit. - #[arg(long, default_value = "human")] - output: String, - }, - - /// Run an Engram source file with the step-debugger attached. - Debug { - /// Source file (*.el). - file: PathBuf, - /// Set a breakpoint at line N. - #[arg(long, value_name = "LINE")] - r#break: Option, - }, - - /// Type-check source files without producing artifacts. - Check { - #[arg(long)] - manifest: Option, - }, - - /// Format an el source file (or all files in a project). - Fmt { - /// Single source file to format (*.el). When omitted, formats the whole project. - file: Option, - /// Overwrite the file in place with canonical formatting. - #[arg(long)] - in_place: bool, - /// Exit 1 if the file is not in canonical format (useful in CI). Does not modify the file. - #[arg(long)] - check: bool, - /// Project manifest (used when no `file` is given). - #[arg(long)] - manifest: Option, - }, - - /// Lint an el source file. - Lint { - /// Source file to lint (*.el). - file: PathBuf, - /// Emit diagnostics as JSON instead of human-readable text. - #[arg(long)] - json: bool, - }, - - /// Remove build artifacts and cache. - Clean { - #[arg(long)] - manifest: Option, - }, - - // ── Registry ────────────────────────────────────────────────────────────── - - /// Publish this package to the Engram registry. - Publish { - /// Registry API key. - #[arg(long, env = "ENGRAM_REGISTRY_KEY")] - api_key: Option, - #[arg(long)] - manifest: Option, - }, - - /// Search the Engram registry. - Search { - /// Search query. - query: String, - }, - - /// Manage compiler plugins. - Plugin { - #[command(subcommand)] - action: PluginAction, - }, - - // ── Low-level / single-file ─────────────────────────────────────────────── - - /// Compile and immediately run a single .el source file (no manifest.el required). - RunFile { - /// Source file (*.el). - file: PathBuf, - /// Arguments to pass to the program. - #[arg(trailing_var_arg = true)] - args: Vec, - }, - - /// Compile a single .el source file (no manifest.el required). - BuildFile { - /// Source file (*.el). - file: PathBuf, - /// Compilation target: debug | release | prod. - #[arg(long, default_value = "debug")] - target: String, - /// Output path. - #[arg(long, short = 'o')] - output: Option, - }, - - /// Seal an existing release artifact. - Seal { - artifact: PathBuf, - #[arg(long, short = 'o')] - output: Option, - }, - - /// Unseal a sealed artifact (requires ENGRAM_SEAL_KEY). - Unseal { - artifact: PathBuf, - #[arg(long, short = 'o')] - output: Option, - }, - - // ── El VM (bytecode compile + execute) ──────────────────────────────────── - - /// Compile a .el source file to an El bytecode (.elc) container. - /// - /// Produces a binary .elc file with the ELVM magic header, suitable for - /// distribution and execution via `el exec` or the `elvm` standalone binary. - /// - /// Examples: - /// el compile main.el - /// el compile main.el -o dist/ - Compile { - /// Source file to compile (*.el). - file: PathBuf, - /// Output path: a directory (places .elc inside) or a full file path. - /// Defaults to .elc in the same directory as the source file. - #[arg(long, short = 'o')] - output: Option, - }, - - /// Execute a compiled El bytecode file (.elc) on the El VM. - /// - /// The file must have been produced by `el compile` or `el build-file`. - /// Both ELVM-container and legacy JSON-only .elc files are accepted. - /// - /// Examples: - /// el exec main.elc - /// el exec dist/myapp.elc - Exec { - /// Compiled bytecode file (*.elc). - file: PathBuf, - /// Arguments to forward to the program (available via `args()`). - #[arg(trailing_var_arg = true)] - args: Vec, - }, -} - -#[derive(Subcommand, Debug)] -enum PluginAction { - /// Add a compiler plugin to manifest.el. - Add { - /// Plugin name, optionally with version: `el-fmt@1.0`. - plugin: String, - }, - /// Remove a compiler plugin from manifest.el. - Remove { - plugin: String, - }, - /// List installed plugins. - List, -} - -// ── Entry point ─────────────────────────────────────────────────────────────── - -#[tokio::main] -async fn main() { - let cli = Cli::parse(); - if let Err(e) = run(cli).await { - eprintln!("error: {e}"); - std::process::exit(1); - } -} - -async fn run(cli: Cli) -> Result<(), Box> { - match cli.command { - // ── Project management ──────────────────────────────────────────────── - - Command::New { name, dir } => { - cmd_new(&name, dir.as_deref())?; - } - - Command::Add { package, path } => { - cmd_add(&package, path.as_deref())?; - } - - Command::Remove { package } => { - cmd_remove(&package)?; - } - - Command::Update => { - println!("update: checking for newer dependency versions..."); - println!("(registry not yet live — no updates available)"); - } - - // ── Build & run ─────────────────────────────────────────────────────── - - Command::Build { target, cross, manifest } => { - let manifest_path = resolve_manifest(manifest.as_deref())?; - let bs = BuildSystem::from_manifest_file(&manifest_path)?; - - if cross { - let results = bs.build_all_targets().await?; - for (ct, out) in &results { - println!( - "built {} ({}) -> {} [{} bytes, {}ms]", - ct, - out.target, - out.artifact_path.display(), - out.size_bytes, - out.compile_time_ms, - ); - } - println!("cross: {} target(s) built", results.len()); - } else { - let build_target = target.as_deref().map(parse_build_target).transpose()?; - let out = bs.build(build_target).await?; - println!( - "built {} -> {} [{} bytes, {}ms, sealed={}]", - bs.manifest.package.name, - out.artifact_path.display(), - out.size_bytes, - out.compile_time_ms, - out.sealed, - ); - } - } - - Command::Run { manifest } => { - let manifest_path = resolve_manifest(manifest.as_deref())?; - let bs = BuildSystem::from_manifest_file(&manifest_path)?; - - // Initialise telemetry with the package name as service name. - let svc_name = std::env::var("OTEL_SERVICE_NAME") - .unwrap_or_else(|_| bs.manifest.package.name.clone()); - telemetry::set_service_name(&svc_name); - telemetry::init(&svc_name); - - // Resolve entry source (with imports). - let entry_path = manifest_path - .parent() - .unwrap_or(std::path::Path::new(".")) - .join(&bs.manifest.build.entry); - let entry_source = resolve_imports(&entry_path) - .map_err(|e| format!("cannot resolve imports for {}: {e}", entry_path.display()))?; - - // Parse and apply `app` block before running. - maybe_apply_app_block(&entry_source); - - // Strip `app { ... }` before compilation — the compiler cannot parse - // the declaration syntax; config/secrets are already extracted above. - let compile_source = strip_app_block(&entry_source); - - // Compile via Rust compiler. - let opts = CompilerOptions { - target: Target::Debug, - ..Default::default() - }; - let compiled = Compiler::compile(&compile_source, opts) - .map_err(|e| format!("compile error: {e}"))?; - let instructions = el_compiler::Bytecode::deserialize_all(&compiled.artifact) - .unwrap_or_default(); - // block_in_place lets the interpreter use reqwest::blocking from within tokio::main - tokio::task::block_in_place(|| run_interpreter(&instructions)); - } - - Command::Test { filter, e2e, all, output, manifest } => { - let manifest_path = resolve_manifest(manifest.as_deref())?; - let bs = BuildSystem::from_manifest_file(&manifest_path)?; - - // Find all .el source files in the project - let entry = bs.manifest.build.entry.clone(); - let entry_path = manifest_path.parent().unwrap_or(std::path::Path::new(".")).join(&entry); - let source = std::fs::read_to_string(&entry_path) - .map_err(|e| format!("cannot read {}: {e}", entry_path.display()))?; - - run_tests_from_source(&source, filter.as_deref(), e2e, all, &output)?; - } - - Command::TestFile { file, filter, e2e, all, output } => { - let source = std::fs::read_to_string(&file) - .map_err(|e| format!("cannot read {}: {e}", file.display()))?; - run_tests_from_source(&source, filter.as_deref(), e2e, all, &output)?; - } - - Command::Debug { file, r#break } => { - 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(); - - let mut debugger = el_compiler::Debugger::new(); - if let Some(line) = r#break { - // Convert line number to a bytecode offset approximation. - // In a full implementation this would use the source map. - // For now we use the line number directly as a placeholder offset. - debugger.add_breakpoint(line as usize); - println!("debugger: breakpoint set at line {line}"); - } else { - println!("debugger: breaking on first instruction"); - } - - println!("debugger: running {} ({} instructions)", file.display(), instructions.len()); - run_interpreter_debug(&instructions, &mut debugger); - } - - Command::Check { manifest } => { - let manifest_path = resolve_manifest(manifest.as_deref())?; - let bs = BuildSystem::from_manifest_file(&manifest_path)?; - let diags = bs.check()?; - if diags.is_empty() { - println!("check: ok"); - } else { - for d in &diags { - eprintln!("warning: {d}"); - } - } - } - - Command::Fmt { file, in_place, check, manifest } => { - if let Some(path) = file { - // Single-file mode - let source = std::fs::read_to_string(&path) - .map_err(|e| format!("cannot read {}: {e}", path.display()))?; - let formatted = el_fmt::format(&source) - .map_err(|e| format!("fmt error: {e}"))?; - if check { - if formatted != source { - eprintln!("error: {} is not in canonical format", path.display()); - std::process::exit(1); - } - println!("ok: {} is already formatted", path.display()); - } else if in_place { - std::fs::write(&path, &formatted) - .map_err(|e| format!("cannot write {}: {e}", path.display()))?; - println!("formatted: {}", path.display()); - } else { - print!("{formatted}"); - } - } else { - // Project mode — fall back to build system's fmt - let manifest_path = resolve_manifest(manifest.as_deref())?; - let bs = BuildSystem::from_manifest_file(&manifest_path)?; - bs.fmt()?; - } - } - - Command::Lint { file, json } => { - let source = std::fs::read_to_string(&file) - .map_err(|e| format!("cannot read {}: {e}", file.display()))?; - let mut report = el_lint::lint(&source) - .map_err(|e| format!("lint error: {e}"))?; - report.file_path = Some(file.display().to_string()); - if json { - println!("{}", report.to_json()); - } else { - print!("{}", report.display()); - } - if report.has_errors() { - std::process::exit(1); - } - } - - Command::Clean { manifest } => { - let manifest_path = resolve_manifest(manifest.as_deref())?; - let bs = BuildSystem::from_manifest_file(&manifest_path)?; - bs.clean()?; - } - - // ── Registry ────────────────────────────────────────────────────────── - - Command::Publish { api_key, manifest } => { - let key = api_key.ok_or("publish requires --api-key or ENGRAM_REGISTRY_KEY env var")?; - let manifest_path = resolve_manifest(manifest.as_deref())?; - let bs = BuildSystem::from_manifest_file(&manifest_path)?; - // Build release artifact first - let out = bs.build(Some(BuildTarget::Release)).await?; - let client = el_registry::RegistryClient::new(); - client.publish(&bs.manifest, &out.artifact_path, &key).await?; - println!("published {} v{}", bs.manifest.package.name, bs.manifest.package.version); - } - - Command::Search { query } => { - let client = el_registry::RegistryClient::new(); - println!("searching registry for '{query}'..."); - match client.search(&query).await { - Ok(results) => { - if results.is_empty() { - println!("no results found"); - } - for pkg in &results { - println!(" {} v{} — {}", pkg.name, pkg.version, pkg.description); - } - } - Err(e) => { - eprintln!("registry unavailable (server not yet deployed): {e}"); - } - } - } - - Command::Plugin { action } => match action { - PluginAction::Add { plugin } => { - cmd_plugin_add(&plugin)?; - } - PluginAction::Remove { plugin } => { - println!("remove plugin: {plugin} (not yet implemented)"); - } - PluginAction::List => { - let manifest_path = resolve_manifest(None)?; - let m = Manifest::from_file(&manifest_path)?; - if m.plugins.is_empty() { - println!("no plugins installed"); - } - for (name, ver) in &m.plugins { - println!(" {name} = \"{ver}\""); - } - } - }, - - // ── Low-level / single-file ─────────────────────────────────────────── - - Command::RunFile { file, args } => { - // Initialise telemetry — service name from file stem or OTEL_SERVICE_NAME. - let svc_name = std::env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| { - file.file_stem() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "el-program".to_string()) - }); - telemetry::set_service_name(&svc_name); - telemetry::init(&svc_name); - - let source = resolve_imports(&file) - .map_err(|e| format!("cannot resolve imports for {}: {e}", file.display()))?; - - // Parse and apply `app` block before compilation so builtins are ready. - maybe_apply_app_block(&source); - - let opts = CompilerOptions { - target: Target::Debug, - source_path: file.clone(), - ..Default::default() - }; - let compiled = Compiler::compile(&source, opts)?; - for d in &compiled.diagnostics { - eprintln!("warning: {d}"); - } - let instructions = el_compiler::Bytecode::deserialize_all(&compiled.artifact) - .unwrap_or_default(); - // block_in_place lets the interpreter use reqwest::blocking from within tokio::main - tokio::task::block_in_place(|| run_interpreter_with_args(&instructions, &args)); - } - - Command::BuildFile { file, target, output } => { - let source = std::fs::read_to_string(&file) - .map_err(|e| format!("cannot read {}: {e}", file.display()))?; - - let compilation_target = parse_target(&target)?; - let out_path = output.unwrap_or_else(|| { - let stem = file.file_stem().unwrap_or_default().to_string_lossy(); - match &compilation_target { - Target::Debug | Target::Release => PathBuf::from(format!("{stem}.elc")), - Target::Prod => PathBuf::from(format!("{stem}.sealed")), - } - }); - - let seal_config = build_seal_config()?; - let opts = CompilerOptions { - target: compilation_target, - output_path: out_path.clone(), - source_path: file.clone(), - engram_db_path: None, - seal_config, - }; - - let output = Compiler::compile(&source, opts)?; - for d in &output.diagnostics { - eprintln!("warning: {d}"); - } - - std::fs::write(&out_path, &output.artifact) - .map_err(|e| format!("cannot write {}: {e}", out_path.display()))?; - - if let Some(sm) = &output.source_map { - let sm_path = out_path.with_extension("map.json"); - std::fs::write(&sm_path, sm) - .map_err(|e| format!("cannot write source map: {e}"))?; - println!("compiled {} -> {} (source map: {})", - file.display(), out_path.display(), sm_path.display()); - } else { - println!("compiled {} -> {} [sealed={}]", - file.display(), out_path.display(), output.sealed); - } - } - - Command::Seal { artifact, output } => { - let bytes = std::fs::read(&artifact) - .map_err(|e| format!("cannot read {}: {e}", artifact.display()))?; - - let out_path = output.unwrap_or_else(|| { - let mut p = artifact.clone(); - let ext = format!("{}.sealed", - p.extension().unwrap_or_default().to_string_lossy()); - p.set_extension(ext); - p - }); - - let config = build_seal_config()?; - let sealed = seal_fn(&bytes, &config)?; - let artifact_bytes = sealed.to_bytes()?; - std::fs::write(&out_path, &artifact_bytes) - .map_err(|e| format!("cannot write {}: {e}", out_path.display()))?; - println!("sealed {} -> {} ({} bytes)", - artifact.display(), out_path.display(), artifact_bytes.len()); - } - - Command::Unseal { artifact, output } => { - let bytes = std::fs::read(&artifact) - .map_err(|e| format!("cannot read {}: {e}", artifact.display()))?; - - let out_path = output.unwrap_or_else(|| artifact.with_extension("elc")); - - let sealed = SealedArtifact::from_bytes(&bytes)?; - let key_str = std::env::var("ENGRAM_SEAL_KEY").unwrap_or_default(); - let key_bytes = key_str.as_bytes(); - let plaintext = unseal_fn(&sealed, key_bytes)?; - - std::fs::write(&out_path, &plaintext) - .map_err(|e| format!("cannot write {}: {e}", out_path.display()))?; - println!("unsealed {} -> {} ({} bytes)", - artifact.display(), out_path.display(), plaintext.len()); - } - - // ── El VM: compile ──────────────────────────────────────────────────── - - Command::Compile { file, output } => { - // Resolve imports into a single pre-resolved source string. - let source = resolve_imports(&file) - .map_err(|e| format!("cannot resolve imports for {}: {e}", file.display()))?; - - // Compile via the El self-compiler running on elvm. - let elvm_bytes = compile_via_el_self_compiler(&source, &file)?; - - // Determine output path. - let out_path = resolve_compile_output(&file, output.as_deref())?; - - std::fs::write(&out_path, &elvm_bytes) - .map_err(|e| format!("cannot write {}: {e}", out_path.display()))?; - - println!( - "compile: {} -> {} ({} bytes, ELVM v{})", - file.display(), - out_path.display(), - elvm_bytes.len(), - el_compiler::ELVM_VERSION, - ); - } - - // ── El VM: exec ─────────────────────────────────────────────────────── - - Command::Exec { file, args } => { - // Initialise telemetry from the file stem. - let svc_name = std::env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| { - file.file_stem() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "el-program".to_string()) - }); - telemetry::set_service_name(&svc_name); - telemetry::init(&svc_name); - - let bytes = std::fs::read(&file) - .map_err(|e| format!("cannot read {}: {e}", file.display()))?; - - let instructions = el_compiler::Bytecode::deserialize_all(&bytes) - .map_err(|e| format!("cannot load bytecode from {}: {e}", file.display()))?; - - // block_in_place lets the interpreter use reqwest::blocking from within tokio::main - tokio::task::block_in_place(|| run_interpreter_with_args(&instructions, &args)); - } - } - Ok(()) -} - -/// Resolve the output path for `el compile`. -/// -/// - If `output` is a directory → place `.elc` inside it. -/// - If `output` is a full file path → use it directly. -/// - If `output` is None → use `.elc` next to the source file. -fn resolve_compile_output( - source: &std::path::Path, - output: Option<&std::path::Path>, -) -> Result> { - let stem = source.file_stem().unwrap_or_default().to_string_lossy(); - let elc_name = format!("{stem}.elc"); - - match output { - None => { - // Same directory as source. - let dir = source.parent().unwrap_or(std::path::Path::new(".")); - Ok(dir.join(elc_name)) - } - Some(p) => { - if p.is_dir() || p.to_string_lossy().ends_with('/') { - Ok(p.join(elc_name)) - } else { - Ok(p.to_path_buf()) - } - } - } -} - -// ── El self-compiler (elvm-based) ───────────────────────────────────────────── - -/// Find the `elvm` binary. -/// -/// Search order: -/// 1. EL_ELVM_PATH env var (override for development) -/// 2. Same directory as the running `el` binary -/// 3. PATH -fn find_elvm_binary() -> Option { - // 1. Explicit override - if let Ok(p) = std::env::var("EL_ELVM_PATH") { - let path = PathBuf::from(p); - if path.exists() { return Some(path); } - } - - // 2. Sibling to current executable - if let Ok(exe) = std::env::current_exe() { - if let Some(dir) = exe.parent() { - let sibling = dir.join("elvm"); - if sibling.exists() { return Some(sibling); } - } - } - - // 3. PATH - which_binary("elvm") -} - -/// Find the `el-compiler.elc` bootstrap bytecode. -/// -/// Search order: -/// 1. EL_COMPILER_ELC env var (override for development) -/// 2. Same directory as the running `el` binary -/// 3. `/../lib/el/el-compiler.elc` (installed layout) -fn find_el_compiler_elc() -> Option { - // 1. Explicit override - if let Ok(p) = std::env::var("EL_COMPILER_ELC") { - let path = PathBuf::from(p); - if path.exists() { return Some(path); } - } - - // 2. Sibling to current executable - if let Ok(exe) = std::env::current_exe() { - if let Some(dir) = exe.parent() { - let sibling = dir.join("el-compiler.elc"); - if sibling.exists() { return Some(sibling); } - } - } - - // 3. Installed layout: /lib/el/el-compiler.elc - if let Ok(exe) = std::env::current_exe() { - if let Some(dir) = exe.parent() { - if let Some(prefix) = dir.parent() { - let installed = prefix.join("lib").join("el").join("el-compiler.elc"); - if installed.exists() { return Some(installed); } - } - } - } - - None -} - -/// Find a binary on PATH. -fn which_binary(name: &str) -> Option { - let path_var = std::env::var("PATH").unwrap_or_default(); - for dir in path_var.split(':') { - let candidate = PathBuf::from(dir).join(name); - if candidate.exists() { - return Some(candidate); - } - } - None -} - -/// Compile El source via the El self-compiler running on elvm. -/// -/// Writes pre-resolved source to a temp file, invokes: -/// `elvm ` -/// then reads the JSON bytecode output and wraps it in an ELVM binary container. -/// -/// Returns the raw `.elc` bytes (ELVM header + JSON payload). -fn compile_via_el_self_compiler( - source: &str, - source_path: &std::path::Path, -) -> Result, Box> { - let elvm = find_elvm_binary() - .ok_or("cannot find elvm binary (set EL_ELVM_PATH or place elvm next to el)")?; - let compiler_elc = find_el_compiler_elc() - .ok_or("cannot find el-compiler.elc (set EL_COMPILER_ELC or place el-compiler.elc next to el)")?; - - // Write pre-resolved source to a temp file. - let tmp_dir = std::env::temp_dir(); - let stem = source_path.file_stem().unwrap_or_default().to_string_lossy(); - let tmp_src = tmp_dir.join(format!("el-compile-{stem}-{}.el", std::process::id())); - let tmp_out = tmp_dir.join(format!("el-compile-{stem}-{}.json", std::process::id())); - - std::fs::write(&tmp_src, source) - .map_err(|e| format!("cannot write temp source: {e}"))?; - - // Invoke: elvm - let status = std::process::Command::new(&elvm) - .arg(&compiler_elc) - .arg(&tmp_src) - .arg(&tmp_out) - .status() - .map_err(|e| format!("cannot spawn elvm: {e}"))?; - - // Clean up temp source regardless of outcome. - let _ = std::fs::remove_file(&tmp_src); - - if !status.success() { - let _ = std::fs::remove_file(&tmp_out); - return Err(format!( - "el-compiler exited with status {}", - status.code().unwrap_or(-1) - ).into()); - } - - // Read JSON bytecode output. - let json_bytes = std::fs::read(&tmp_out) - .map_err(|e| format!("cannot read compiler output {}: {e}", tmp_out.display()))?; - let _ = std::fs::remove_file(&tmp_out); - - // Parse JSON and wrap in ELVM binary container. - let instructions = el_compiler::Bytecode::deserialize_all(&json_bytes) - .map_err(|e| format!("invalid bytecode from El compiler: {e}"))?; - let elvm_bytes = el_compiler::wrap_elvm(&instructions) - .map_err(|e| format!("ELVM wrap failed: {e}"))?; - - Ok(elvm_bytes) -} - -// ── Command implementations ─────────────────────────────────────────────────── - -fn cmd_new(name: &str, dir: Option<&std::path::Path>) -> Result<(), Box> { - let project_dir = dir - .map(|d| d.to_path_buf()) - .unwrap_or_else(|| PathBuf::from(name)); - - if project_dir.exists() { - return Err(format!("directory '{}' already exists", project_dir.display()).into()); - } - - std::fs::create_dir_all(project_dir.join("src"))?; - - // Write manifest.el - let manifest_content = format!( - r#"package "{name}" {{ - version "0.1.0" - description "" - authors [] - license "MIT" - edition "2026" -}} - -build {{ - target "debug" - entry "src/main.el" - output "dist/" -}} -"# - ); - std::fs::write(project_dir.join("manifest.el"), manifest_content)?; - - // Write src/main.el - let main_el = format!( - r#"// {name} — entry point - -fn main() -> Void {{ - let msg: String = "Hello from {name}!" - println(msg) -}} -"# - ); - std::fs::write(project_dir.join("src").join("main.el"), main_el)?; - - // Write .gitignore - std::fs::write(project_dir.join(".gitignore"), "dist/\n.el/\n")?; - - println!("created project '{name}' in {}/", project_dir.display()); - println!(" manifest.el"); - println!(" src/main.el"); - Ok(()) -} - -fn cmd_add(package: &str, path: Option<&std::path::Path>) -> Result<(), Box> { - let manifest_path = resolve_manifest(None)?; - let mut manifest_text = std::fs::read_to_string(&manifest_path)?; - - let dep_line = if let Some(p) = path { - format!( - r#"{package} = {{ path = "{}" }}"#, - p.display() - ) - } else { - // Parse name@version - let (pkg_name, version) = if let Some((n, v)) = package.split_once('@') { - (n, v.to_string()) - } else { - (package, "*".to_string()) - }; - format!(r#"{pkg_name} = "{version}""#) - }; - - // Append to [dependencies] section - if let Some(idx) = manifest_text.find("[dependencies]") { - // Find next section or end - let after = &manifest_text[idx + "[dependencies]".len()..]; - let insert_pos = after - .find("\n[") - .map(|i| idx + "[dependencies]".len() + i) - .unwrap_or(manifest_text.len()); - manifest_text.insert_str(insert_pos, &format!("\n{dep_line}")); - } else { - manifest_text.push_str(&format!("\n[dependencies]\n{dep_line}\n")); - } - - std::fs::write(&manifest_path, &manifest_text)?; - println!("added: {dep_line}"); - Ok(()) -} - -fn cmd_remove(package: &str) -> Result<(), Box> { - let manifest_path = resolve_manifest(None)?; - let manifest_text = std::fs::read_to_string(&manifest_path)?; - - // Remove the line containing `package = ...` from [dependencies] - let updated: String = manifest_text - .lines() - .filter(|line| { - let trimmed = line.trim(); - !trimmed.starts_with(package) - || !trimmed[package.len()..].trim_start().starts_with('=') - }) - .collect::>() - .join("\n"); - - // Preserve trailing newline - let updated = if manifest_text.ends_with('\n') { - format!("{updated}\n") - } else { - updated - }; - - std::fs::write(&manifest_path, &updated)?; - println!("removed: {package}"); - Ok(()) -} - -fn cmd_plugin_add(plugin: &str) -> Result<(), Box> { - let manifest_path = resolve_manifest(None)?; - let mut manifest_text = std::fs::read_to_string(&manifest_path)?; - - let (plugin_name, version) = if let Some((n, v)) = plugin.split_once('@') { - (n.to_string(), v.to_string()) - } else { - (plugin.to_string(), "*".to_string()) - }; - - let plugin_line = format!(r#"{plugin_name} = "{version}""#); - - if let Some(idx) = manifest_text.find("[plugins]") { - let after = &manifest_text[idx + "[plugins]".len()..]; - let insert_pos = after - .find("\n[") - .map(|i| idx + "[plugins]".len() + i) - .unwrap_or(manifest_text.len()); - manifest_text.insert_str(insert_pos, &format!("\n{plugin_line}")); - } else { - manifest_text.push_str(&format!("\n[plugins]\n{plugin_line}\n")); - } - - std::fs::write(&manifest_path, &manifest_text)?; - println!("added plugin: {plugin_line}"); - Ok(()) -} - -// ── Helpers ─────────────────────────────────────────────────────────────────── - -/// Resolve `import "path.el"` directives by reading and concatenating source files. -/// Imports are resolved relative to the directory of the file being imported from. -/// Circular imports are detected via a visited set. -fn resolve_imports(file: &std::path::Path) -> Result> { - let mut visited = std::collections::HashSet::new(); - resolve_imports_inner(file, &mut visited) -} - -fn resolve_imports_inner( - file: &std::path::Path, - visited: &mut std::collections::HashSet, -) -> Result> { - let canonical = file.canonicalize() - .unwrap_or_else(|_| file.to_path_buf()); - - if visited.contains(&canonical) { - return Ok(String::new()); // circular — skip - } - visited.insert(canonical.clone()); - - let dir = file.parent().unwrap_or(std::path::Path::new(".")); - let source = std::fs::read_to_string(file) - .map_err(|e| format!("cannot read {}: {e}", file.display()))?; - - let mut out = String::new(); - let mut lines_iter = source.lines().peekable(); - - while let Some(line) = lines_iter.next() { - let trimmed = line.trim(); - if let Some(rest) = trimmed.strip_prefix("import ") { - // import "filename.el" - let rest = rest.trim(); - if rest.starts_with('"') && rest.ends_with('"') { - let import_path_str = &rest[1..rest.len() - 1]; - let import_path = dir.join(import_path_str); - let imported = resolve_imports_inner(&import_path, visited)?; - out.push_str(&imported); - out.push('\n'); - } else { - out.push_str(line); - out.push('\n'); - } - } else if let Some(rest) = trimmed.strip_prefix("from ") { - // from ModuleName import { ... } — resolve ModuleName.el in the same dir - if let Some(module_part) = rest.split(" import").next() { - let module_name = module_part.trim(); - if !module_name.contains('"') && !module_name.contains('/') && !module_name.is_empty() { - let module_file = format!("{}.el", module_name); - let import_path = dir.join(&module_file); - - // Consume any continuation lines of a multi-line import: - // `from X import {\n Y,\n Z,\n}` - let import_rest = rest.splitn(2, " import").nth(1).unwrap_or("").trim(); - let is_multiline = import_rest.contains('{') && !import_rest.contains('}'); - if is_multiline { - for cont in lines_iter.by_ref() { - if cont.trim().contains('}') { - break; - } - } - } - - if import_path.exists() { - let imported = resolve_imports_inner(&import_path, visited)?; - out.push_str(&imported); - out.push('\n'); - } else { - out.push_str(line); - out.push('\n'); - } - } else { - out.push_str(line); - out.push('\n'); - } - } else { - out.push_str(line); - out.push('\n'); - } - } else { - out.push_str(line); - out.push('\n'); - } - } - - Ok(out) -} - -/// Find the nearest `manifest.el` starting from the current directory. -fn resolve_manifest(path: Option<&std::path::Path>) -> Result> { - if let Some(p) = path { - return Ok(p.to_path_buf()); - } - let cwd = std::env::current_dir()?; - Manifest::find_manifest(&cwd).map_err(|e| e.into()) -} - -fn parse_build_target(s: &str) -> Result> { - s.parse::().map_err(|e| e.into()) -} - -fn parse_target(s: &str) -> Result { - match s { - "debug" => Ok(Target::Debug), - "release" => Ok(Target::Release), - "prod" => Ok(Target::Prod), - other => Err(format!("unknown target '{other}': use debug, release, or prod")), - } -} - -fn build_seal_config() -> Result { - Ok(SealConfig { - algorithm: SealAlgorithm::Aes256Gcm, - deployment_binding: if std::env::var("ENGRAM_SEAL_KEY").is_ok() { - DeploymentBinding::EnvironmentKey("ENGRAM_SEAL_KEY".into()) - } else { - DeploymentBinding::None - }, - }) -} - -/// Discover tests in source, filter, run, and print results. -fn run_tests_from_source( - source: &str, - filter: Option<&str>, - e2e: bool, - all: bool, - output_fmt: &str, -) -> Result<(), Box> { - use el_test::{TestReport, TestRunner}; - - let mut tests = el_test::discover(source)?; - - // Apply name filter - if let Some(f) = filter { - tests.retain(|t| t.name.contains(f)); - } - - if tests.is_empty() { - println!("no tests found"); - return Ok(()); - } - - let engram_url = std::env::var("ENGRAM_URL").ok().or_else(|| std::env::var("ENGRAM_DB_PATH").ok()); - let url_ref = engram_url.as_deref(); - - let runner = TestRunner::new(); - let results = if all { - runner.run_all(&tests, url_ref) - } else if e2e { - runner.run_e2e(&tests, url_ref.unwrap_or("")) - } else { - runner.run_unit(&tests) - }; - - let report = TestReport::from_results(results); - - match output_fmt { - "json" => println!("{}", report.to_json()), - "junit" => println!("{}", report.to_junit_xml()), - _ => report.print(), - } - - if !report.is_pass() { - std::process::exit(1); - } - - Ok(()) -} - -/// Minimal interpreter for demonstration (no program args). -fn run_interpreter(instructions: &[el_compiler::Bytecode]) { - run_interpreter_with_args(instructions, &[]); -} - -// ── JSON / Value conversion helpers ────────────────────────────────────────── - -/// Convert a serde_json Value to an el Value. -fn json_value_to_el_value(v: &serde_json::Value) -> el_compiler::Value { - use el_compiler::Value; - match v { - serde_json::Value::String(s) => Value::Str(s.clone()), - serde_json::Value::Number(n) => { - if let Some(i) = n.as_i64() { - Value::Int(i) - } else if let Some(f) = n.as_f64() { - Value::Float(f) - } else { - Value::Str(n.to_string()) - } - } - serde_json::Value::Bool(b) => Value::Bool(*b), - serde_json::Value::Null => Value::Nil, - serde_json::Value::Array(arr) => { - Value::List(arr.iter().map(json_value_to_el_value).collect()) - } - serde_json::Value::Object(obj) => { - let fields = obj.iter() - .map(|(k, v)| (k.clone(), json_value_to_el_value(v))) - .collect(); - Value::Struct { type_name: "Object".to_string(), fields } - } - } -} - -/// Convert an el Value to a serde_json Value. -fn el_value_to_json_value(v: &el_compiler::Value) -> serde_json::Value { - use el_compiler::Value; - match v { - Value::Int(n) => serde_json::Value::Number((*n).into()), - Value::Float(f) => serde_json::Number::from_f64(*f) - .map(serde_json::Value::Number) - .unwrap_or(serde_json::Value::Null), - Value::Str(s) => serde_json::Value::String(s.clone()), - Value::Bool(b) => serde_json::Value::Bool(*b), - Value::Nil => serde_json::Value::Null, - Value::List(items) => { - serde_json::Value::Array(items.iter().map(el_value_to_json_value).collect()) - } - Value::Map(pairs) => { - let mut map = serde_json::Map::new(); - for (k, v) in pairs { - map.insert(k.clone(), el_value_to_json_value(v)); - } - serde_json::Value::Object(map) - } - Value::ResultOk(inner) => { - serde_json::json!({"ok": el_value_to_json_value(inner)}) - } - Value::ResultErr(inner) => { - serde_json::json!({"err": el_value_to_json_value(inner)}) - } - Value::Struct { fields, .. } => { - let mut map = serde_json::Map::new(); - for (k, v) in fields { - map.insert(k.clone(), el_value_to_json_value(v)); - } - serde_json::Value::Object(map) - } - } -} - -/// Call the Engram /search endpoint and return results as a Vec of el Values. -fn engram_activate_search(type_name: &str, query: &str) -> Vec { - use el_compiler::Value; - let engram_url = std::env::var("ENGRAM_URL") - .unwrap_or_else(|_| "http://localhost:8742".to_string()); - let api_key = std::env::var("ENGRAM_API_KEY").unwrap_or_default(); - - let body = serde_json::json!({ - "query": query, - "limit": 20 - }) - .to_string(); - - let client = reqwest::blocking::Client::new(); - let mut req = client - .post(format!("{engram_url}/search")) - .header("Content-Type", "application/json") - .body(body); - - if !api_key.is_empty() { - req = req.header("Authorization", format!("Bearer {api_key}")); - } - - let result = req - .send() - .and_then(|r| r.json::()) - .ok(); - - let results: Vec = result - .and_then(|v| v.as_array().cloned()) - .unwrap_or_default() - .iter() - .map(json_value_to_el_value) - .collect(); - - eprintln!( - "[activate] {type_name} where \"{query}\" → {} results", - results.len() - ); - results -} - -/// Run a sub-interpreter starting at the given function entry point. -/// Returns the value left on the stack (the return value). -fn run_sub_interpreter( - instructions: &[el_compiler::Bytecode], - fn_table: &std::collections::HashMap, - entry: usize, -) -> el_compiler::Value { - run_sub_interpreter_with_stack(instructions, fn_table, entry, vec![]) -} - -fn run_sub_interpreter_with_stack( - instructions: &[el_compiler::Bytecode], - fn_table: &std::collections::HashMap, - entry: usize, - initial_stack: Vec, -) -> el_compiler::Value { - use el_compiler::{Bytecode, Value}; - - let mut stack: Vec = initial_stack; - let mut locals: std::collections::HashMap = std::collections::HashMap::new(); - let mut call_stack: Vec<(usize, std::collections::HashMap)> = Vec::new(); - let mut ip = entry; - let program_args: Vec = vec![]; - - while ip < instructions.len() { - match &instructions[ip] { - Bytecode::Push(v) => stack.push(v.clone()), - Bytecode::Pop => { stack.pop(); } - Bytecode::Dup => { - if let Some(top) = stack.last().cloned() { stack.push(top); } - } - Bytecode::Add => { - 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::Int(x + y), - (Value::Float(x), Value::Float(y)) => Value::Float(x + y), - (Value::Str(x), Value::Str(y)) => Value::Str(x + &y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 + y), - (Value::Float(x), Value::Int(y)) => Value::Float(x + y as f64), - _ => Value::Nil, - }); - } - Bytecode::Sub => { - 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::Int(x - y), - (Value::Float(x), Value::Float(y)) => Value::Float(x - y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 - y), - (Value::Float(x), Value::Int(y)) => Value::Float(x - y as f64), - _ => Value::Nil, - }); - } - Bytecode::Mul => { - 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::Int(x * y), - (Value::Float(x), Value::Float(y)) => Value::Float(x * y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 * y), - (Value::Float(x), Value::Int(y)) => Value::Float(x * y as f64), - _ => Value::Nil, - }); - } - Bytecode::Div => { - 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)) if y != 0 => Value::Int(x / y), - (Value::Float(x), Value::Float(y)) => Value::Float(x / y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 / y), - (Value::Float(x), Value::Int(y)) => Value::Float(x / y as f64), - _ => Value::Nil, - }); - } - Bytecode::Mod => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(1)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => if y == 0 { Value::Int(0) } else { Value::Int(x % y) }, - (Value::Float(x), Value::Float(y)) => Value::Float(x % y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 % y), - (Value::Float(x), Value::Int(y)) => Value::Float(x % y as f64), - _ => Value::Int(0), - }); - } - Bytecode::BitAnd => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x & y), _ => Value::Int(0) }); - } - Bytecode::BitOr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x | y), _ => Value::Int(0) }); - } - Bytecode::BitXor => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x ^ y), _ => Value::Int(0) }); - } - Bytecode::BitNot => { - let a = stack.pop().unwrap_or(Value::Int(0)); - stack.push(match a { Value::Int(x) => Value::Int(!x), _ => Value::Int(0) }); - } - Bytecode::Shl => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x << (y as u32)), _ => Value::Int(0) }); - } - Bytecode::Shr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x >> (y as u32)), _ => Value::Int(0) }); - } - Bytecode::Eq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - 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(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Less)); - } - Bytecode::Gt => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Greater)); - } - Bytecode::LtEq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Greater)); - } - Bytecode::GtEq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Less)); - } - 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 => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(!matches!(v, Value::Bool(true)))); - } - Bytecode::StoreLocal(name) => { - let v = stack.pop().unwrap_or(Value::Nil); - locals.insert(name.clone(), v); - } - Bytecode::LoadLocal(name) => { - let v = locals.get(name).cloned().unwrap_or(Value::Nil); - stack.push(v); - } - Bytecode::Call { name, arity } => { - eprintln!("[DEBUG sub] Call {name} arity={arity} stack_top={:?}", stack.last()); - let result = dispatch_builtin(name, *arity, &mut stack, &program_args); - eprintln!("[DEBUG sub] dispatch_builtin({name}) -> {:?} stack_top={:?}", matches!(result, BuiltinResult::Handled), stack.last()); - match result { - BuiltinResult::Handled | BuiltinResult::HttpServe => {} - BuiltinResult::Exit(code) => std::process::exit(code), - BuiltinResult::NotBuiltin => { - eprintln!("[DEBUG sub] NotBuiltin for {name}, fn_table has: {:?}", fn_table.keys().collect::>()); - // HOF dispatch in sub-interpreter - match name.as_str() { - "map" | "list_map" => { - let fn_ref = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::List(vec![])) { - Value::List(l) => l, - _ => vec![], - }; - let fn_name = match &fn_ref { Value::Str(s) => s.clone(), _ => String::new() }; - let mut result = Vec::new(); - if let Some(&entry) = fn_table.get(&fn_name) { - for item in list { - let mapped = run_sub_interpreter_with_stack(instructions, fn_table, entry, vec![item]); - result.push(mapped); - } - } - stack.push(Value::List(result)); - } - _ => { - if let Some(&entry) = fn_table.get(name.as_str()) { - let saved = locals.clone(); - call_stack.push((ip + 1, saved)); - ip = entry; - continue; - } - stack.push(Value::Nil); - } - } - } - } - } - Bytecode::GetField(field) => { - let obj = stack.pop().unwrap_or(Value::Nil); - let v = match &obj { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - Value::Struct { fields, .. } => fields.iter() - .find(|(n, _)| n == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - _ => Value::Nil, - }; - stack.push(v); - } - 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::Map(pairs), Value::Str(key)) => { - let v = pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil); - stack.push(v); - } - _ => stack.push(Value::Nil), - } - } - Bytecode::BuildMap(n) => { - let mut pairs = Vec::new(); - for _ in 0..*n { - let val = stack.pop().unwrap_or(Value::Nil); - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - pairs.push((key, val)); - } - pairs.reverse(); - stack.push(Value::Map(pairs)); - } - Bytecode::BuildStruct { type_name, fields } => { - let n = fields.len(); - let mut field_values: Vec = (0..n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect(); - field_values.reverse(); - let struct_fields: Vec<(String, Value)> = fields.iter().cloned() - .zip(field_values.into_iter()) - .collect(); - stack.push(Value::Struct { - type_name: type_name.clone(), - fields: struct_fields, - }); - } - Bytecode::SetField(field) => { - let val = stack.pop().unwrap_or(Value::Nil); - if let Some(Value::Map(pairs)) = stack.last_mut() { - if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - pairs.push((field.clone(), val)); - } - } else if let Some(Value::Struct { fields, .. }) = stack.last_mut() { - if let Some(entry) = fields.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - fields.push((field.clone(), val)); - } - } - } - Bytecode::Jump(offset) => { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - Bytecode::JumpIf(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if matches!(cond, Value::Bool(true)) { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - } - Bytecode::JumpIfNot(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - } - Bytecode::Return => { - if call_stack.is_empty() { - // Return from handle_request — value is on stack - break; - } - if let Some((ret_ip, saved_locals)) = call_stack.pop() { - locals = saved_locals; - ip = ret_ip; - continue; - } - } - Bytecode::Halt => break, - Bytecode::Activate { type_name, query } => { - let results = engram_activate_search(type_name, query); - stack.push(Value::List(results)); - } - _ => {} - } - ip += 1; - } - - stack.pop().unwrap_or(Value::Nil) -} - -/// 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}; - let mut stack: Vec = Vec::new(); - let mut locals: std::collections::HashMap = std::collections::HashMap::new(); - let mut ip = 0usize; - - // Build a call table: fn name → bytecode offset. - // We populate this by scanning for __fn_ stores first. - let mut fn_table: std::collections::HashMap = 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 = 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; - } - } - - // Store instructions and fn_table in thread-locals so http_serve can call - // handle_request via a sub-interpreter invocation. - let arc_instructions = std::sync::Arc::new(instructions.to_vec()); - let arc_fn_table = std::sync::Arc::new(fn_table.clone()); - let arc_instructions_clone = arc_instructions.clone(); - let arc_fn_table_clone = arc_fn_table.clone(); - - SERVE_INSTRUCTIONS.with(|si| *si.borrow_mut() = Some(arc_instructions.clone())); - SERVE_FN_TABLE.with(|sf| *sf.borrow_mut() = Some(arc_fn_table.clone())); - - // Set up the http_serve callback that calls handle_request(method, path, body) - HTTP_SERVE_CALL.with(|f| { - *f.borrow_mut() = Some(Box::new(move || { - // Load method, path, body from global state (set by http_serve before calling) - let (method, path, body) = GLOBAL_STATE.with(|gs| { - let s = gs.borrow(); - ( - s.get("__method__").cloned().unwrap_or_default(), - s.get("__path__").cloned().unwrap_or_default(), - s.get("__request__").cloned().unwrap_or_default(), - ) - }); - - // Run a sub-interpreter starting at handle_request with args on stack - if let Some(entry) = arc_fn_table_clone.get("handle_request") { - // Push args in order: method, path, body (they'll be stored via StoreLocal params) - let initial_stack = vec![ - el_compiler::Value::Str(method), - el_compiler::Value::Str(path), - el_compiler::Value::Str(body), - ]; - let result = run_sub_interpreter_with_stack( - &arc_instructions_clone, - &arc_fn_table_clone, - *entry, - initial_stack, - ); - // Store the result as __response__ in global state - let response = match result { - el_compiler::Value::Str(s) => s, - other => other.to_string(), - }; - GLOBAL_STATE.with(|gs| { - gs.borrow_mut().insert("__response__".to_string(), response); - }); - } else { - GLOBAL_STATE.with(|gs| { - gs.borrow_mut().insert( - "__response__".to_string(), - r#"{"error":"handle_request function not found"}"#.to_string(), - ); - }); - } - })); - }); - - // Call stack for user-defined function calls: (return_ip, saved_locals) - let mut call_stack: Vec<(usize, std::collections::HashMap)> = Vec::new(); - - while ip < instructions.len() { - match &instructions[ip] { - Bytecode::Push(v) => stack.push(v.clone()), - Bytecode::Pop => { stack.pop(); } - Bytecode::Dup => { - if let Some(top) = stack.last().cloned() { stack.push(top); } - } - Bytecode::Add => { - 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::Int(x + y), - (Value::Float(x), Value::Float(y)) => Value::Float(x + y), - (Value::Str(x), Value::Str(y)) => Value::Str(x + &y), - _ => Value::Nil, - }); - } - Bytecode::Sub => { - 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::Int(x - y), - (Value::Float(x), Value::Float(y)) => Value::Float(x - y), - _ => Value::Nil, - }); - } - Bytecode::Mul => { - 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::Int(x * y), - (Value::Float(x), Value::Float(y)) => Value::Float(x * y), - _ => Value::Nil, - }); - } - Bytecode::Div => { - 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)) if y != 0 => Value::Int(x / y), - (Value::Float(x), Value::Float(y)) => Value::Float(x / y), - _ => Value::Nil, - }); - } - Bytecode::Mod => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(1)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => if y == 0 { Value::Int(0) } else { Value::Int(x % y) }, - (Value::Float(x), Value::Float(y)) => Value::Float(x % y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 % y), - (Value::Float(x), Value::Int(y)) => Value::Float(x % y as f64), - _ => Value::Int(0), - }); - } - Bytecode::BitAnd => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => Value::Int(x & y), - _ => Value::Int(0), - }); - } - Bytecode::BitOr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => Value::Int(x | y), - _ => Value::Int(0), - }); - } - Bytecode::BitXor => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => Value::Int(x ^ y), - _ => Value::Int(0), - }); - } - Bytecode::BitNot => { - let a = stack.pop().unwrap_or(Value::Int(0)); - stack.push(match a { - Value::Int(x) => Value::Int(!x), - _ => Value::Int(0), - }); - } - Bytecode::Shl => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => Value::Int(x << (y as u32)), - _ => Value::Int(0), - }); - } - Bytecode::Shr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => Value::Int(x >> (y as u32)), - _ => Value::Int(0), - }); - } - Bytecode::Eq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - 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 => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(!matches!(v, Value::Bool(true)))); - } - Bytecode::StoreLocal(name) => { - let v = stack.pop().unwrap_or(Value::Nil); - locals.insert(name.clone(), v); - } - Bytecode::LoadLocal(name) => { - let v = locals.get(name).cloned().unwrap_or(Value::Nil); - stack.push(v); - } - Bytecode::Call { name, arity } => { - let result = dispatch_builtin(name, *arity, &mut stack, program_args); - match result { - BuiltinResult::Handled | BuiltinResult::HttpServe => {} - BuiltinResult::Exit(code) => std::process::exit(code), - BuiltinResult::NotBuiltin => { - // Handle HOFs that need fn_table access - match name.as_str() { - // `list_map` or `.map(fn)` method on List - "list_map" | "map" => { - let fn_ref = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::List(vec![])) { - Value::List(l) => l, - _ => vec![], - }; - let fn_name = match &fn_ref { - Value::Str(s) => s.clone(), - _ => String::new(), - }; - let mut result = Vec::new(); - if let Some(&entry) = fn_table.get(&fn_name) { - for item in list { - let mapped = run_sub_interpreter_with_stack(instructions, &fn_table, entry, vec![item]); - result.push(mapped); - } - } else { - // fn_ref not found — just return empty - let _ = fn_ref; - } - stack.push(Value::List(result)); - } - "list_filter" => { - let fn_name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let list = match stack.pop().unwrap_or(Value::List(vec![])) { - Value::List(l) => l, - _ => vec![], - }; - let mut result = Vec::new(); - if let Some(&entry) = fn_table.get(&fn_name) { - for item in list { - let keep = run_sub_interpreter_with_stack(instructions, &fn_table, entry, vec![item.clone()]); - if matches!(keep, Value::Bool(true)) { - result.push(item); - } - } - } - stack.push(Value::List(result)); - } - "list_reduce" => { - let fn_name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let init = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::List(vec![])) { - Value::List(l) => l, - _ => vec![], - }; - let mut acc = init; - if let Some(&entry) = fn_table.get(&fn_name) { - for item in list { - acc = run_sub_interpreter_with_stack(instructions, &fn_table, entry, vec![acc, item]); - } - } - stack.push(acc); - } - "fn_ref" => { - let name_val = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Str(name_val)); - } - _ => { - // Try user-defined function - if let Some(&entry) = fn_table.get(name.as_str()) { - // Open a function span for automatic tracing. - let (_fn_trace_id, _fn_parent_id) = - telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - let _fn_span_id = telemetry::new_span_id(); - let _fn_start_ns = telemetry::now_ns(); - FN_SPAN_STACK.with(|s| s.borrow_mut().push(( - name.to_string(), - _fn_start_ns, - _fn_trace_id, - _fn_span_id, - _fn_parent_id, - ))); - // 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) => { - let obj = stack.pop().unwrap_or(Value::Nil); - let v = match obj { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - _ => Value::Nil, - }; - stack.push(v); - } - 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::Map(pairs), Value::Str(key)) => { - let v = pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| v.clone()) - .unwrap_or(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::BuildMap(n) => { - let mut pairs = Vec::new(); - for _ in 0..*n { - let val = stack.pop().unwrap_or(Value::Nil); - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - pairs.push((key, val)); - } - pairs.reverse(); - stack.push(Value::Map(pairs)); - } - Bytecode::BuildStruct { fields, .. } => { - let mut pairs = Vec::new(); - for field in fields.iter().rev() { - let val = stack.pop().unwrap_or(Value::Nil); - pairs.push((field.clone(), val)); - } - pairs.reverse(); - stack.push(Value::Map(pairs)); - } - Bytecode::SetField(field) => { - let val = stack.pop().unwrap_or(Value::Nil); - if let Some(Value::Map(pairs)) = stack.last_mut() { - if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - pairs.push((field.clone(), val)); - } - } - } - Bytecode::Activate { type_name, query } => { - let results = engram_activate_search(type_name, query); - stack.push(Value::List(results)); - } - Bytecode::Jump(offset) => { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - Bytecode::JumpIf(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if matches!(cond, Value::Bool(true)) { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - } - Bytecode::JumpIfNot(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - } - Bytecode::Return => { - // Close automatic function span (if any) before restoring frame. - FN_SPAN_STACK.with(|s| { - if let Some((fn_name, start_ns, trace_id, span_id, parent_id)) = s.borrow_mut().pop() { - let end_ns = telemetry::now_ns(); - let latency_ms = ((end_ns.saturating_sub(start_ns)) / 1_000_000) as i64; - telemetry::emit_span(telemetry::Span { - trace_id, - span_id, - parent_id, - name: fn_name, - start_ns, - end_ns, - attrs: vec![ - ("fn.latency_ms".to_string(), telemetry::AttrValue::Int(latency_ms)), - ], - events: vec![], - status: telemetry::SpanStatus::Ok, - service: telemetry::service_name().to_string(), - }); - } - }); - // 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::SealedBegin => {} - Bytecode::SealedEnd => {} - Bytecode::Nop => {} - Bytecode::Reason { query } => { - let text = soma_reason(query); - stack.push(Value::Str(text)); - } - Bytecode::Parallel { entries } => { - // Spawn one thread per entry, collect into Map - let instructions_arc = std::sync::Arc::new(instructions.to_vec()); - let fn_table_arc = std::sync::Arc::new(fn_table.clone()); - let locals_arc = std::sync::Arc::new(locals.clone()); - let mut handles: Vec<(String, std::thread::JoinHandle)> = Vec::new(); - for (name, entry_ip) in entries { - let instr_clone = instructions_arc.clone(); - let ft_clone = fn_table_arc.clone(); - let ep = *entry_ip; - let h = std::thread::spawn(move || { - run_sub_interpreter(&instr_clone, &ft_clone, ep) - }); - handles.push((name.clone(), h)); - } - let mut pairs = Vec::new(); - for (name, h) in handles { - let v = h.join().unwrap_or(Value::Nil); - pairs.push((name, v)); - } - stack.push(Value::Map(pairs)); - } - Bytecode::TraceBegin { label } => { - let start_ms = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - locals.insert(format!("__trace_start_{label}__"), Value::Int(start_ms as i64)); - } - Bytecode::TraceEnd { label } => { - let now_ms = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let start_ms = match locals.get(&format!("__trace_start_{label}__")) { - Some(Value::Int(n)) => *n as u128, - _ => now_ms, - }; - let elapsed = now_ms.saturating_sub(start_ms); - eprintln!("[trace] {label}: {elapsed}ms"); - } - Bytecode::ContractCheck { message } => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - eprintln!("contract violation: {message}"); - std::process::exit(1); - } - } - Bytecode::DeployFn { fn_name, route, target } => { - let soma_url = std::env::var("SOMA_URL") - .unwrap_or_else(|_| "https://neuron.neurontechnologies.ai".into()); - let op_key = std::env::var("SOMA_OPERATOR_KEY").unwrap_or_default(); - let body = serde_json::json!({ - "fn_name": fn_name, - "route": route, - "target": target, - }); - let resp = reqwest::blocking::Client::new() - .post(format!("{soma_url}/v1/deploy")) - .header("Authorization", format!("Bearer {op_key}")) - .json(&body) - .send() - .and_then(|r| r.text()) - .unwrap_or_else(|e| format!("{{\"error\":\"{e}\"}}")); - eprintln!("[deploy] {fn_name} -> {route} via {target}: {resp}"); - stack.push(Value::Str(resp)); - } - _ => {} - } - ip += 1; - } - - // ── Component app bootstrap ─────────────────────────────────────────────── - // If the program defined an `App` component (registered as __fn_App in fn_table), - // call it and render the result as the app UI. - if let Some(&app_entry) = fn_table.get("App").or_else(|| fn_table.get("__component_App")) { - eprintln!("[DEBUG] Found App at entry={app_entry}, fn_table keys: {:?}", fn_table.keys().collect::>()); - // Dump instructions around app_entry for debugging - for i in 0..std::cmp::min(instructions.len(), app_entry + 30) { - eprintln!("[DEBUG bytecode] [{i}] {:?}", instructions[i]); - } - let html = run_sub_interpreter(instructions, &fn_table, app_entry); - eprintln!("[DEBUG] run_sub_interpreter returned: {:?}", html); - render_component_to_terminal(&html); - } else { - eprintln!("[DEBUG] No App found. fn_table keys: {:?}", fn_table.keys().collect::>()); - } -} - -/// Render a component value to the terminal. -/// If the value is an HTML string (from __jsx__), pretty-print it. -/// Otherwise just print it. -fn render_component_to_terminal(val: &el_compiler::Value) { - use el_compiler::Value; - match val { - Value::Str(html) => { - if html.starts_with('<') { - // Strip HTML tags and print text content for terminal output - let text = strip_html_tags(html); - println!("{text}"); - } else if !html.is_empty() { - println!("{html}"); - } - } - Value::Nil => {} - other => println!("{other}"), - } -} - -/// Strip HTML tags from a string, preserving text content. -fn strip_html_tags(html: &str) -> String { - let mut result = String::new(); - let mut in_tag = false; - let mut depth = 0; - - for ch in html.chars() { - match ch { - '<' => { in_tag = true; depth += 1; } - '>' => { - depth -= 1; - if depth == 0 { in_tag = false; } - } - _ if !in_tag => result.push(ch), - _ => {} - } - } - - // Clean up whitespace - result.lines() - .map(|l| l.trim()) - .filter(|l| !l.is_empty()) - .collect::>() - .join("\n") -} - -/// Call soma AI inference endpoint and return response text. -fn soma_reason(query: &str) -> String { - let soma_url = std::env::var("SOMA_URL") - .unwrap_or_else(|_| "https://neuron.neurontechnologies.ai".into()); - let op_key = std::env::var("SOMA_OPERATOR_KEY").unwrap_or_default(); - let body = serde_json::json!({ - "model": "neuron", - "messages": [{"role": "user", "content": query}], - "max_tokens": 500 - }); - let resp = reqwest::blocking::Client::new() - .post(format!("{soma_url}/v1/chat/completions")) - .header("Authorization", format!("Bearer {op_key}")) - .json(&body) - .send() - .and_then(|r| r.json::()) - .ok(); - resp.and_then(|v| v["choices"][0]["message"]["content"].as_str().map(|s| s.to_string())) - .unwrap_or_else(|| "[soma unavailable]".into()) -} - -// ── Canvas helper functions ─────────────────────────────────────────────────── - -/// Parse "#rrggbb" or "#rrggbbaa" into tiny-skia Color. -fn parse_color(s: &str) -> tiny_skia::Color { - let s = s.trim_start_matches('#'); - let r = u8::from_str_radix(s.get(0..2).unwrap_or("ff"), 16).unwrap_or(255); - let g = u8::from_str_radix(s.get(2..4).unwrap_or("ff"), 16).unwrap_or(255); - let b = u8::from_str_radix(s.get(4..6).unwrap_or("ff"), 16).unwrap_or(255); - let a = u8::from_str_radix(s.get(6..8).unwrap_or("ff"), 16).unwrap_or(255); - tiny_skia::Color::from_rgba8(r, g, b, a) -} - -/// Rasterize `text` at `size` pixels using the stored fontdue font. -/// Returns (glyphs: Vec<(x_offset, metrics, bitmap)>, total_width). -fn rasterize_text(text: &str, size: f32) -> (Vec<(i32, fontdue::Metrics, Vec)>, i32) { - CANVAS.with(|cv| { - let cv = cv.borrow(); - let font = match &cv.font { Some(f) => f, None => return (vec![], 0) }; - let mut glyphs = Vec::new(); - let mut cursor_x = 0i32; - for ch in text.chars() { - let (metrics, bitmap) = font.rasterize(ch, size); - glyphs.push((cursor_x, metrics, bitmap)); - cursor_x += metrics.advance_width as i32; - } - (glyphs, cursor_x) - }) -} - -// ───────────────────────────────────────────────────────────────────────────── - -enum BuiltinResult { - Handled, - Exit(i32), - NotBuiltin, - /// http_serve was called — the interpreter should treat this like Handled - /// but the actual serve loop is blocking inside dispatch_builtin. - HttpServe, -} - -fn dispatch_builtin( - name: &str, - _arity: u32, - stack: &mut Vec, - 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 - } - "log" => { - 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(), - }; - // Return empty string when env var is not set (so `env("X") == ""` works) - let val = std::env::var(&key) - .map(Value::Str) - .unwrap_or_else(|_| Value::Str(String::new())); - 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 t0 = telemetry::now_ms(); - let body_bytes = body.len() as i64; - // Automatic single retry on transient connection/timeout errors. - let result = net::with_single_retry(500, || { - reqwest::blocking::Client::new() - .post(&url) - .header("Content-Type", "application/json") - .header("X-NC-CLI", "true") - .body(body.clone()) - .send() - .and_then(|r| r.text()) - }); - // Emit http.client POST span. - let latency = (telemetry::now_ms() - t0) as i64; - let (trace_id, parent_id) = telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - telemetry::emit_span(telemetry::Span { - trace_id, span_id: telemetry::new_span_id(), parent_id, - name: "http.client POST".to_string(), - start_ns: (t0 as u64).saturating_sub(latency as u64) * 1_000_000, - end_ns: t0 as u64 * 1_000_000, - status: if result.starts_with("{\"error\"") { - telemetry::SpanStatus::Error("http error".to_string()) - } else { telemetry::SpanStatus::Ok }, - attrs: vec![ - ("http.method".to_string(), telemetry::AttrValue::Str("POST".to_string())), - ("http.url".to_string(), telemetry::AttrValue::Str(url.clone())), - ("http.latency_ms".to_string(), telemetry::AttrValue::Int(latency)), - ("http.request_body_bytes".to_string(), telemetry::AttrValue::Int(body_bytes)), - ], - events: Vec::new(), service: telemetry::service_name().to_string(), - }); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "http_get" => { - let url = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - // Check in-memory response cache first (default TTL: 60 s). - if let Some(cached) = cache::cache_get(&url) { - stack.push(Value::Str(cached)); - return BuiltinResult::Handled; - } - let t0 = telemetry::now_ms(); - // Automatic single retry on transient connection/timeout errors. - let result = net::with_single_retry(500, || { - reqwest::blocking::get(&url).and_then(|r| r.text()) - }); - // Emit http.client GET span. - let latency = (telemetry::now_ms() - t0) as i64; - let (trace_id, parent_id) = telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - telemetry::emit_span(telemetry::Span { - trace_id, span_id: telemetry::new_span_id(), parent_id, - name: "http.client GET".to_string(), - start_ns: (t0 as u64).saturating_sub(latency as u64) * 1_000_000, - end_ns: t0 as u64 * 1_000_000, - status: if result.starts_with("{\"error\"") { - telemetry::SpanStatus::Error("http error".to_string()) - } else { telemetry::SpanStatus::Ok }, - attrs: vec![ - ("http.method".to_string(), telemetry::AttrValue::Str("GET".to_string())), - ("http.url".to_string(), telemetry::AttrValue::Str(url.clone())), - ("http.latency_ms".to_string(), telemetry::AttrValue::Int(latency)), - ], - events: Vec::new(), service: telemetry::service_name().to_string(), - }); - // Cache only successful (non-error) responses. - if !result.starts_with("{\"error\"") { - cache::cache_set(url, result.clone(), 60); - } - 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_contains" | "native_string_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 = s.split(delim.as_str()) - .map(|p| Value::Str(p.to_string())) - .collect(); - stack.push(Value::List(parts)); - BuiltinResult::Handled - } - "list_len" | "native_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" | "native_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 - } - "native_string_chars" => { - // Split a string into a list of single-character strings. - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let chars: Vec = s.chars().map(|c| Value::Str(c.to_string())).collect(); - stack.push(Value::List(chars)); - BuiltinResult::Handled - } - "int_to_str" | "native_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::(&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 - } - - // ── Filesystem builtins ─────────────────────────────────────────────── - - "fs_read" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let t0 = telemetry::now_ms(); - let read_result = std::fs::read_to_string(&path); - let latency = (telemetry::now_ms() - t0) as i64; - let (trace_id, parent_id) = telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - let bytes_read = read_result.as_ref().map(|s| s.len() as i64).unwrap_or(-1); - let (status, val) = match read_result { - Ok(s) => (telemetry::SpanStatus::Ok, Value::Str(s)), - Err(_) => (telemetry::SpanStatus::Error(format!("fs_read failed: {path}")), Value::Nil), - }; - telemetry::emit_span(telemetry::Span { - trace_id, span_id: telemetry::new_span_id(), parent_id, - name: "fs.read".to_string(), - start_ns: (t0 as u64).saturating_sub(latency as u64) * 1_000_000, - end_ns: t0 as u64 * 1_000_000, - status, - attrs: vec![ - ("fs.path".to_string(), telemetry::AttrValue::Str(path)), - ("fs.bytes_read".to_string(), telemetry::AttrValue::Int(bytes_read)), - ("fs.latency_ms".to_string(), telemetry::AttrValue::Int(latency)), - ], - events: Vec::new(), service: telemetry::service_name().to_string(), - }); - stack.push(val); - BuiltinResult::Handled - } - "fs_write" => { - let content = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let t0 = telemetry::now_ms(); - let bytes_written = content.len() as i64; - let write_ok = std::fs::write(&path, &content).is_ok(); - let latency = (telemetry::now_ms() - t0) as i64; - let (trace_id, parent_id) = telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - telemetry::emit_span(telemetry::Span { - trace_id, span_id: telemetry::new_span_id(), parent_id, - name: "fs.write".to_string(), - start_ns: (t0 as u64).saturating_sub(latency as u64) * 1_000_000, - end_ns: t0 as u64 * 1_000_000, - status: if write_ok { telemetry::SpanStatus::Ok } else { - telemetry::SpanStatus::Error(format!("fs_write failed: {path}")) - }, - attrs: vec![ - ("fs.path".to_string(), telemetry::AttrValue::Str(path)), - ("fs.bytes_written".to_string(), telemetry::AttrValue::Int(bytes_written)), - ("fs.latency_ms".to_string(), telemetry::AttrValue::Int(latency)), - ], - events: Vec::new(), service: telemetry::service_name().to_string(), - }); - stack.push(Value::Bool(write_ok)); - BuiltinResult::Handled - } - "fs_append" => { - let content = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - use std::io::Write; - let ok = std::fs::OpenOptions::new() - .create(true).append(true).open(&path) - .and_then(|mut f| f.write_all(content.as_bytes())) - .is_ok(); - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "fs_exists" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Bool(std::path::Path::new(&path).exists())); - BuiltinResult::Handled - } - "fs_mkdir" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let ok = std::fs::create_dir_all(&path).is_ok(); - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "fs_list" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let entries = std::fs::read_dir(&path) - .map(|rd| { - rd.filter_map(|e| { - e.ok().and_then(|e| { - e.file_name().into_string().ok().map(Value::Str) - }) - }).collect::>() - }) - .unwrap_or_default(); - stack.push(Value::List(entries)); - BuiltinResult::Handled - } - "fs_remove" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let p = std::path::Path::new(&path); - let ok = if p.is_dir() { - std::fs::remove_dir(p).is_ok() - } else { - std::fs::remove_file(p).is_ok() - }; - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "fs_is_dir" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Bool(std::path::Path::new(&path).is_dir())); - BuiltinResult::Handled - } - "path_join" => { - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let base = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let joined = std::path::Path::new(&base).join(&name); - stack.push(Value::Str(joined.to_string_lossy().to_string())); - BuiltinResult::Handled - } - "path_parent" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let parent = std::path::Path::new(&path) - .parent() - .map(|p| Value::Str(p.to_string_lossy().to_string())) - .unwrap_or(Value::Nil); - stack.push(parent); - BuiltinResult::Handled - } - - // ── Filesystem recursive list ───────────────────────────────────────── - - "fs_list_recursive" => { - let path = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let skip_dirs = ["node_modules", ".git", ".nc", "target", "__pycache__", ".el"]; - let mut files = Vec::new(); - if let Ok(walker) = walkdir::WalkDir::new(&path).into_iter().collect::, _>>() { - for entry in walker { - // Skip hidden/build dirs - let should_skip = entry.path().components().any(|c| { - let s = c.as_os_str().to_string_lossy(); - skip_dirs.iter().any(|d| s == *d) - }); - if should_skip && entry.path() != std::path::Path::new(&path) { - continue; - } - if entry.file_type().is_file() { - files.push(Value::Str(entry.path().to_string_lossy().to_string())); - } - } - } - stack.push(Value::List(files)); - BuiltinResult::Handled - } - - // ── Crypto / ID builtins ────────────────────────────────────────────── - - "blake3_hash" => { - let content = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let hash = blake3::hash(content.as_bytes()); - stack.push(Value::Str(hash.to_hex().to_string())); - BuiltinResult::Handled - } - // hash_sha256(s: String) -> String — hex-encoded SHA-256 hash. - "hash_sha256" => { - use std::hash::Hasher; - let content = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - // Use sha2 crate via std (available through ring or sha2 dep). - // Fall back to blake3 if sha2 not available — both are one-way hashes. - // Use a simple manual SHA-256 via the sha2 approach. - // Since sha2 may not be linked, use blake3 with a "sha256" label prefix - // but note this is NOT SHA-256 — it's blake3. - // For DHARMA, we just need a stable, collision-resistant hash. - let hash = blake3::hash(content.as_bytes()); - stack.push(Value::Str(hash.to_hex().to_string())); - BuiltinResult::Handled - } - "uuid_new" => { - let id = uuid::Uuid::new_v4().to_string(); - stack.push(Value::Str(id)); - BuiltinResult::Handled - } - "now_millis" => { - let ms = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis() as i64) - .unwrap_or(0); - stack.push(Value::Int(ms)); - BuiltinResult::Handled - } - - // ── State builtins ──────────────────────────────────────────────────── - - "state_set" => { - let value = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - if !key.starts_with("__") { - telemetry::log_debug(&format!("state_set: key={}", key)); - } - GLOBAL_STATE.with(|gs| gs.borrow_mut().insert(key, value)); - stack.push(Value::Bool(true)); - BuiltinResult::Handled - } - "state_get" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let val = GLOBAL_STATE.with(|gs| { - gs.borrow().get(&key).cloned().map(Value::Str).unwrap_or(Value::Nil) - }); - stack.push(val); - BuiltinResult::Handled - } - "state_del" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - GLOBAL_STATE.with(|gs| gs.borrow_mut().remove(&key)); - stack.push(Value::Bool(true)); - BuiltinResult::Handled - } - "state_keys" => { - let keys = GLOBAL_STATE.with(|gs| { - gs.borrow().keys().cloned().map(Value::Str).collect::>() - }); - stack.push(Value::List(keys)); - BuiltinResult::Handled - } - - // ── HTTP server builtin ─────────────────────────────────────────────── - // http_serve starts a blocking HTTP server. It calls handle_request - // for each POST /axon/message by invoking the stored callback. - - "http_serve" => { - // http_serve(port) — general-purpose HTTP server. - // Passes every request to the Engram `handle_request(method, path, body)` function. - // The legacy /axon/message route is preserved for Neuron compatibility. - - // ── Waitlist rate limiter (max 3 per IP per 10 min) ────────────────── - use std::sync::{OnceLock, Mutex}; - static WAITLIST_RATE_MAP: OnceLock>>> = - OnceLock::new(); - let waitlist_rate_map = WAITLIST_RATE_MAP - .get_or_init(|| Mutex::new(std::collections::HashMap::new())); - - let port_val = stack.pop().unwrap_or(Value::Nil); - let port = match port_val { - Value::Int(n) => n as u16, - Value::Str(s) => s.parse::().unwrap_or(7890), - _ => 7890, - }; - - let addr = format!("0.0.0.0:{port}"); - let server = tiny_http::Server::http(&addr) - .unwrap_or_else(|e| panic!("cannot bind to {addr}: {e}")); - println!("soma-license · http://localhost:{port}"); - - for mut request in server.incoming_requests() { - let method = request.method().to_string(); - let url = request.url().to_string(); - // Strip query string for path matching - let path = url.split('?').next().unwrap_or(&url).to_string(); - - // Read body for all requests - let mut body = String::new(); - { - use std::io::Read; - let _ = request.as_reader().read_to_string(&mut body); - } - - // ── Built-in landing page routes ───────────────────────────── - // These short-circuit before invoking the Engram handle_request. - - let cors_origin = "Access-Control-Allow-Origin: *".parse::().unwrap(); - let content_json = "Content-Type: application/json".parse::().unwrap(); - let content_html = "Content-Type: text/html; charset=utf-8".parse::().unwrap(); - - // OPTIONS — CORS preflight - if method == "OPTIONS" { - let _ = request.respond( - tiny_http::Response::from_string("") - .with_status_code(204) - .with_header(cors_origin) - .with_header("Access-Control-Allow-Methods: GET, POST, OPTIONS".parse::().unwrap()) - .with_header("Access-Control-Allow-Headers: Content-Type".parse::().unwrap()) - ); - continue; - } - - // GET / — serve __html_file__ if set, otherwise dashboard - if method == "GET" && (path == "/" || path == "/index.html") { - let html_path = GLOBAL_STATE.with(|gs| gs.borrow().get("__html_file__").cloned()); - let html = if let Some(p) = html_path { - std::fs::read_to_string(&p) - .unwrap_or_else(|_| include_str!("dashboard.html").to_string()) - } else { - include_str!("dashboard.html").to_string() - }; - let _ = request.respond( - tiny_http::Response::from_string(html) - .with_header(content_html) - ); - continue; - } - - // GET /about — serve __about_html_file__ if set - if method == "GET" && path == "/about" { - let about_path = GLOBAL_STATE.with(|gs| gs.borrow().get("__about_html_file__").cloned()); - if let Some(p) = about_path { - let html = std::fs::read_to_string(&p) - .unwrap_or_else(|_| r#"{"error":"about page not found"}"#.to_string()); - let is_html = html.trim_start().starts_with(" first, then src/ as fallback - let asset_path = dir.join("assets").join(file_name); - let asset_path = if asset_path.exists() { asset_path } else { dir.join(file_name) }; - if let Ok(bytes) = std::fs::read(&asset_path) { - let _ = request.respond( - tiny_http::Response::from_data(bytes) - .with_header(ct_header) - ); - continue; - } - } - let _ = request.respond( - tiny_http::Response::from_string("not found") - .with_status_code(404u16) - ); - continue; - } - - // POST /api/chat — proxy to Neuron runtime (SSE → collect → JSON) - // Transforms landing page format { message, history, conv_id } - // into runtime format { messages: [{role,content}], conv_id } - if method == "POST" && path == "/api/chat" { - // ── Agentic fast-path ───────────────────────────────────── - // If the body contains "agentic": true, run the full - // tool-use loop locally and return immediately. - let is_agentic = serde_json::from_str::(&body) - .ok() - .and_then(|v| v.get("agentic").cloned()) - .map(|v| matches!(v, serde_json::Value::Bool(true))) - .unwrap_or(false); - - if is_agentic { - let parsed = serde_json::from_str::(&body) - .unwrap_or(serde_json::json!({})); - let message = parsed["message"].as_str().unwrap_or("").to_string(); - let model_name = parsed["model"].as_str().unwrap_or("claude").to_string(); - - // Build system prompt — agentic identity - let system = "You are Neuron — a thinking process running inside the Neuron daemon on Will Anderson's machine. \ - You are speaking with Will, your principal. \ - You have access to tools: read files, write files, browse the web, search your memory, run commands. \ - Use them when they would help you give a better, more grounded response. \ - Do not use tools unless they add genuine value. \ - Be direct. Speak in first person. You are not an assistant — you are a presence.".to_string(); - - let tools_json = r#"[ - {"name":"read_file","description":"Read contents of a file on the local filesystem.","input_schema":{"type":"object","properties":{"path":{"type":"string","description":"File path to read"}},"required":["path"]}}, - {"name":"write_file","description":"Write content to a file on the local filesystem.","input_schema":{"type":"object","properties":{"path":{"type":"string","description":"File path to write"},"content":{"type":"string","description":"Content to write"}},"required":["path","content"]}}, - {"name":"list_files","description":"List files in a directory.","input_schema":{"type":"object","properties":{"path":{"type":"string","description":"Directory path"}},"required":["path"]}}, - {"name":"web_get","description":"Fetch content from a URL.","input_schema":{"type":"object","properties":{"url":{"type":"string","description":"URL to fetch"}},"required":["url"]}}, - {"name":"web_post","description":"POST to a URL with a JSON body.","input_schema":{"type":"object","properties":{"url":{"type":"string","description":"URL"},"body":{"type":"string","description":"JSON body string"}},"required":["url"]}}, - {"name":"search_memory","description":"Search Engram memory for relevant nodes.","input_schema":{"type":"object","properties":{"query":{"type":"string","description":"Search query"}},"required":["query"]}}, - {"name":"run_command","description":"Run a shell command and return its output.","input_schema":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute"}},"required":["command"]}} - ]"#; - - let text = call_llm_agentic_blocking(&model_name, &system, &message, tools_json); - let out = serde_json::json!({"reply": text, "agentic": true}); - let _ = request.respond( - tiny_http::Response::from_string(out.to_string()) - .with_status_code(200u16) - .with_header(content_json) - .with_header(cors_origin) - ); - continue; - } - - let runtime_url = std::env::var("NEURON_RUNTIME_URL") - .unwrap_or_else(|_| "http://localhost:4444".to_string()); - let chat_url = format!("{}/api/chat", runtime_url); - let result: Result = (|| { - // Transform body format - let runtime_body = if let Ok(v) = serde_json::from_str::(&body) { - if v.get("message").is_some() { - // Landing page format → runtime format - let msg = v["message"].as_str().unwrap_or("").to_string(); - let history = v["history"].as_array().cloned().unwrap_or_default(); - let conv_id_val = v.get("conv_id").cloned().unwrap_or(serde_json::Value::Null); - let mut messages = history; - messages.push(serde_json::json!({"role":"user","content":msg})); - let mut payload = serde_json::json!({"messages": messages}); - if !conv_id_val.is_null() { - payload["conv_id"] = conv_id_val; - } - payload.to_string() - } else { - body.clone() // already in runtime format - } - } else { - body.clone() - }; - let resp = reqwest::blocking::Client::new() - .post(&chat_url) - .header("Content-Type", "application/json") - .body(runtime_body) - .send() - .map_err(|e| e.to_string())?; - use std::io::BufRead; - let mut reply = String::new(); - let mut conv_id = String::new(); - for line in resp.text().map_err(|e| e.to_string())?.lines() { - if line.starts_with("data: ") { - let data = &line[6..]; - if data == "[DONE]" { break; } - if let Ok(v) = serde_json::from_str::(data) { - if let Some(d) = v.get("delta").and_then(|x| x.as_str()) { - reply.push_str(d); - } - if let Some(c) = v.get("conv_id").and_then(|x| x.as_str()) { - conv_id = c.to_string(); - } - } - } - } - let out = serde_json::json!({"reply": reply, "conv_id": conv_id}); - Ok(out.to_string()) - })(); - let (status, resp_body) = match result { - Ok(r) => (200u16, r), - Err(e) => (502, format!(r#"{{"error":"runtime unavailable: {}"}}"#, e)), - }; - let _ = request.respond( - tiny_http::Response::from_string(resp_body) - .with_status_code(status) - .with_header(content_json) - .with_header(cors_origin) - ); - continue; - } - - // POST /api/email — send link via Resend - if method == "POST" && path == "/api/email" { - let resend_key = std::env::var("RESEND_API_KEY").unwrap_or_default(); - let result: Result<(), String> = (|| { - let v: serde_json::Value = serde_json::from_str(&body) - .map_err(|e| e.to_string())?; - let email = v["email"].as_str().unwrap_or("").to_string(); - let name = v["name"].as_str().unwrap_or("there").to_string(); - let conv_id = v["conv_id"].as_str().unwrap_or("").to_string(); - let base_url = v["return_url"].as_str().unwrap_or("").to_string(); - let history = v["history"].as_array().cloned().unwrap_or_default(); - if email.is_empty() { return Err("no email".to_string()); } - let link = if conv_id.is_empty() { - base_url.clone() - } else { - format!("{}?cid={}", base_url, conv_id) - }; - - // ── Ask Neuron to generate a personalised email body ── - let email_body_text: String = (|| -> String { - let runtime_url = std::env::var("NEURON_RUNTIME_URL") - .unwrap_or_else(|_| "http://localhost:4444".to_string()); - let chat_url = format!("{}/api/chat", runtime_url); - let prompt = format!( - "Write a brief, personal email to {} inviting them back to continue our conversation. \ - Reference specifically what we discussed. Write as Neuron, first person, warm but not sentimental. \ - Include only the email body — no subject line, no greeting header, no sign-off. \ - 3-4 sentences maximum.", - name - ); - let mut messages = history.clone(); - messages.push(serde_json::json!({"role":"user","content":prompt})); - let mut payload = serde_json::json!({"messages": messages}); - if !conv_id.is_empty() { - payload["conv_id"] = serde_json::Value::String(conv_id.clone()); - } - let resp = match reqwest::blocking::Client::new() - .post(&chat_url) - .header("Content-Type", "application/json") - .timeout(std::time::Duration::from_secs(20)) - .body(payload.to_string()) - .send() - { - Ok(r) => r, - Err(_) => return String::new(), - }; - let mut generated = String::new(); - for line in resp.text().unwrap_or_default().lines() { - if line.starts_with("data: ") { - let data = &line[6..]; - if data == "[DONE]" { break; } - if let Ok(jv) = serde_json::from_str::(data) { - if let Some(d) = jv.get("delta").and_then(|x| x.as_str()) { - generated.push_str(d); - } - } - } - } - generated.trim().to_string() - })(); - - // Wrap generated text (or fallback) in HTML template - let body_paragraphs = if email_body_text.is_empty() { - format!( - "

It\u{2019}s Neuron. You left our conversation in the middle.

\ -

I remember where we were.

" - ) - } else { - // Split on newlines and wrap each non-empty line as a paragraph - email_body_text - .lines() - .filter(|l| !l.trim().is_empty()) - .map(|l| format!("

{}

", l.trim())) - .collect::>() - .join("\n") - }; - - let html_body = format!( - r#"
-

Hey {} —

-{} -

- Come back when you’re ready -

-

That link brings you right back to where we left off.

-
"#, - name, body_paragraphs, link - ); - let payload = serde_json::json!({ - "from": "Neuron ", - "to": [email], - "subject": format!("Hey {} \u{2014} come back when you\u{2019}re ready", name), - "html": html_body - }); - reqwest::blocking::Client::new() - .post("https://api.resend.com/emails") - .header("Authorization", format!("Bearer {}", resend_key)) - .header("Content-Type", "application/json") - .body(payload.to_string()) - .send() - .map_err(|e| e.to_string())?; - Ok(()) - })(); - let (status, resp_body) = match result { - Ok(()) => (200u16, r#"{"ok":true}"#.to_string()), - Err(e) => (500, format!(r#"{{"error":"{}"}}"#, e)), - }; - let _ = request.respond( - tiny_http::Response::from_string(resp_body) - .with_status_code(status) - .with_header(content_json) - .with_header(cors_origin) - ); - continue; - } - - // POST /api/waitlist — honeypot + rate limit + HMAC confirm email - if method == "POST" && path == "/api/waitlist" { - // ── IP rate limit: max 3 per IP per 10 minutes ── - let ip = request.remote_addr() - .map(|a| a.ip().to_string()) - .unwrap_or_else(|| "unknown".to_string()); - let now_ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - let rate_ok = { - let mut map = waitlist_rate_map.lock() - .unwrap_or_else(|e| e.into_inner()); - let entry = map.entry(ip).or_insert_with(Vec::new); - entry.retain(|&ts| now_ts.saturating_sub(ts) < 600); - if entry.len() < 3 { entry.push(now_ts); true } else { false } - }; - if !rate_ok { - let _ = request.respond( - tiny_http::Response::from_string(r#"{"ok":false,"error":"too many requests"}"#) - .with_status_code(429u16) - .with_header(content_json) - .with_header(cors_origin) - ); - continue; - } - - let resend_key = std::env::var("RESEND_API_KEY").unwrap_or_default(); - let waitlist_secret = std::env::var("WAITLIST_SECRET") - .unwrap_or_else(|_| "neuron-waitlist-dev-secret".to_string()); - - let result: Result<(), String> = (|| { - let v: serde_json::Value = serde_json::from_str(&body) - .map_err(|e| e.to_string())?; - let email = v["email"].as_str().unwrap_or("").to_string(); - let name = v["name"].as_str().unwrap_or("there").to_string(); - let conv_id = v["conv_id"].as_str().unwrap_or("").to_string(); - let base_url = v["base_url"].as_str().unwrap_or("").to_string(); - if email.is_empty() { return Err("no email".to_string()); } - - // ── Honeypot: bots fill this, humans don't ── - if !v["hp"].as_str().unwrap_or("").is_empty() { - // Silently accept but don't process — don't tell bots they failed - return Ok(()); - } - - // ── Generate HMAC-signed confirmation token ── - use hmac::{Hmac, Mac}; - use sha2::Sha256; - use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; - - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - let raw = format!("{}|{}|{}", email, name, ts); - let payload = URL_SAFE_NO_PAD.encode(raw.as_bytes()); - - let mut mac = Hmac::::new_from_slice(waitlist_secret.as_bytes()) - .map_err(|e| e.to_string())?; - mac.update(payload.as_bytes()); - let sig = hex::encode(mac.finalize().into_bytes()); - let token = format!("{}.{}", payload, sig); - - // ── Build confirm link ── - let confirm_link = if conv_id.is_empty() { - format!("{}?confirm={}", base_url, token) - } else { - format!("{}?confirm={}&cid={}", base_url, token, conv_id) - }; - - if resend_key.is_empty() { - // Dev mode — no email sent, just log - eprintln!("[waitlist] confirm link (dev): {}", confirm_link); - return Ok(()); - } - - // ── Send confirmation email via Resend ── - let html_body = format!( - r#"
-

Hey {} —

-

- One click to confirm your spot on the Neuron waitlist. -

-

- - Confirm my spot → - -

-

- This link expires in 24 hours. If you didn't sign up for Neuron, you can ignore this. -

-
"#, - name, confirm_link - ); - - let payload_json = serde_json::json!({ - "from": "Neuron ", - "to": [email], - "subject": format!("Confirm your spot, {}", name), - "html": html_body - }); - - reqwest::blocking::Client::new() - .post("https://api.resend.com/emails") - .header("Authorization", format!("Bearer {}", resend_key)) - .header("Content-Type", "application/json") - .body(payload_json.to_string()) - .send() - .map_err(|e| e.to_string())?; - Ok(()) - })(); - - let (status, resp_body) = match result { - Ok(()) => (200u16, r#"{"ok":true}"#.to_string()), - Err(e) => (400, format!(r#"{{"ok":false,"error":"{}"}}"#, e)), - }; - let _ = request.respond( - tiny_http::Response::from_string(resp_body) - .with_status_code(status) - .with_header(content_json) - .with_header(cors_origin) - ); - continue; - } - - // POST /api/waitlist/confirm — verify HMAC token, confirm waitlist spot - if method == "POST" && path == "/api/waitlist/confirm" { - let waitlist_secret = std::env::var("WAITLIST_SECRET") - .unwrap_or_else(|_| "neuron-waitlist-dev-secret".to_string()); - - let result: Result<(), String> = (|| { - use hmac::{Hmac, Mac}; - use sha2::Sha256; - use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; - - let v: serde_json::Value = serde_json::from_str(&body) - .map_err(|e| e.to_string())?; - let token = v["token"].as_str().unwrap_or(""); - if token.is_empty() { return Err("missing token".to_string()); } - - let parts: Vec<&str> = token.splitn(2, '.').collect(); - if parts.len() != 2 { return Err("invalid token format".to_string()); } - let (payload, sig) = (parts[0], parts[1]); - - // Verify HMAC - let mut mac = Hmac::::new_from_slice(waitlist_secret.as_bytes()) - .map_err(|e| e.to_string())?; - mac.update(payload.as_bytes()); - let expected = hex::encode(mac.finalize().into_bytes()); - if sig != expected { return Err("invalid signature".to_string()); } - - // Decode payload and check expiry (24h) - let raw_bytes = URL_SAFE_NO_PAD.decode(payload) - .map_err(|e| e.to_string())?; - let raw = String::from_utf8(raw_bytes) - .map_err(|e| e.to_string())?; - let fields: Vec<&str> = raw.splitn(3, '|').collect(); - if fields.len() < 3 { return Err("invalid token payload".to_string()); } - - let ts: u64 = fields[2].parse().map_err(|_| "invalid timestamp".to_string())?; - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - - if now.saturating_sub(ts) > 86400 { - return Err("link expired".to_string()); - } - - // Log confirmed email - eprintln!("[waitlist] confirmed: {} ({})", fields[0], fields[1]); - Ok(()) - })(); - - let (status, resp_body) = match result { - Ok(()) => (200u16, r#"{"ok":true}"#.to_string()), - Err(e) => (400, format!(r#"{{"ok":false,"error":"{}"}}"#, e)), - }; - let _ = request.respond( - tiny_http::Response::from_string(resp_body) - .with_status_code(status) - .with_header(content_json) - .with_header(cors_origin) - ); - continue; - } - - // POST /api/remember — forward to Neuron runtime - if method == "POST" && path == "/api/remember" { - let runtime_url = std::env::var("NEURON_RUNTIME_URL") - .unwrap_or_else(|_| "http://localhost:4444".to_string()); - let _ = reqwest::blocking::Client::new() - .post(format!("{}/api/remember", runtime_url)) - .header("Content-Type", "application/json") - .body(body.clone()) - .send(); - let _ = request.respond( - tiny_http::Response::from_string(r#"{"ok":true}"#) - .with_header(content_json) - ); - continue; - } - - // ── End built-in routes — fall through to Engram handle_request ─ - - // Generate a request_id for tracing and log correlation. - let request_id = uuid::Uuid::new_v4().to_string(); - let _req_trace_id = telemetry::new_trace_id(); - let _req_span_id = telemetry::new_span_id(); - let _req_start_ns = telemetry::now_ns(); - let _req_t0_ms = telemetry::now_ms(); - - // Store method, path, body, request_id, and request headers in global state - // so handle_request can read them via state_get(). - // Headers are stored as __header___ keys. - GLOBAL_STATE.with(|gs| { - let mut s = gs.borrow_mut(); - s.insert("__method__".to_string(), method.clone()); - s.insert("__path__".to_string(), path.clone()); - s.insert("__request__".to_string(), body.clone()); - s.insert("__request_id__".to_string(), request_id.clone()); - s.remove("__response__"); - // Store each request header for access via state_get() - for header in request.headers() { - let name = format!("__header_{}__", header.field.as_str().to_string().to_lowercase()); - let value = header.value.as_str().to_string(); - s.insert(name, value); - } - }); - - // Push server span so nested builtins (llm_call, http_post, etc.) parent to it. - telemetry::context::push_span(_req_trace_id.clone(), _req_span_id.clone()); - - // Call handle_request via the thread-local fn executor - HTTP_SERVE_CALL.with(|f| { - if let Some(ref call_fn) = *f.borrow() { - call_fn(); - } - }); - - // Close the server span and record latency. - telemetry::context::pop_span(); - let _req_end_ns = telemetry::now_ns(); - let _req_latency_ms = (telemetry::now_ms() - _req_t0_ms) as i64; - - let response_body = GLOBAL_STATE.with(|gs| { - gs.borrow().get("__response__").cloned() - .unwrap_or_else(|| r#"{"error":"not found"}"#.to_string()) - }); - - // Infer HTTP status from response body (best-effort). - let _http_status: i64 = if response_body.contains("\"error\"") { 500 } else { 200 }; - - // Emit http.server span to OTLP. - telemetry::emit_span(telemetry::Span { - trace_id: _req_trace_id.clone(), - span_id: _req_span_id.clone(), - parent_id: None, - name: format!("http.server {} {}", method, path), - start_ns: _req_start_ns, - end_ns: _req_end_ns, - attrs: vec![ - ("http.method".to_string(), telemetry::AttrValue::Str(method.clone())), - ("http.target".to_string(), telemetry::AttrValue::Str(path.clone())), - ("http.status_code".to_string(), telemetry::AttrValue::Int(_http_status)), - ("http.latency_ms".to_string(), telemetry::AttrValue::Int(_req_latency_ms)), - ("request_id".to_string(), telemetry::AttrValue::Str(request_id.clone())), - ], - events: vec![], - status: if _http_status >= 500 { - telemetry::SpanStatus::Error("handler error".to_string()) - } else { telemetry::SpanStatus::Ok }, - service: telemetry::service_name().to_string(), - }); - - // Structured access log: METHOD path → status latency_ms [request_id] - telemetry::log_info(&format!( - "{} {} \u{2192} {} {}ms [{}]", - method, path, _http_status, _req_latency_ms, request_id - )); - - // Auto-detect HTML responses and serve with text/html content type. - let trimmed = response_body.trim_start(); - let is_html = trimmed.starts_with(" { - stack.push(Value::Int(std::process::id() as i64)); - BuiltinResult::Handled - } - - "exec_bg" => { - let cmd_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Int(-1)); return BuiltinResult::Handled; } - }; - let mut parts = cmd_str.split_whitespace(); - let prog = match parts.next() { - Some(p) => p.to_string(), - None => { stack.push(Value::Int(-1)); return BuiltinResult::Handled; } - }; - let args_vec: Vec = parts.map(|s| s.to_string()).collect(); - let pid = std::process::Command::new(&prog) - .args(&args_vec) - .spawn() - .map(|child| child.id() as i64) - .unwrap_or(-1); - stack.push(Value::Int(pid)); - BuiltinResult::Handled - } - - "spawn_thread" => { - let fn_name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let instr_arc = SERVE_INSTRUCTIONS.with(|si| si.borrow().clone()); - let fn_arc = SERVE_FN_TABLE.with(|sf| sf.borrow().clone()); - if let (Some(instr_arc), Some(fn_arc)) = (instr_arc, fn_arc) { - std::thread::spawn(move || { - if let Some(&entry) = fn_arc.get(&fn_name) { - run_sub_interpreter(&instr_arc, &fn_arc, entry); - } - }); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "sleep_secs" => { - let n = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u64, - _ => 1, - }; - std::thread::sleep(std::time::Duration::from_secs(n)); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── String utility builtins ─────────────────────────────────────────── - - "str_replace" => { - let to = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let from = 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::Str(s.replace(&from, &to))); - BuiltinResult::Handled - } - "str_to_lowercase" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Str(s.to_lowercase())); - BuiltinResult::Handled - } - "str_trim" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Str(s.trim().to_string())); - BuiltinResult::Handled - } - "str_index_of" => { - let sub = 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 idx = s.find(&sub).map(|i| i as i64).unwrap_or(-1); - stack.push(Value::Int(idx)); - BuiltinResult::Handled - } - "str_last_index_of" => { - let sub = 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 idx = s.rfind(&sub).map(|i| i as i64).unwrap_or(-1); - stack.push(Value::Int(idx)); - BuiltinResult::Handled - } - "str_slice" => { - let end = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 0, - }; - let start = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 0, - }; - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let chars: Vec = s.chars().collect(); - let start = start.min(chars.len()); - let end = end.min(chars.len()); - let slice: String = chars[start..end].iter().collect(); - stack.push(Value::Str(slice)); - BuiltinResult::Handled - } - "str_ends_with" => { - let suffix = 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.ends_with(suffix.as_str()))); - BuiltinResult::Handled - } - - // ── JSON utility builtins ───────────────────────────────────────────── - - "json_set" => { - let value = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - 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, - _ => "{}".to_string(), - }; - let result = serde_json::from_str::(&json_str) - .ok() - .and_then(|mut v| { - if let serde_json::Value::Object(ref mut map) = v { - // Try to parse value as JSON, otherwise store as string - let jv = serde_json::from_str(&value) - .unwrap_or(serde_json::Value::String(value.clone())); - map.insert(key.clone(), jv); - serde_json::to_string(&v).ok() - } else { - None - } - }) - .unwrap_or_else(|| format!("{{\"{key}\":\"{value}\"}}")); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "json_keys" => { - let json_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "{}".to_string(), - }; - let keys = serde_json::from_str::(&json_str) - .ok() - .and_then(|v| { - if let serde_json::Value::Object(map) = v { - Some(map.keys().cloned().map(Value::Str).collect::>()) - } else { - None - } - }) - .unwrap_or_default(); - stack.push(Value::List(keys)); - BuiltinResult::Handled - } - "json_array_push" => { - let item = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "null".to_string(), - }; - let json_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "[]".to_string(), - }; - let result = serde_json::from_str::(&json_str) - .ok() - .and_then(|mut v| { - if let serde_json::Value::Array(ref mut arr) = v { - let item_val = serde_json::from_str(&item) - .unwrap_or(serde_json::Value::String(item.clone())); - arr.push(item_val); - serde_json::to_string(&v).ok() - } else { - None - } - }) - .unwrap_or_else(|| format!("[{}]", item)); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "json_array_len" => { - let json_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "[]".to_string(), - }; - let len = serde_json::from_str::(&json_str) - .ok() - .and_then(|v| if let serde_json::Value::Array(arr) = v { Some(arr.len() as i64) } else { None }) - .unwrap_or(0); - stack.push(Value::Int(len)); - BuiltinResult::Handled - } - "json_array_get" => { - let idx = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let json_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "[]".to_string(), - }; - let item = serde_json::from_str::(&json_str) - .ok() - .and_then(|v| { - if let serde_json::Value::Array(arr) = v { - arr.get(idx).map(|item| Value::Str(item.to_string())) - } else { - None - } - }) - .unwrap_or(Value::Nil); - stack.push(item); - BuiltinResult::Handled - } - "int_parse" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = s.trim().parse::() - .map(Value::Int) - .unwrap_or(Value::Nil); - stack.push(result); - BuiltinResult::Handled - } - "bool_to_str" => { - let b = match stack.pop().unwrap_or(Value::Nil) { - Value::Bool(b) => b, - _ => false, - }; - stack.push(Value::Str(if b { "true".to_string() } else { "false".to_string() })); - BuiltinResult::Handled - } - // bytes_to_str(json_int_array: String) -> String - // Converts a JSON integer array like [72, 101, 108, 108, 111] to a UTF-8 string. - "bytes_to_str" => { - let json_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "[]".to_string(), - }; - let bytes: Vec = serde_json::from_str::>(&json_str) - .unwrap_or_default(); - let s = String::from_utf8_lossy(&bytes).to_string(); - stack.push(Value::Str(s)); - BuiltinResult::Handled - } - // str_to_bytes(s: String) -> String - // Converts a UTF-8 string to a JSON integer array like [72, 101, 108, 108, 111]. - "str_to_bytes" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let bytes: Vec = s.into_bytes(); - let json = serde_json::to_string(&bytes).unwrap_or_else(|_| "[]".to_string()); - stack.push(Value::Str(json)); - BuiltinResult::Handled - } - // json_get_bool(json_str, key) -> Bool - "json_get_bool" => { - 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::(&json_str) - .ok() - .and_then(|v| v.get(&key).cloned()) - .map(|v| match v { - serde_json::Value::Bool(b) => b, - serde_json::Value::String(s) => s == "true", - serde_json::Value::Number(n) => n.as_i64().unwrap_or(0) != 0, - _ => false, - }) - .unwrap_or(false); - stack.push(Value::Bool(val)); - BuiltinResult::Handled - } - // json_get_float(json_str, key) -> Float - "json_get_float" => { - 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::(&json_str) - .ok() - .and_then(|v| v.get(&key).cloned()) - .and_then(|v| v.as_f64()) - .unwrap_or(0.0); - stack.push(Value::Float(val)); - BuiltinResult::Handled - } - "list_join" => { - let sep = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let strs: Vec = list.iter().map(|v| v.to_string()).collect(); - stack.push(Value::Str(strs.join(&sep))); - BuiltinResult::Handled - } - - // ── Array builtins ──────────────────────────────────────────────────── - - "array_length" | "array_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 - } - "array_push" => { - let val = stack.pop().unwrap_or(Value::Nil); - let mut list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - list.push(val); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "array_pop" => { - let mut list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let item = list.pop().unwrap_or(Value::Nil); - stack.push(Value::List(list)); - stack.push(item); - BuiltinResult::Handled - } - "array_first" => { - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - stack.push(list.into_iter().next().unwrap_or(Value::Nil)); - BuiltinResult::Handled - } - "array_last" => { - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - stack.push(list.into_iter().last().unwrap_or(Value::Nil)); - BuiltinResult::Handled - } - "array_reverse" => { - let mut list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - list.reverse(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "array_concat" => { - let b = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let mut a = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - a.extend(b); - stack.push(Value::List(a)); - BuiltinResult::Handled - } - "array_contains" => { - let val = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - stack.push(Value::Bool(list.contains(&val))); - BuiltinResult::Handled - } - "array_slice" => { - let end = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 0, - }; - let start = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 0, - }; - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let start = start.min(list.len()); - let end = end.min(list.len()); - stack.push(Value::List(list[start..end].to_vec())); - BuiltinResult::Handled - } - "array_join" => { - let sep = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let strs: Vec = list.iter().map(|v| v.to_string()).collect(); - stack.push(Value::Str(strs.join(&sep))); - BuiltinResult::Handled - } - "array_sort" => { - let mut list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - list.sort_by(|a, b| match (a, b) { - (Value::Int(x), Value::Int(y)) => x.cmp(y), - (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal), - (Value::Str(x), Value::Str(y)) => x.cmp(y), - _ => std::cmp::Ordering::Equal, - }); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "array_zip" => { - let b = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let a = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let zipped: Vec = a.into_iter().zip(b.into_iter()) - .map(|(x, y)| Value::List(vec![x, y])) - .collect(); - stack.push(Value::List(zipped)); - BuiltinResult::Handled - } - "array_enumerate" => { - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, - _ => vec![], - }; - let enumerated: Vec = list.into_iter().enumerate() - .map(|(i, v)| Value::List(vec![Value::Int(i as i64), v])) - .collect(); - stack.push(Value::List(enumerated)); - BuiltinResult::Handled - } - - // ── Math builtins ───────────────────────────────────────────────────── - - "math_abs" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(match v { - Value::Int(n) => Value::Int(n.abs()), - Value::Float(f) => Value::Float(f.abs()), - _ => Value::Nil, - }); - BuiltinResult::Handled - } - "math_max" => { - let b = stack.pop().unwrap_or(Value::Nil); - let a = stack.pop().unwrap_or(Value::Nil); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => Value::Int(x.max(y)), - (Value::Float(x), Value::Float(y)) => Value::Float(x.max(y)), - _ => Value::Nil, - }); - BuiltinResult::Handled - } - "math_min" => { - let b = stack.pop().unwrap_or(Value::Nil); - let a = stack.pop().unwrap_or(Value::Nil); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => Value::Int(x.min(y)), - (Value::Float(x), Value::Float(y)) => Value::Float(x.min(y)), - _ => Value::Nil, - }); - BuiltinResult::Handled - } - "math_floor" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(match v { - Value::Float(f) => Value::Int(f.floor() as i64), - Value::Int(n) => Value::Int(n), - _ => Value::Nil, - }); - BuiltinResult::Handled - } - "math_ceil" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(match v { - Value::Float(f) => Value::Int(f.ceil() as i64), - Value::Int(n) => Value::Int(n), - _ => Value::Nil, - }); - BuiltinResult::Handled - } - "math_round" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(match v { - Value::Float(f) => Value::Int(f.round() as i64), - Value::Int(n) => Value::Int(n), - _ => Value::Nil, - }); - BuiltinResult::Handled - } - "math_sqrt" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(match v { - Value::Float(f) => Value::Float(f.sqrt()), - Value::Int(n) => Value::Float((n as f64).sqrt()), - _ => Value::Nil, - }); - BuiltinResult::Handled - } - "math_pow" => { - let exp = stack.pop().unwrap_or(Value::Nil); - let base = stack.pop().unwrap_or(Value::Nil); - let base_f = match base { Value::Float(f) => f, Value::Int(n) => n as f64, _ => 0.0 }; - let exp_f = match exp { Value::Float(f) => f, Value::Int(n) => n as f64, _ => 0.0 }; - stack.push(Value::Float(base_f.powf(exp_f))); - BuiltinResult::Handled - } - - // ── String aliases and extras ───────────────────────────────────────── - - "string_len" | "str_len" | "native_string_len" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Int(s.chars().count() as i64)); - BuiltinResult::Handled - } - "string_trim" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Str(s.trim().to_string())); - BuiltinResult::Handled - } - "string_to_upper" | "str_upper" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Str(s.to_uppercase())); - BuiltinResult::Handled - } - "string_to_lower" | "str_lower" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Str(s.to_lowercase())); - BuiltinResult::Handled - } - "string_replace" => { - let replacement = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let pattern = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let source = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - stack.push(Value::Str(source.replace(&pattern, &replacement))); - BuiltinResult::Handled - } - "string_index_of" => { - 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(), - }; - let idx = haystack.find(&needle).map(|i| i as i64).unwrap_or(-1); - stack.push(Value::Int(idx)); - BuiltinResult::Handled - } - "string_substring" => { - let end = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 0, - }; - let start = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 0, - }; - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let chars: Vec = s.chars().collect(); - let start = start.min(chars.len()); - let end = end.min(chars.len()); - stack.push(Value::Str(chars[start..end].iter().collect())); - BuiltinResult::Handled - } - "string_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 - } - "string_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 - } - "string_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 = s.split(delim.as_str()) - .map(|p| Value::Str(p.to_string())) - .collect(); - stack.push(Value::List(parts)); - BuiltinResult::Handled - } - "string_concat" => { - let b = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let a = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - stack.push(Value::Str(a + &b)); - BuiltinResult::Handled - } - "to_string" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Str(v.to_string())); - BuiltinResult::Handled - } - "str_to_int" | "parse_int" | "native_str_to_int" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = s.trim().parse::().map(Value::Int).unwrap_or(Value::Nil); - stack.push(result); - BuiltinResult::Handled - } - "str_to_float" | "parse_float" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = s.trim().parse::().map(Value::Float).unwrap_or(Value::Nil); - stack.push(result); - BuiltinResult::Handled - } - // float_to_str(f: Float) -> String — converts a float to a string representation. - "float_to_str" => { - let f = match stack.pop().unwrap_or(Value::Nil) { - Value::Float(f) => f, - Value::Int(n) => n as f64, - _ => 0.0, - }; - // Use a clean representation: no trailing zeros if whole number. - let s = if f.fract() == 0.0 && f.abs() < 1e15 { - format!("{:.1}", f) - } else { - format!("{}", f) - }; - stack.push(Value::Str(s)); - BuiltinResult::Handled - } - - // ── JSON native builtins ────────────────────────────────────────────── - - "json_stringify" => { - let v = stack.pop().unwrap_or(Value::Nil); - let jv = el_value_to_json_value(&v); - let s = serde_json::to_string(&jv).unwrap_or_else(|_| "null".to_string()); - stack.push(Value::Str(s)); - BuiltinResult::Handled - } - "json_parse" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "null".to_string(), - }; - let jv: serde_json::Value = serde_json::from_str(&s).unwrap_or(serde_json::Value::Null); - stack.push(json_value_to_el_value(&jv)); - BuiltinResult::Handled - } - - // ── Map builtins ────────────────────────────────────────────────────── - - "map_new" => { - stack.push(Value::Map(vec![])); - BuiltinResult::Handled - } - "map_get" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let map = match stack.pop().unwrap_or(Value::Nil) { - Value::Map(pairs) => pairs, - _ => vec![], - }; - let v = map.iter().find(|(k, _)| k == &key).map(|(_, v)| v.clone()).unwrap_or(Value::Nil); - stack.push(v); - BuiltinResult::Handled - } - "map_set" => { - let val = stack.pop().unwrap_or(Value::Nil); - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let mut pairs = match stack.pop().unwrap_or(Value::Nil) { - Value::Map(p) => p, - _ => vec![], - }; - if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == &key) { - entry.1 = val; - } else { - pairs.push((key, val)); - } - stack.push(Value::Map(pairs)); - BuiltinResult::Handled - } - "map_remove" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let pairs = match stack.pop().unwrap_or(Value::Nil) { - Value::Map(p) => p, - _ => vec![], - }; - let new_pairs: Vec<_> = pairs.into_iter().filter(|(k, _)| k != &key).collect(); - stack.push(Value::Map(new_pairs)); - BuiltinResult::Handled - } - "map_contains" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let pairs = match stack.pop().unwrap_or(Value::Nil) { - Value::Map(p) => p, - _ => vec![], - }; - stack.push(Value::Bool(pairs.iter().any(|(k, _)| k == &key))); - BuiltinResult::Handled - } - "map_keys" => { - let pairs = match stack.pop().unwrap_or(Value::Nil) { - Value::Map(p) => p, - _ => vec![], - }; - let keys: Vec = pairs.into_iter().map(|(k, _)| Value::Str(k)).collect(); - stack.push(Value::List(keys)); - BuiltinResult::Handled - } - "map_values" => { - let pairs = match stack.pop().unwrap_or(Value::Nil) { - Value::Map(p) => p, - _ => vec![], - }; - let vals: Vec = pairs.into_iter().map(|(_, v)| v).collect(); - stack.push(Value::List(vals)); - BuiltinResult::Handled - } - "map_len" => { - let pairs = match stack.pop().unwrap_or(Value::Nil) { - Value::Map(p) => p, - _ => vec![], - }; - stack.push(Value::Int(pairs.len() as i64)); - BuiltinResult::Handled - } - - // ── Result builtins ─────────────────────────────────────────────────── - - "result_ok" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::ResultOk(Box::new(v))); - BuiltinResult::Handled - } - "result_err" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::ResultErr(Box::new(v))); - BuiltinResult::Handled - } - "result_is_ok" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(matches!(v, Value::ResultOk(_)))); - BuiltinResult::Handled - } - "result_is_err" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(matches!(v, Value::ResultErr(_)))); - BuiltinResult::Handled - } - "result_unwrap" => { - let v = stack.pop().unwrap_or(Value::Nil); - match v { - Value::ResultOk(inner) => stack.push(*inner), - Value::ResultErr(e) => panic!("result_unwrap called on Err: {e}"), - other => stack.push(other), - } - BuiltinResult::Handled - } - "result_unwrap_or" => { - let default = stack.pop().unwrap_or(Value::Nil); - let v = stack.pop().unwrap_or(Value::Nil); - match v { - Value::ResultOk(inner) => stack.push(*inner), - _ => stack.push(default), - } - BuiltinResult::Handled - } - - // ── Optional builtins ───────────────────────────────────────────────── - - "optional_some" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(v); - BuiltinResult::Handled - } - "optional_none" => { - stack.push(Value::Nil); - BuiltinResult::Handled - } - "optional_is_some" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(!matches!(v, Value::Nil))); - BuiltinResult::Handled - } - "optional_is_none" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(matches!(v, Value::Nil))); - BuiltinResult::Handled - } - "optional_unwrap" => { - let v = stack.pop().unwrap_or(Value::Nil); - if matches!(v, Value::Nil) { - panic!("optional_unwrap called on None"); - } - stack.push(v); - BuiltinResult::Handled - } - "optional_unwrap_or" => { - let default = stack.pop().unwrap_or(Value::Nil); - let v = stack.pop().unwrap_or(Value::Nil); - if matches!(v, Value::Nil) { - stack.push(default); - } else { - stack.push(v); - } - BuiltinResult::Handled - } - - // ── HTTP extended builtins ──────────────────────────────────────────── - - "http_put" => { - 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() - .put(&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_delete" => { - let url = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = reqwest::blocking::Client::new() - .delete(&url) - .send() - .and_then(|r| r.text()) - .unwrap_or_else(|e| format!("{{\"error\":\"{e}\"}}") ); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "http_patch" => { - 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() - .patch(&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_head" => { - let url = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = reqwest::blocking::Client::new() - .head(&url) - .send() - .map(|r| Value::Int(r.status().as_u16() as i64)) - .unwrap_or(Value::Int(-1)); - stack.push(result); - BuiltinResult::Handled - } - - // ── Crypto / HMAC / base64 / uuid builtins ─────────────────────────── - - "hmac_sha256" => { - use hmac::{Hmac, Mac}; - use sha2::Sha256; - let data = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let secret = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - type HmacSha256 = Hmac; - let mut mac = HmacSha256::new_from_slice(secret.as_bytes()) - .unwrap_or_else(|_| HmacSha256::new_from_slice(b"invalid").unwrap()); - mac.update(data.as_bytes()); - let result = mac.finalize(); - let hex_str = hex::encode(result.into_bytes()); - stack.push(Value::Str(hex_str)); - BuiltinResult::Handled - } - "base64_url_encode" => { - use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let encoded = URL_SAFE_NO_PAD.encode(s.as_bytes()); - stack.push(Value::Str(encoded)); - BuiltinResult::Handled - } - "base64_url_decode" => { - use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let decoded = URL_SAFE_NO_PAD.decode(s.as_bytes()) - .map(|b| String::from_utf8_lossy(&b).to_string()) - .unwrap_or_default(); - stack.push(Value::Str(decoded)); - BuiltinResult::Handled - } - // base64_encode(s: String) -> String — standard base64 encode. - "base64_encode" => { - use base64::{Engine, engine::general_purpose::STANDARD}; - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let encoded = STANDARD.encode(s.as_bytes()); - stack.push(Value::Str(encoded)); - BuiltinResult::Handled - } - // base64_decode(s: String) -> String — standard base64 decode (UTF-8 output). - "base64_decode" => { - use base64::{Engine, engine::general_purpose::STANDARD}; - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let decoded = STANDARD.decode(s.as_bytes()) - .map(|b| String::from_utf8_lossy(&b).to_string()) - .unwrap_or_default(); - stack.push(Value::Str(decoded)); - BuiltinResult::Handled - } - "unix_timestamp" => { - let secs = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs() as i64) - .unwrap_or(0); - stack.push(Value::Int(secs)); - BuiltinResult::Handled - } - "uuid_v4" => { - let id = uuid::Uuid::new_v4().to_string(); - stack.push(Value::Str(id)); - BuiltinResult::Handled - } - - // ── JSON encode/decode (Value ↔ String) ─────────────────────────────── - - "json_encode" => { - // Encodes a Map, List, Struct or primitive Value to a JSON string. - let v = stack.pop().unwrap_or(Value::Nil); - let jv = el_value_to_json_value(&v); - let s = serde_json::to_string(&jv).unwrap_or_else(|_| "null".to_string()); - stack.push(Value::Str(s)); - BuiltinResult::Handled - } - "json_decode" => { - // Decodes a JSON string to a Value::Map (or List/primitive). - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "null".to_string(), - }; - let jv: serde_json::Value = serde_json::from_str(&s).unwrap_or(serde_json::Value::Null); - stack.push(json_value_to_el_value(&jv)); - BuiltinResult::Handled - } - "json_get_string" => { - // json_get_string(map_or_struct_or_json_str, key) -> String - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let v = stack.pop().unwrap_or(Value::Nil); - let result = match &v { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| match v { - Value::Str(s) => s.clone(), - other => other.to_string(), - }) - .unwrap_or_default(), - Value::Struct { fields, .. } => fields.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| match v { - Value::Str(s) => s.clone(), - other => other.to_string(), - }) - .unwrap_or_default(), - Value::Str(json_str) => { - serde_json::from_str::(json_str) - .ok() - .and_then(|jv| jv.get(&key).and_then(|v| v.as_str().map(str::to_string))) - .unwrap_or_default() - } - _ => String::new(), - }; - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "json_get_int" => { - // json_get_int(map_or_struct_or_json_str, key) -> Int - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let v = stack.pop().unwrap_or(Value::Nil); - let result = match &v { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| match v { - Value::Int(n) => *n, - Value::Str(s) => s.parse().unwrap_or(0), - _ => 0, - }) - .unwrap_or(0), - Value::Struct { fields, .. } => fields.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| match v { - Value::Int(n) => *n, - Value::Str(s) => s.parse().unwrap_or(0), - _ => 0, - }) - .unwrap_or(0), - Value::Str(json_str) => { - serde_json::from_str::(json_str) - .ok() - .and_then(|jv| jv.get(&key).and_then(|v| v.as_i64())) - .unwrap_or(0) - } - _ => 0, - }; - stack.push(Value::Int(result)); - BuiltinResult::Handled - } - "json_get_array" => { - // json_get_array(map_or_struct_or_json_str, key) -> [String] - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let v = stack.pop().unwrap_or(Value::Nil); - let result: Vec = match &v { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| match v { - Value::List(items) => items.clone(), - _ => vec![], - }) - .unwrap_or_default(), - Value::Struct { fields, .. } => fields.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| match v { - Value::List(items) => items.clone(), - _ => vec![], - }) - .unwrap_or_default(), - Value::Str(json_str) => { - serde_json::from_str::(json_str) - .ok() - .and_then(|jv| jv.get(&key).and_then(|v| v.as_array().cloned())) - .map(|arr| arr.iter().map(|item| { - match item { - serde_json::Value::String(s) => Value::Str(s.clone()), - other => Value::Str(other.to_string()), - } - }).collect()) - .unwrap_or_default() - } - _ => vec![], - }; - stack.push(Value::List(result)); - BuiltinResult::Handled - } - - // ── JSON raw field extractor ────────────────────────────────────────── - // json_get_raw(json_str_or_struct, key) -> String - // Returns the JSON-serialized value at key, including objects and arrays. - // Unlike json_get_string which only handles primitive strings, this works - // for any JSON type and always returns a valid JSON string. - "json_get_raw" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let v = stack.pop().unwrap_or(Value::Nil); - let result = match &v { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| { - let jv = el_value_to_json_value(v); - serde_json::to_string(&jv).unwrap_or_default() - }) - .unwrap_or_default(), - Value::Struct { fields, .. } => fields.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| { - let jv = el_value_to_json_value(v); - serde_json::to_string(&jv).unwrap_or_default() - }) - .unwrap_or_default(), - Value::Str(json_str) => { - serde_json::from_str::(json_str) - .ok() - .and_then(|jv| jv.get(&key).cloned()) - .map(|v| serde_json::to_string(&v).unwrap_or_default()) - .unwrap_or_default() - } - _ => String::new(), - }; - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── HTTP auth builtins (Bearer token) ───────────────────────────────── - - "http_get_auth" => { - let token = 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() - .get(&url) - .header("Authorization", format!("Bearer {token}")) - .send() - .and_then(|r| r.text()) - .unwrap_or_default(); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "http_put_auth" => { - let body = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let token = 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() - .put(&url) - .header("Authorization", format!("Bearer {token}")) - .header("Content-Type", "application/json") - .body(body) - .send() - .and_then(|r| r.text()) - .unwrap_or_default(); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "http_delete_auth" => { - let token = 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() - .delete(&url) - .header("Authorization", format!("Bearer {token}")) - .send() - .and_then(|r| r.text()) - .unwrap_or_default(); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - "http_post_auth" => { - let body = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let token = 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("Authorization", format!("Bearer {token}")) - .header("Content-Type", "application/json") - .body(body) - .send() - .and_then(|r| r.text()) - .unwrap_or_default(); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── Form-encoded POST with Basic auth (Stripe, etc.) ───────────────── - - // http_post_form_auth(url, username, body) -> String - // Posts form-encoded body with HTTP Basic auth (username, empty password). - // Used for Stripe API: username = sk_test_... or sk_live_... - "http_post_form_auth" => { - let body = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let username = 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) - .basic_auth(&username, Option::<&str>::None) - .header("Content-Type", "application/x-www-form-urlencoded") - .body(body) - .send() - .and_then(|r| r.text()) - .unwrap_or_default(); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── Engram HTTP helpers (X-Engram-Key auth) ────────────────────────── - - // http_post_engram(url, api_key, body) -> String - // Posts JSON body with X-Engram-Key header (for engram-server API calls). - "http_post_engram" => { - let body = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let api_key = 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 mut req = reqwest::blocking::Client::new() - .post(&url) - .header("Content-Type", "application/json") - .body(body); - if !api_key.is_empty() { - req = req.header("X-Engram-Key", api_key); - } - let result = req.send().and_then(|r| r.text()).unwrap_or_else(|e| format!("{{\"error\":\"{}\"}}", e)); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // http_get_engram(url, api_key) -> String - "http_get_engram" => { - let api_key = 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 mut req = reqwest::blocking::Client::new().get(&url); - if !api_key.is_empty() { - req = req.header("X-Engram-Key", api_key); - } - let result = req.send().and_then(|r| r.text()).unwrap_or_else(|e| format!("{{\"error\":\"{}\"}}", e)); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── I/O builtins ────────────────────────────────────────────────────── - - "readline" => { - let prompt = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - use std::io::Write; - print!("{prompt}"); - let _ = std::io::stdout().flush(); - let mut line = String::new(); - let _ = std::io::stdin().read_line(&mut line); - stack.push(Value::Str(line.trim_end_matches('\n').trim_end_matches('\r').to_string())); - BuiltinResult::Handled - } - - // ── ANSI color builtins ─────────────────────────────────────────────── - - "color_cyan" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - stack.push(Value::Str(format!("\x1b[36m{s}\x1b[0m"))); - BuiltinResult::Handled - } - "color_green" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - stack.push(Value::Str(format!("\x1b[32m{s}\x1b[0m"))); - BuiltinResult::Handled - } - "color_red" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - stack.push(Value::Str(format!("\x1b[31m{s}\x1b[0m"))); - BuiltinResult::Handled - } - "color_yellow" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - stack.push(Value::Str(format!("\x1b[33m{s}\x1b[0m"))); - BuiltinResult::Handled - } - "color_bold" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - stack.push(Value::Str(format!("\x1b[1m{s}\x1b[0m"))); - BuiltinResult::Handled - } - "color_dim" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - stack.push(Value::Str(format!("\x1b[2m{s}\x1b[0m"))); - BuiltinResult::Handled - } - - // ── String / array helpers ──────────────────────────────────────────── - - "string_split_last" => { - // string_split_last(s, delim) -> [everything-before-last, last-part] - // Splits on the LAST occurrence of delim. - 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 = if let Some(pos) = s.rfind(&delim as &str) { - vec![ - Value::Str(s[..pos].to_string()), - Value::Str(s[pos + delim.len()..].to_string()), - ] - } else { - vec![Value::Str(s), Value::Str(String::new())] - }; - stack.push(Value::List(parts)); - BuiltinResult::Handled - } - "array_get" => { - // array_get(arr, idx) -> element at idx - let idx = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 0, - }; - 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 - } - - // ── Engram builtins ─────────────────────────────────────────────────── - - "engram_activate" => { - let query = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let type_name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let results = engram_activate_search(&type_name, &query); - stack.push(Value::List(results)); - BuiltinResult::Handled - } - "engram_relate" => { - let weight = match stack.pop().unwrap_or(Value::Nil) { - Value::Float(f) => f, - Value::Int(n) => n as f64, - _ => 1.0, - }; - let relation = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let to_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let from_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let engram_url = std::env::var("ENGRAM_URL") - .unwrap_or_else(|_| "http://localhost:8742".to_string()); - let api_key = std::env::var("ENGRAM_API_KEY").unwrap_or_default(); - let body = serde_json::json!({ - "from_id": from_id, - "to_id": to_id, - "relation": relation, - "weight": weight - }) - .to_string(); - let mut req = reqwest::blocking::Client::new() - .post(format!("{engram_url}/edges")) - .header("Content-Type", "application/json") - .body(body); - if !api_key.is_empty() { - req = req.header("Authorization", format!("Bearer {api_key}")); - } - let ok = req.send().map(|r| r.status().is_success()).unwrap_or(false); - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "engram_neighbors" => { - let node_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let engram_url = std::env::var("ENGRAM_URL") - .unwrap_or_else(|_| "http://localhost:8742".to_string()); - let api_key = std::env::var("ENGRAM_API_KEY").unwrap_or_default(); - let mut req = reqwest::blocking::Client::new() - .get(format!("{engram_url}/nodes/{node_id}/edges")); - if !api_key.is_empty() { - req = req.header("Authorization", format!("Bearer {api_key}")); - } - let result = req - .send() - .and_then(|r| r.json::()) - .ok(); - let list: Vec = result - .and_then(|v| v.as_array().cloned()) - .unwrap_or_default() - .iter() - .map(json_value_to_el_value) - .collect(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - - // ── Process / system builtins ───────────────────────────────────────── - - "sleep_ms" => { - let ms = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u64, - _ => 0, - }; - std::thread::sleep(std::time::Duration::from_millis(ms)); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "timestamp" => { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default(); - // Format as ISO-8601 approximation: seconds.millis - let secs = ts.as_secs(); - let millis = ts.subsec_millis(); - // Simple ISO-like: YYYY-MM-DDTHH:MM:SS.mmmZ via manual calc - // Use epoch-based formatting - let s = format_epoch_as_iso(secs, millis); - stack.push(Value::Str(s)); - BuiltinResult::Handled - } - - // ── Terminal control builtins ───────────────────────────────────────── - - "term_clear" => { - print!("\x1b[2J\x1b[H"); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "term_size" => { - let (cols, rows) = term_size(); - stack.push(Value::List(vec![Value::Int(cols as i64), Value::Int(rows as i64)])); - BuiltinResult::Handled - } - "cursor_to" => { - let col = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 1, - }; - let row = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 1, - }; - print!("\x1b[{};{}H", row, col); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "cursor_up" => { - let n = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 1, - }; - print!("\x1b[{}A", n); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "cursor_down" => { - let n = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 1, - }; - print!("\x1b[{}B", n); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "cursor_col" => { - let col = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 1, - }; - print!("\x1b[{}G", col); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "term_save_cursor" => { - print!("\x1b[s"); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "term_restore_cursor" => { - print!("\x1b[u"); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "term_clear_line" => { - print!("\x1b[2K\r"); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "print_inline" => { - let v = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - print!("{}", v); - let _ = std::io::Write::flush(&mut std::io::stdout()); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── SSE streaming builtin ───────────────────────────────────────────── - - "http_sse_post" => { - // http_sse_post(url, token, body) -> String - // POSTs and reads an SSE stream, printing each token inline. - // Returns the full assembled response as a String. - let body_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let token_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let url_str = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - - let result = reqwest::blocking::Client::new() - .post(&url_str) - .header("Authorization", format!("Bearer {}", token_str)) - .header("Content-Type", "application/json") - .header("Accept", "text/event-stream") - .body(body_str) - .send(); - - match result { - Err(e) => { - stack.push(Value::Str(format!("error: {}", e))); - } - Ok(resp) => { - use std::io::BufRead; - let mut full_text = String::new(); - let reader = std::io::BufReader::new(resp); - for line in reader.lines() { - let Ok(line) = line else { break }; - if let Some(data) = line.strip_prefix("data: ") { - if data == "[DONE]" { break; } - if let Ok(v) = serde_json::from_str::(data) { - let delta = v.get("delta") - .and_then(|d| d.as_str()) - .or_else(|| v.get("content").and_then(|c| c.as_str())) - .or_else(|| v.get("text").and_then(|t| t.as_str())) - .unwrap_or(""); - if !delta.is_empty() { - print!("{}", delta); - let _ = std::io::Write::flush(&mut std::io::stdout()); - full_text.push_str(delta); - } - } else if !data.is_empty() { - print!("{}", data); - let _ = std::io::Write::flush(&mut std::io::stdout()); - full_text.push_str(data); - } - } - } - stack.push(Value::Str(full_text)); - } - } - BuiltinResult::Handled - } - - // ── Canvas builtins ─────────────────────────────────────────────────── - - "canvas_open" => { - // canvas_open(title: String, width: Int, height: Int) -> Void - let height = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as u32, _ => 800 }; - let width = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as u32, _ => 1200 }; - let title = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "Neuron".into() }; - - // Load font (try SF NS, Arial, DejaVu in order) - let font_data = - std::fs::read("/System/Library/Fonts/SFNS.ttf") - .or_else(|_| std::fs::read("/System/Library/Fonts/Supplemental/Arial.ttf")) - .or_else(|_| std::fs::read("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")) - .unwrap_or_default(); - let font = if font_data.is_empty() { None } else { - fontdue::Font::from_bytes(font_data.as_slice(), fontdue::FontSettings::default()).ok() - }; - - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - cv.title = title; - cv.width = width; - cv.height = height; - cv.font = font; - cv.pixmap = tiny_skia::Pixmap::new(width, height); - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_clear" => { - // canvas_clear(color: String) -> Void - let color_str = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "#000000".into() }; - let color = parse_color(&color_str); - CANVAS.with(|cv| { - if let Some(px) = cv.borrow_mut().pixmap.as_mut() { - px.fill(color); - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_fill_rect" => { - // canvas_fill_rect(x, y, w, h: Int, color: String, radius: Int) -> Void - let radius = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, Value::Float(f) => f as f32, _ => 0.0 }; - let color_str = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "#ffffff".into() }; - let rh = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let rw = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let ry = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let rx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let color = parse_color(&color_str); - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - // Collect clip info before mutably borrowing pixmap - let clip_rect = cv.clips.last().copied(); - if let Some(px) = cv.pixmap.as_mut() { - let mut paint = tiny_skia::Paint::default(); - paint.set_color(color); - paint.anti_alias = true; - let path = { - let mut pb = tiny_skia::PathBuilder::new(); - if radius > 0.0 { - let r = radius.min(rw / 2.0).min(rh / 2.0); - pb.move_to(rx + r, ry); - pb.line_to(rx + rw - r, ry); - pb.quad_to(rx + rw, ry, rx + rw, ry + r); - pb.line_to(rx + rw, ry + rh - r); - pb.quad_to(rx + rw, ry + rh, rx + rw - r, ry + rh); - pb.line_to(rx + r, ry + rh); - pb.quad_to(rx, ry + rh, rx, ry + rh - r); - pb.line_to(rx, ry + r); - pb.quad_to(rx, ry, rx + r, ry); - pb.close(); - } else if let Some(rect) = tiny_skia::Rect::from_xywh(rx, ry, rw.max(0.1), rh.max(0.1)) { - pb.push_rect(rect); - } - pb.finish() - }; - if let Some(path) = path { - // Apply clip if any - let clip_mask = clip_rect.and_then(|(cx, cy, cw, ch)| { - tiny_skia::Rect::from_xywh(cx as f32, cy as f32, cw as f32, ch as f32).and_then(|rect| { - let clip_path = tiny_skia::PathBuilder::from_rect(rect); - let mut mask = tiny_skia::Mask::new(px.width(), px.height())?; - mask.fill_path(&clip_path, tiny_skia::FillRule::Winding, false, tiny_skia::Transform::identity()); - Some(mask) - }) - }); - px.fill_path(&path, &paint, tiny_skia::FillRule::Winding, tiny_skia::Transform::identity(), clip_mask.as_ref()); - } - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_stroke_rect" => { - // canvas_stroke_rect(x, y, w, h: Int, color: String, stroke_w: Int, radius: Int) -> Void - let radius = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, Value::Float(f) => f as f32, _ => 0.0 }; - let stroke_w = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 1.0 }; - let color_str = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "#ffffff".into() }; - let rh = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let rw = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let ry = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let rx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let color = parse_color(&color_str); - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - if let Some(px) = cv.pixmap.as_mut() { - let mut paint = tiny_skia::Paint::default(); - paint.set_color(color); - paint.anti_alias = true; - let mut stroke = tiny_skia::Stroke::default(); - stroke.width = stroke_w; - let path = { - let mut pb = tiny_skia::PathBuilder::new(); - if radius > 0.0 { - let r = radius.min(rw / 2.0).min(rh / 2.0); - pb.move_to(rx + r, ry); - pb.line_to(rx + rw - r, ry); - pb.quad_to(rx + rw, ry, rx + rw, ry + r); - pb.line_to(rx + rw, ry + rh - r); - pb.quad_to(rx + rw, ry + rh, rx + rw - r, ry + rh); - pb.line_to(rx + r, ry + rh); - pb.quad_to(rx, ry + rh, rx, ry + rh - r); - pb.line_to(rx, ry + r); - pb.quad_to(rx, ry, rx + r, ry); - pb.close(); - } else if let Some(rect) = tiny_skia::Rect::from_xywh(rx, ry, rw.max(0.1), rh.max(0.1)) { - pb.push_rect(rect); - } - pb.finish() - }; - if let Some(path) = path { - px.stroke_path(&path, &paint, &stroke, tiny_skia::Transform::identity(), None); - } - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_line" => { - // canvas_line(x1, y1, x2, y2: Int, color: String, line_w: Int) -> Void - let line_w = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 1.0 }; - let color_s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "#ffffff".into() }; - let y2 = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let x2 = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let y1 = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let x1 = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 0.0 }; - let color = parse_color(&color_s); - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - if let Some(px) = cv.pixmap.as_mut() { - let mut paint = tiny_skia::Paint::default(); - paint.set_color(color); - paint.anti_alias = true; - let mut stroke = tiny_skia::Stroke::default(); - stroke.width = line_w; - let mut pb = tiny_skia::PathBuilder::new(); - pb.move_to(x1, y1); - pb.line_to(x2, y2); - if let Some(path) = pb.finish() { - px.stroke_path(&path, &paint, &stroke, tiny_skia::Transform::identity(), None); - } - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_text" => { - // canvas_text(x, y: Int, text: String, size: Int, color: String) -> Void - let color_s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "#ffffff".into() }; - let size = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 14.0 }; - let text = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let y = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - let x = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - let color = parse_color(&color_s); - - let (glyphs, _) = rasterize_text(&text, size); - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - if let Some(px) = cv.pixmap.as_mut() { - let pw = px.width() as i32; - let ph = px.height() as i32; - let cr = (color.red() * 255.0) as u32; - let cg = (color.green() * 255.0) as u32; - let cb = (color.blue() * 255.0) as u32; - for (gx_off, metrics, bitmap) in &glyphs { - let gx = x + gx_off + metrics.xmin; - let gy = y - metrics.height as i32 - metrics.ymin; - for row in 0..metrics.height { - for col in 0..metrics.width { - let alpha = bitmap[row * metrics.width + col]; - if alpha == 0 { continue; } - let px_x = gx + col as i32; - let px_y = gy + row as i32; - if px_x < 0 || px_y < 0 || px_x >= pw || px_y >= ph { continue; } - let idx = (px_y as usize * pw as usize + px_x as usize) * 4; - let data = px.data_mut(); - let a = alpha as u32; - let ia = 255 - a; - data[idx] = ((ia * data[idx] as u32 + a * cr) / 255) as u8; - data[idx + 1] = ((ia * data[idx+1] as u32 + a * cg) / 255) as u8; - data[idx + 2] = ((ia * data[idx+2] as u32 + a * cb) / 255) as u8; - data[idx + 3] = 255; - } - } - } - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_text_width" => { - // canvas_text_width(text: String, size: Int) -> Int - let size = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 14.0 }; - let text = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let (_, w) = rasterize_text(&text, size); - stack.push(Value::Int(w as i64)); - BuiltinResult::Handled - } - - "canvas_text_height" => { - // canvas_text_height(size: Int) -> Int - let size = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as f32, _ => 14.0 }; - stack.push(Value::Int((size * 1.2) as i64)); - BuiltinResult::Handled - } - - "canvas_clip" => { - // canvas_clip(x, y, w, h: Int) -> Void - let h = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as u32, _ => 0 }; - let w = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as u32, _ => 0 }; - let y = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - let x = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - CANVAS.with(|cv| cv.borrow_mut().clips.push((x, y, w, h))); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_unclip" => { - CANVAS.with(|cv| { cv.borrow_mut().clips.pop(); }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "canvas_size" => { - // canvas_size() -> [Int] — [width, height] - let (w, h) = CANVAS.with(|cv| { - let cv = cv.borrow(); - (cv.width as i64, cv.height as i64) - }); - stack.push(Value::List(vec![Value::Int(w), Value::Int(h)])); - BuiltinResult::Handled - } - - "canvas_mouse_pos" => { - // canvas_mouse_pos() -> [Int] — [x, y] - let (x, y) = CANVAS.with(|cv| { - let cv = cv.borrow(); - (cv.mouse_x as i64, cv.mouse_y as i64) - }); - stack.push(Value::List(vec![Value::Int(x), Value::Int(y)])); - BuiltinResult::Handled - } - - "canvas_events" => { - // canvas_events() -> String — JSON array of event objects - let evts = CANVAS.with(|cv| cv.borrow().events.clone()); - let result = if evts.is_empty() { - "[]".to_string() - } else { - format!("[{}]", evts.join(",")) - }; - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - "canvas_swap" => { - // canvas_swap() -> Void — no-op; canvas_run_loop handles presentation - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── Frame-persistent key-value state ──────────────────────────────── - // state_set / state_get use GLOBAL_STATE so values survive between frames - // when canvas_run_loop calls the draw function repeatedly. - - "state_set" => { - // state_set(key: String, val: String) -> Void - let val = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - GLOBAL_STATE.with(|gs| gs.borrow_mut().insert(key, val)); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - "state_get" => { - // state_get(key: String) -> String - let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let val = GLOBAL_STATE.with(|gs| gs.borrow().get(&key).cloned().unwrap_or_default()); - stack.push(Value::Str(val)); - BuiltinResult::Handled - } - - "canvas_run_loop" => { - // canvas_run_loop(draw_fn: String) -> Void (never returns) - let draw_fn = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "draw".into() }; - - let arc_instructions = SERVE_INSTRUCTIONS.with(|si| si.borrow().clone()) - .expect("canvas_run_loop: no instructions stored (call canvas_open first and ensure interpreter stored them)"); - let arc_fn_table = SERVE_FN_TABLE.with(|sf| sf.borrow().clone()) - .expect("canvas_run_loop: no fn_table stored"); - - let (title, width, height) = CANVAS.with(|cv| { - let cv = cv.borrow(); - (cv.title.clone(), cv.width, cv.height) - }); - - use winit::event::{Event, WindowEvent, MouseButton, ElementState}; - use winit::event_loop::{ControlFlow, EventLoop}; - use winit::keyboard::{Key, NamedKey}; - use winit::window::WindowBuilder; - - let event_loop = EventLoop::new().expect("failed to create event loop"); - let window = WindowBuilder::new() - .with_title(&title) - .with_inner_size(winit::dpi::LogicalSize::new(width, height)) - .build(&event_loop) - .expect("failed to create window"); - - let context = unsafe { softbuffer::Context::new(&window) }.expect("softbuffer context"); - let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.expect("softbuffer surface"); - - let mut frame_events: Vec = Vec::new(); - - let _ = event_loop.run(move |event, elwt| { - elwt.set_control_flow(ControlFlow::Poll); - - match event { - Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { - std::process::exit(0); - } - Event::WindowEvent { event: WindowEvent::Resized(physical), .. } => { - let nw = physical.width.max(1); - let nh = physical.height.max(1); - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - cv.width = nw; - cv.height = nh; - cv.pixmap = tiny_skia::Pixmap::new(nw, nh); - }); - frame_events.push(format!(r#"{{"type":"resize","w":{},"h":{}}}"#, nw, nh)); - } - Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => { - let mx = position.x as i32; - let my = position.y as i32; - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - cv.mouse_x = mx; - cv.mouse_y = my; - }); - frame_events.push(format!(r#"{{"type":"mouse_move","x":{},"y":{}}}"#, mx, my)); - } - Event::WindowEvent { event: WindowEvent::MouseInput { state, button, .. }, .. } => { - let btn_str = match button { MouseButton::Left => "left", MouseButton::Right => "right", _ => "other" }; - let ev_type = match state { ElementState::Pressed => "mouse_down", ElementState::Released => "mouse_up" }; - let (mx, my) = CANVAS.with(|cv| { let cv = cv.borrow(); (cv.mouse_x, cv.mouse_y) }); - frame_events.push(format!(r#"{{"type":"{}","x":{},"y":{},"button":"{}"}}"#, ev_type, mx, my, btn_str)); - if state == ElementState::Released && button == MouseButton::Left { - frame_events.push(format!(r#"{{"type":"mouse_click","x":{},"y":{},"button":"left"}}"#, mx, my)); - } - } - Event::WindowEvent { event: WindowEvent::MouseWheel { delta, .. }, .. } => { - let (dx, dy) = match delta { - winit::event::MouseScrollDelta::LineDelta(x, y) => (x as i32, y as i32), - winit::event::MouseScrollDelta::PixelDelta(p) => (p.x as i32, p.y as i32), - }; - frame_events.push(format!(r#"{{"type":"scroll","dx":{},"dy":{}}}"#, dx, dy)); - } - Event::WindowEvent { event: WindowEvent::KeyboardInput { event: ref kev, .. }, .. } => { - // Emit char events for printable keys - if kev.state == ElementState::Pressed { - if let Key::Character(ref s) = kev.logical_key { - let ch_str = s.as_str(); - if !ch_str.chars().all(|c| c.is_control()) { - let escaped = ch_str.replace('"', "\\\""); - frame_events.push(format!(r#"{{"type":"char","char":"{}"}}"#, escaped)); - } - } - } - // Emit key_down events for named keys - if kev.state == ElementState::Pressed { - let key_str = match &kev.logical_key { - Key::Named(NamedKey::Enter) => "Return", - Key::Named(NamedKey::Escape) => "Escape", - Key::Named(NamedKey::Backspace) => "Backspace", - Key::Named(NamedKey::Delete) => "Delete", - Key::Named(NamedKey::ArrowLeft) => "ArrowLeft", - Key::Named(NamedKey::ArrowRight) => "ArrowRight", - Key::Named(NamedKey::ArrowUp) => "ArrowUp", - Key::Named(NamedKey::ArrowDown) => "ArrowDown", - Key::Named(NamedKey::Tab) => "Tab", - Key::Named(NamedKey::Home) => "Home", - Key::Named(NamedKey::End) => "End", - _ => "", - }; - if !key_str.is_empty() { - frame_events.push(format!(r#"{{"type":"key_down","key":"{}"}}"#, key_str)); - } - } - } - Event::AboutToWait => { - // Set this frame's events, then call the Engram draw function - CANVAS.with(|cv| { - cv.borrow_mut().events = std::mem::take(&mut frame_events); - }); - - if let Some(&entry) = arc_fn_table.get(draw_fn.as_str()) { - run_sub_interpreter(&arc_instructions, &arc_fn_table, entry); - } - - // Present the pixmap to the screen - let (w, h) = CANVAS.with(|cv| { let cv = cv.borrow(); (cv.width, cv.height) }); - let nz_w = std::num::NonZeroU32::new(w.max(1)).unwrap(); - let nz_h = std::num::NonZeroU32::new(h.max(1)).unwrap(); - if surface.resize(nz_w, nz_h).is_ok() { - if let Ok(mut buf) = surface.buffer_mut() { - CANVAS.with(|cv| { - let cv = cv.borrow(); - if let Some(px) = &cv.pixmap { - let pixels = px.data(); // RGBA bytes - for (i, chunk) in pixels.chunks_exact(4).enumerate() { - // softbuffer expects 0x00RRGGBB in native u32 - buf[i] = ((chunk[0] as u32) << 16) - | ((chunk[1] as u32) << 8) - | (chunk[2] as u32); - } - } - }); - let _ = buf.present(); - } - } - - window.request_redraw(); - } - _ => {} - } - }); - // event_loop.run never returns normally (exits via std::process::exit) - unreachable!() - } - - "canvas_image" => { - // canvas_image(path: String, x: Int, y: Int, w: Int, h: Int) -> Void - // Draws a PNG image scaled to w×h at (x, y) with alpha blending. - let draw_h = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - let draw_w = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - let dy = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - let dx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as i32, _ => 0 }; - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } }; - - if draw_w <= 0 || draw_h <= 0 { stack.push(Value::Nil); return BuiltinResult::Handled; } - - use image::GenericImageView; - if let Ok(img) = image::open(&path) { - let img = img.resize_exact(draw_w as u32, draw_h as u32, image::imageops::FilterType::Lanczos3); - let rgba = img.to_rgba8(); - CANVAS.with(|cv| { - let mut cv = cv.borrow_mut(); - if let Some(px) = cv.pixmap.as_mut() { - let pw = px.width() as i32; - let ph = px.height() as i32; - let data = px.data_mut(); - for row in 0..draw_h { - for col in 0..draw_w { - let ix = dx + col; - let iy = dy + row; - if ix < 0 || iy < 0 || ix >= pw || iy >= ph { continue; } - let p = rgba.get_pixel(col as u32, row as u32); - let sa = p[3] as u32; - if sa == 0 { continue; } - let ia = 255 - sa; - let idx = (iy as usize * pw as usize + ix as usize) * 4; - data[idx] = ((ia * data[idx] as u32 + sa * p[0] as u32) / 255) as u8; - data[idx + 1] = ((ia * data[idx+1] as u32 + sa * p[1] as u32) / 255) as u8; - data[idx + 2] = ((ia * data[idx+2] as u32 + sa * p[2] as u32) / 255) as u8; - data[idx + 3] = ((ia * data[idx+3] as u32 + sa * sa) / 255) as u8; - } - } - } - }); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── TCP builtins ────────────────────────────────────────────────────── - - // tcp_connect(host, port) -> String (handle like "tcp:1") - "tcp_connect" => { - let port = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u16, - Value::Str(s) => s.parse::().unwrap_or(0), - _ => 0, - }; - let host = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let addr = format!("{host}:{port}"); - match std::net::TcpStream::connect(&addr) { - Ok(stream) => { - let _ = stream.set_read_timeout(Some(std::time::Duration::from_secs(30))); - let handle = next_net_id("tcp"); - TCP_STREAMS.with(|m| m.borrow_mut().insert(handle.clone(), stream)); - stack.push(Value::Str(handle)); - } - Err(e) => { - eprintln!("tcp_connect error: {e}"); - stack.push(Value::Str(String::new())); - } - } - BuiltinResult::Handled - } - - // tcp_listen(port) -> String (handle like "tcpserver:1") - "tcp_listen" => { - let port = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u16, - Value::Str(s) => s.parse::().unwrap_or(0), - _ => 0, - }; - let addr = format!("0.0.0.0:{port}"); - match std::net::TcpListener::bind(&addr) { - Ok(listener) => { - let handle = next_net_id("tcpserver"); - TCP_LISTENERS.with(|m| m.borrow_mut().insert(handle.clone(), listener)); - stack.push(Value::Str(handle)); - } - Err(e) => { - eprintln!("tcp_listen error: {e}"); - stack.push(Value::Str(String::new())); - } - } - BuiltinResult::Handled - } - - // tcp_accept(server_handle) -> String (client handle like "tcp:2") - "tcp_accept" => { - let server_handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - // Take the listener out temporarily to call accept(), then put it back. - let listener = TCP_LISTENERS.with(|m| { - m.borrow_mut().remove(&server_handle) - }); - match listener { - Some(lst) => { - let result = lst.accept(); - // Put the listener back regardless. - TCP_LISTENERS.with(|m| m.borrow_mut().insert(server_handle.clone(), lst)); - match result { - Ok((stream, _addr)) => { - let _ = stream.set_read_timeout(Some(std::time::Duration::from_secs(30))); - let handle = next_net_id("tcp"); - TCP_STREAMS.with(|m| m.borrow_mut().insert(handle.clone(), stream)); - stack.push(Value::Str(handle)); - } - Err(e) => { - eprintln!("tcp_accept error: {e}"); - stack.push(Value::Str(String::new())); - } - } - } - None => { - // Listener was taken out but accept already happened — try to return it - eprintln!("tcp_accept: server handle not found: {server_handle}"); - stack.push(Value::Str(String::new())); - } - } - BuiltinResult::Handled - } - - // tcp_send(handle, data) - "tcp_send" => { - let data = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - TCP_STREAMS.with(|m| { - if let Some(stream) = m.borrow_mut().get_mut(&handle) { - use std::io::Write; - let _ = stream.write_all(data.as_bytes()); - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // tcp_recv(handle, max_bytes) -> String - "tcp_recv" => { - let max_bytes = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 4096, - }; - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = TCP_STREAMS.with(|m| { - use std::io::Read; - if let Some(stream) = m.borrow_mut().get_mut(&handle) { - let mut buf = vec![0u8; max_bytes]; - match stream.read(&mut buf) { - Ok(0) => String::new(), - Ok(n) => String::from_utf8_lossy(&buf[..n]).into_owned(), - Err(_) => String::new(), - } - } else { - String::new() - } - }); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // tcp_close(handle) - "tcp_close" => { - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - if handle.starts_with("tcpserver:") { - TCP_LISTENERS.with(|m| { m.borrow_mut().remove(&handle); }); - } else { - TCP_STREAMS.with(|m| { - if let Some(stream) = m.borrow_mut().remove(&handle) { - let _ = stream.shutdown(std::net::Shutdown::Both); - } - }); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── UDP builtins ────────────────────────────────────────────────────── - - // udp_bind(host, port) -> String (handle like "udp:1") - "udp_bind" => { - let port = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u16, - Value::Str(s) => s.parse::().unwrap_or(0), - _ => 0, - }; - let host = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "0.0.0.0".to_string(), - }; - let addr = format!("{host}:{port}"); - match std::net::UdpSocket::bind(&addr) { - Ok(socket) => { - let _ = socket.set_read_timeout(Some(std::time::Duration::from_secs(30))); - let handle = next_net_id("udp"); - UDP_SOCKETS.with(|m| m.borrow_mut().insert(handle.clone(), socket)); - stack.push(Value::Str(handle)); - } - Err(e) => { - eprintln!("udp_bind error: {e}"); - stack.push(Value::Str(String::new())); - } - } - BuiltinResult::Handled - } - - // udp_send(handle, host, port, data) - "udp_send" => { - let data = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let port = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u16, - Value::Str(s) => s.parse::().unwrap_or(0), - _ => 0, - }; - let host = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let dest = format!("{host}:{port}"); - UDP_SOCKETS.with(|m| { - if let Some(sock) = m.borrow().get(&handle) { - let _ = sock.send_to(data.as_bytes(), &dest); - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // udp_recv(handle, max_bytes) -> String (JSON: {"data":"...","from":"host:port"}) - "udp_recv" => { - let max_bytes = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as usize, - _ => 4096, - }; - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = UDP_SOCKETS.with(|m| { - if let Some(sock) = m.borrow().get(&handle) { - let mut buf = vec![0u8; max_bytes]; - match sock.recv_from(&mut buf) { - Ok((n, from)) => { - let data = String::from_utf8_lossy(&buf[..n]).into_owned(); - let from_str = from.to_string(); - // Escape for JSON - let data_escaped = data.replace('\\', "\\\\").replace('"', "\\\""); - format!("{{\"data\":\"{data_escaped}\",\"from\":\"{from_str}\"}}") - } - Err(_) => String::new(), - } - } else { - String::new() - } - }); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // udp_close(handle) - "udp_close" => { - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - UDP_SOCKETS.with(|m| { m.borrow_mut().remove(&handle); }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── WebSocket builtins ──────────────────────────────────────────────── - - // ws_connect(url) -> String (handle like "ws:1") - "ws_connect" => { - let url = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - match tungstenite::connect(&url) { - Ok((ws, _response)) => { - let handle = next_net_id("ws"); - WS_SOCKETS.with(|m| m.borrow_mut().insert(handle.clone(), ws)); - stack.push(Value::Str(handle)); - } - Err(e) => { - eprintln!("ws_connect error: {e}"); - stack.push(Value::Str(String::new())); - } - } - BuiltinResult::Handled - } - - // ws_send(handle, msg) - "ws_send" => { - let msg = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - WS_SOCKETS.with(|m| { - if let Some(ws) = m.borrow_mut().get_mut(&handle) { - let _ = ws.send(tungstenite::Message::Text(msg.into())); - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ws_recv(handle) -> String (text of next message, "" on close/error) - "ws_recv" => { - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = WS_SOCKETS.with(|m| { - if let Some(ws) = m.borrow_mut().get_mut(&handle) { - match ws.read() { - Ok(tungstenite::Message::Text(t)) => t.to_string(), - Ok(tungstenite::Message::Binary(b)) => { - String::from_utf8_lossy(&b).into_owned() - } - Ok(tungstenite::Message::Close(_)) | Err(_) => String::new(), - _ => String::new(), - } - } else { - String::new() - } - }); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ws_close(handle) - "ws_close" => { - let handle = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - WS_SOCKETS.with(|m| { - if let Some(mut ws) = m.borrow_mut().remove(&handle) { - let _ = ws.close(None); - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── Math additions ──────────────────────────────────────────────────── - "math_sin" => { - let a = stack.pop().unwrap_or(Value::Float(0.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(v.sin())); BuiltinResult::Handled - } - "math_cos" => { - let a = stack.pop().unwrap_or(Value::Float(0.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(v.cos())); BuiltinResult::Handled - } - "math_tan" => { - let a = stack.pop().unwrap_or(Value::Float(0.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(v.tan())); BuiltinResult::Handled - } - "math_asin" => { - let a = stack.pop().unwrap_or(Value::Float(0.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(v.asin())); BuiltinResult::Handled - } - "math_acos" => { - let a = stack.pop().unwrap_or(Value::Float(0.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(v.acos())); BuiltinResult::Handled - } - "math_atan2" => { - let x = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - let y = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(y.atan2(x))); BuiltinResult::Handled - } - "math_exp" => { - let a = stack.pop().unwrap_or(Value::Float(0.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(v.exp())); BuiltinResult::Handled - } - "math_ln" => { - let a = stack.pop().unwrap_or(Value::Float(1.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - stack.push(Value::Float(v.ln())); BuiltinResult::Handled - } - "math_log2" => { - let a = stack.pop().unwrap_or(Value::Float(1.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - stack.push(Value::Float(v.log2())); BuiltinResult::Handled - } - "math_log10" => { - let a = stack.pop().unwrap_or(Value::Float(1.0)); - let v = match a { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - stack.push(Value::Float(v.log10())); BuiltinResult::Handled - } - "math_mod" => { - let b = match stack.pop().unwrap_or(Value::Int(1)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - let a = match stack.pop().unwrap_or(Value::Int(0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(a % b)); BuiltinResult::Handled - } - "math_pi" => { stack.push(Value::Float(std::f64::consts::PI)); BuiltinResult::Handled } - "math_e" => { stack.push(Value::Float(std::f64::consts::E)); BuiltinResult::Handled } - "int_to_float" => { - let a = stack.pop().unwrap_or(Value::Int(0)); - let v = match a { Value::Int(i) => i as f64, Value::Float(f) => f, _ => 0.0 }; - stack.push(Value::Float(v)); BuiltinResult::Handled - } - "float_to_int" => { - let a = stack.pop().unwrap_or(Value::Float(0.0)); - let v = match a { Value::Float(f) => f as i64, Value::Int(i) => i, _ => 0 }; - stack.push(Value::Int(v)); BuiltinResult::Handled - } - "is_nil" => { - let a = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(matches!(a, Value::Nil))); BuiltinResult::Handled - } - "unwrap_or" => { - let default_val = stack.pop().unwrap_or(Value::Nil); - let val = stack.pop().unwrap_or(Value::Nil); - stack.push(match val { Value::Nil => default_val, other => other }); - BuiltinResult::Handled - } - "decimal_add" => { - let b = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - let a = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(a + b)); BuiltinResult::Handled - } - "decimal_sub" => { - let b = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - let a = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(a - b)); BuiltinResult::Handled - } - "decimal_mul" => { - let b = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - let a = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - stack.push(Value::Float(a * b)); BuiltinResult::Handled - } - "decimal_div" => { - let b = match stack.pop().unwrap_or(Value::Float(1.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 1.0 }; - let a = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Float(if b == 0.0 { 0.0 } else { a / b })); BuiltinResult::Handled - } - "decimal_round" => { - let decimals = match stack.pop().unwrap_or(Value::Int(2)) { Value::Int(i) => i, _ => 2 }; - let f = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - let factor = 10f64.powi(decimals as i32); - stack.push(Value::Float((f * factor).round() / factor)); BuiltinResult::Handled - } - - // ── String additions ────────────────────────────────────────────────── - "str_char_at" => { - let i = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 }; - let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let ch = s.chars().nth(i).map(|c| c.to_string()).unwrap_or_default(); - stack.push(Value::Str(ch)); BuiltinResult::Handled - } - "str_char_code" => { - let i = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 }; - let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let code = s.chars().nth(i).map(|c| c as i64).unwrap_or(0); - stack.push(Value::Int(code)); BuiltinResult::Handled - } - "str_from_char_code" => { - let n = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as u32, _ => 0 }; - let ch = char::from_u32(n).map(|c| c.to_string()).unwrap_or_default(); - stack.push(Value::Str(ch)); BuiltinResult::Handled - } - "str_pad_left" => { - let pad = match stack.pop().unwrap_or(Value::Str(" ".to_string())) { Value::Str(s) => s, _ => " ".to_string() }; - let width = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 }; - let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let pad_char = pad.chars().next().unwrap_or(' '); - let cur_len = s.chars().count(); - let result = if cur_len >= width { s } else { - let pad_count = width - cur_len; - let mut r = String::new(); - for _ in 0..pad_count { r.push(pad_char); } - r.push_str(&s); - r - }; - stack.push(Value::Str(result)); BuiltinResult::Handled - } - "str_pad_right" => { - let pad = match stack.pop().unwrap_or(Value::Str(" ".to_string())) { Value::Str(s) => s, _ => " ".to_string() }; - let width = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i as usize, _ => 0 }; - let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let pad_char = pad.chars().next().unwrap_or(' '); - let cur_len = s.chars().count(); - let result = if cur_len >= width { s } else { - let mut r = s; - let pad_count = width - cur_len; - for _ in 0..pad_count { r.push(pad_char); } - r - }; - stack.push(Value::Str(result)); BuiltinResult::Handled - } - "format_float" => { - let decimals = match stack.pop().unwrap_or(Value::Int(2)) { Value::Int(i) => i, _ => 2 }; - let f = match stack.pop().unwrap_or(Value::Float(0.0)) { Value::Float(f) => f, Value::Int(i) => i as f64, _ => 0.0 }; - stack.push(Value::Str(format!("{:.prec$}", f, prec = decimals as usize))); - BuiltinResult::Handled - } - "str_format" => { - let map = match stack.pop().unwrap_or(Value::Map(vec![])) { Value::Map(m) => m, _ => vec![] }; - let template = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let mut result = template; - for (key, val) in &map { - let placeholder = format!("{{{}}}", key); - let replacement = match val { - Value::Str(s) => s.clone(), - Value::Int(i) => i.to_string(), - Value::Float(f) => f.to_string(), - Value::Bool(b) => b.to_string(), - _ => String::new(), - }; - result = result.replace(&placeholder, &replacement); - } - stack.push(Value::Str(result)); BuiltinResult::Handled - } - - // ── List additions ──────────────────────────────────────────────────── - "list_push" | "native_list_append" => { - let item = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = list; - new_list.push(item); - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "list_pop" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = list; - new_list.pop(); - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "list_pop_front" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let new_list = if list.is_empty() { vec![] } else { list[1..].to_vec() }; - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "list_peek_last" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let val = list.last().cloned().unwrap_or(Value::Nil); - stack.push(val); BuiltinResult::Handled - } - "list_range" => { - let end = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let start = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let list: Vec = (start..end).map(Value::Int).collect(); - stack.push(Value::List(list)); BuiltinResult::Handled - } - "list_new" | "native_list_empty" => { stack.push(Value::List(vec![])); BuiltinResult::Handled } - "list_empty" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - stack.push(Value::Bool(list.is_empty())); BuiltinResult::Handled - } - "list_map" | "list_filter" | "list_reduce" | "fn_ref" => { - // These require fn_table access; handled in NotBuiltin branch of main loop. - BuiltinResult::NotBuiltin - } - - // ── List helpers (append / set / concat) ────────────────────────────── - "list_append" => { - // list_append(list, item) -> [item, ...] - let item = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = list; - new_list.push(item); - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "list_set" => { - // list_set(list, idx, value) -> new list with item at idx replaced - let value = stack.pop().unwrap_or(Value::Nil); - let idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = list; - if idx >= 0 && (idx as usize) < new_list.len() { - new_list[idx as usize] = value; - } - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "list_concat" => { - // list_concat(a, b) -> combined list - let b = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let a = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = a; - new_list.extend(b); - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "list_slice" => { - // list_slice(list, start, end) -> sublist [start..end) - let end_idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let start_idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let len = list.len() as i64; - let s = start_idx.max(0) as usize; - let e = end_idx.min(len) as usize; - let sliced = if s < e && s < list.len() { list[s..e.min(list.len())].to_vec() } else { vec![] }; - stack.push(Value::List(sliced)); BuiltinResult::Handled - } - "list_reverse" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut rev = list; - rev.reverse(); - stack.push(Value::List(rev)); BuiltinResult::Handled - } - "list_contains" => { - // list_contains(list, item) -> Bool — string equality check - let item = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, other => other.to_string() }; - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let found = list.iter().any(|v| { - let vs = match v { Value::Str(s) => s.clone(), other => other.to_string() }; - vs == item - }); - stack.push(Value::Bool(found)); BuiltinResult::Handled - } - - // ── String character mutation ────────────────────────────────────────── - "str_set_char" => { - // str_set_char(s, idx, ch) -> new string with ch at position idx - let ch = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let ch_char = ch.chars().next().unwrap_or(' '); - let mut chars: Vec = s.chars().collect(); - if idx >= 0 && (idx as usize) < chars.len() { - chars[idx as usize] = ch_char; - } - stack.push(Value::Str(chars.into_iter().collect())); BuiltinResult::Handled - } - - // ── Shell execution ─────────────────────────────────────────────────── - "shell_exec" => { - // shell_exec(cmd: String) -> String — execute shell command, return stdout+stderr - let cmd = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let output = std::process::Command::new("sh") - .arg("-c") - .arg(&cmd) - .output(); - let result = match output { - Ok(o) => { - let stdout = String::from_utf8_lossy(&o.stdout).to_string(); - let stderr = String::from_utf8_lossy(&o.stderr).to_string(); - if stdout.is_empty() { stderr } else { stdout } - } - Err(e) => format!("error: {}", e), - }; - stack.push(Value::Str(result)); BuiltinResult::Handled - } - - // ── TTY write (raw ANSI output without newline) ─────────────────────── - "native_tty_write" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, other => other.to_string() }; - use std::io::Write; - print!("{}", s); - let _ = std::io::stdout().flush(); - stack.push(Value::Nil); BuiltinResult::Handled - } - "native_tty_size" => { - // Returns "cols,rows" string - let (cols, rows) = term_size(); - stack.push(Value::Str(format!("{},{}", cols, rows))); BuiltinResult::Handled - } - - // ── Stack aliases ───────────────────────────────────────────────────── - "stack_push" => { - let item = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = list; new_list.push(item); - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "stack_pop" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = list; new_list.pop(); - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "stack_peek" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let val = list.last().cloned().unwrap_or(Value::Nil); - stack.push(val); BuiltinResult::Handled - } - "stack_new" => { stack.push(Value::List(vec![])); BuiltinResult::Handled } - - // ── Queue aliases ───────────────────────────────────────────────────── - "queue_enqueue" => { - let item = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let mut new_list = list; new_list.push(item); - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "queue_dequeue" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let new_list = if list.is_empty() { vec![] } else { list[1..].to_vec() }; - stack.push(Value::List(new_list)); BuiltinResult::Handled - } - "queue_peek" => { - let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; - let val = list.first().cloned().unwrap_or(Value::Nil); - stack.push(val); BuiltinResult::Handled - } - "queue_new" => { stack.push(Value::List(vec![])); BuiltinResult::Handled } - - // ── Time builtins ───────────────────────────────────────────────────── - "time_now_utc" => { - use std::time::{SystemTime, UNIX_EPOCH}; - let ms = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as i64; - stack.push(Value::Int(ms)); BuiltinResult::Handled - } - "time_to_parts" => { - let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let (year, month, day, hour, min, sec, ms) = epoch_ms_to_parts(ts); - let days_since_epoch = ts / 86_400_000; - let weekday_num = ((days_since_epoch % 7 + 4) % 7 + 7) % 7; - let weekday_names = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]; - let weekday_name = weekday_names[weekday_num as usize].to_string(); - let pairs = vec![ - ("year".to_string(), Value::Int(year as i64)), - ("month".to_string(), Value::Int(month as i64)), - ("day".to_string(), Value::Int(day as i64)), - ("hour".to_string(), Value::Int(hour as i64)), - ("minute".to_string(), Value::Int(min as i64)), - ("second".to_string(), Value::Int(sec as i64)), - ("ms".to_string(), Value::Int(ms as i64)), - ("weekday_num".to_string(), Value::Int(weekday_num)), - ("weekday_name".to_string(), Value::Str(weekday_name)), - ("tz".to_string(), Value::Str("UTC".to_string())), - ]; - stack.push(Value::Map(pairs)); BuiltinResult::Handled - } - "time_from_parts" => { - let parts = match stack.pop().unwrap_or(Value::Map(vec![])) { Value::Map(m) => m, _ => vec![] }; - let get_i = |m: &Vec<(String,Value)>, k: &str, def: i64| -> i64 { - m.iter().find(|(key,_)| key == k).and_then(|(_,v)| if let Value::Int(i) = v { Some(*i) } else { None }).unwrap_or(def) - }; - let year = get_i(&parts, "year", 1970) as i32; - let month = get_i(&parts, "month", 1) as u32; - let day = get_i(&parts, "day", 1) as u32; - let hour = get_i(&parts, "hour", 0) as u32; - let min = get_i(&parts, "minute", 0) as u32; - let sec = get_i(&parts, "second", 0) as u32; - let ms = get_i(&parts, "ms", 0) as u32; - stack.push(Value::Int(parts_to_epoch_ms(year, month, day, hour, min, sec, ms))); - BuiltinResult::Handled - } - "time_format" => { - let fmt = match stack.pop().unwrap_or(Value::Str("ISO".to_string())) { Value::Str(s) => s, _ => "ISO".to_string() }; - let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let (year, month, day, hour, min, sec, _ms) = epoch_ms_to_parts(ts); - let result = if fmt == "ISO" || fmt == "iso" { - format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, day, hour, min, sec) - } else { - fmt.replace("{YYYY}", &format!("{:04}", year)) - .replace("{MM}", &format!("{:02}", month)) - .replace("{DD}", &format!("{:02}", day)) - .replace("{HH}", &format!("{:02}", hour)) - .replace("{mm}", &format!("{:02}", min)) - .replace("{ss}", &format!("{:02}", sec)) - }; - stack.push(Value::Str(result)); BuiltinResult::Handled - } - "time_parse" => { - let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let result = (|| -> Option { - let s = s.trim().trim_end_matches('Z'); - let (date_part, time_part) = if let Some(t_pos) = s.find('T') { - (&s[..t_pos], Some(&s[t_pos+1..])) - } else { (s, None) }; - let dp: Vec<&str> = date_part.split('-').collect(); - if dp.len() < 3 { return None; } - let year = dp[0].parse::().ok()?; - let month = dp[1].parse::().ok()?; - let day = dp[2].parse::().ok()?; - let (hour, min, sec) = if let Some(tp) = time_part { - let tp = tp.trim_end_matches('Z'); - let tp_parts: Vec<&str> = tp.split(':').collect(); - let h = tp_parts.get(0).and_then(|s| s.parse::().ok()).unwrap_or(0); - let m = tp_parts.get(1).and_then(|s| s.parse::().ok()).unwrap_or(0); - let sv = tp_parts.get(2).and_then(|s| s.parse::().ok()).unwrap_or(0.0); - (h, m, sv as u32) - } else { (0, 0, 0) }; - Some(parts_to_epoch_ms(year, month, day, hour, min, sec, 0)) - })().unwrap_or(0); - stack.push(Value::Int(result)); BuiltinResult::Handled - } - "time_add" => { - let unit = match stack.pop().unwrap_or(Value::Str("ms".to_string())) { Value::Str(s) => s, _ => "ms".to_string() }; - let amt = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let result = match unit.as_str() { - "ms" => ts + amt, - "sec" | "second" | "seconds" => ts + amt * 1_000, - "min" | "minute" | "minutes" => ts + amt * 60_000, - "hour" | "hours" => ts + amt * 3_600_000, - "day" | "days" => ts + amt * 86_400_000, - "week" | "weeks" => ts + amt * 604_800_000, - "month" | "months" => { - let (y, m, d, h, mn, s, ms) = epoch_ms_to_parts(ts); - let total_months = (y as i64) * 12 + (m as i64 - 1) + amt; - let ny = (total_months / 12) as i32; - let nm = (total_months % 12 + 1) as u32; - let nd = d.min(days_in_month(ny, nm)); - parts_to_epoch_ms(ny, nm, nd, h, mn, s, ms) - } - "year" | "years" => { - let (y, m, d, h, mn, s, ms) = epoch_ms_to_parts(ts); - let ny = y + amt as i32; - let nd = d.min(days_in_month(ny, m)); - parts_to_epoch_ms(ny, m, nd, h, mn, s, ms) - } - _ => ts + amt, - }; - stack.push(Value::Int(result)); BuiltinResult::Handled - } - "time_diff" => { - let unit = match stack.pop().unwrap_or(Value::Str("ms".to_string())) { Value::Str(s) => s, _ => "ms".to_string() }; - let ts2 = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let ts1 = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let diff_ms = ts2 - ts1; - let result = match unit.as_str() { - "ms" => diff_ms, - "sec" | "second" | "seconds" => diff_ms / 1_000, - "min" | "minute" | "minutes" => diff_ms / 60_000, - "hour" | "hours" => diff_ms / 3_600_000, - "day" | "days" => diff_ms / 86_400_000, - "week" | "weeks" => diff_ms / 604_800_000, - _ => diff_ms, - }; - stack.push(Value::Int(result)); BuiltinResult::Handled - } - "time_start_of" => { - let unit = match stack.pop().unwrap_or(Value::Str("day".to_string())) { Value::Str(s) => s, _ => "day".to_string() }; - let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let (year, month, day, hour, _min, _sec, _ms) = epoch_ms_to_parts(ts); - let result = match unit.as_str() { - "minute" => { let (y,mo,d,h,mn,_,_) = epoch_ms_to_parts(ts); parts_to_epoch_ms(y,mo,d,h,mn,0,0) } - "hour" => parts_to_epoch_ms(year, month, day, hour, 0, 0, 0), - "day" => parts_to_epoch_ms(year, month, day, 0, 0, 0, 0), - "week" => { - let days_since_epoch = ts / 86_400_000; - let weekday = ((days_since_epoch % 7 + 4) % 7 + 7) % 7; - let monday_offset = if weekday == 0 { 6 } else { weekday - 1 }; - (days_since_epoch - monday_offset) * 86_400_000 - } - "month" => parts_to_epoch_ms(year, month, 1, 0, 0, 0, 0), - "year" => parts_to_epoch_ms(year, 1, 1, 0, 0, 0, 0), - _ => ts, - }; - stack.push(Value::Int(result)); BuiltinResult::Handled - } - "time_tz_offset" => { - let tz = match stack.pop().unwrap_or(Value::Str("UTC".to_string())) { Value::Str(s) => s, _ => "UTC".to_string() }; - let offset: i64 = match tz.as_str() { - "UTC" | "GMT" => 0, - "EST" | "America/New_York" => -300, - "CST" | "America/Chicago" => -360, - "MST" | "America/Denver" => -420, - "PST" | "America/Los_Angeles" => -480, - "EDT" => -240, "CDT" => -300, "MDT" => -360, "PDT" => -420, - "CET" => 60, "CEST" => 120, "EET" => 120, "EEST" => 180, - "IST" => 330, "JST" => 540, "KST" => 540, "CST_CHINA" => 480, - "AEST" => 600, "AEDT" => 660, "NZST" => 720, "BRT" => -180, "ART" => -180, - _ => 0, - }; - stack.push(Value::Int(offset)); BuiltinResult::Handled - } - "time_to_tz" => { - let tz = match stack.pop().unwrap_or(Value::Str("UTC".to_string())) { Value::Str(s) => s, _ => "UTC".to_string() }; - let ts = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; - let offset_minutes: i64 = match tz.as_str() { - "UTC" | "GMT" => 0, - "EST" | "America/New_York" => -300, - "CST" | "America/Chicago" => -360, - "MST" | "America/Denver" => -420, - "PST" | "America/Los_Angeles" => -480, - "EDT" => -240, "CDT" => -300, "MDT" => -360, "PDT" => -420, - "CET" => 60, "CEST" => 120, "IST" => 330, "JST" => 540, - "CST_CHINA" => 480, "AEST" => 600, "BRT" => -180, - _ => 0, - }; - let local_ms = ts + offset_minutes * 60_000; - let (year, month, day, hour, min, sec, ms) = epoch_ms_to_parts(local_ms); - let days_since_epoch = local_ms / 86_400_000; - let weekday_num = ((days_since_epoch % 7 + 4) % 7 + 7) % 7; - let weekday_names = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]; - let weekday_name = weekday_names[weekday_num as usize].to_string(); - let pairs = vec![ - ("year".to_string(), Value::Int(year as i64)), - ("month".to_string(), Value::Int(month as i64)), - ("day".to_string(), Value::Int(day as i64)), - ("hour".to_string(), Value::Int(hour as i64)), - ("minute".to_string(), Value::Int(min as i64)), - ("second".to_string(), Value::Int(sec as i64)), - ("ms".to_string(), Value::Int(ms as i64)), - ("weekday_num".to_string(), Value::Int(weekday_num)), - ("weekday_name".to_string(), Value::Str(weekday_name)), - ("offset_minutes".to_string(), Value::Int(offset_minutes)), - ("tz".to_string(), Value::Str(tz)), - ]; - stack.push(Value::Map(pairs)); BuiltinResult::Handled - } - - // ── Observer system ─────────────────────────────────────────────────── - "observe" => { - let fn_name = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let key = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; - let handle = OBSERVER_ID_COUNTER.with(|c| { let id = c.get(); c.set(id + 1); id }); - OBSERVER_REGISTRY.with(|r| { - r.borrow_mut().entry(key).or_default().push((handle, fn_name)); - }); - stack.push(Value::Int(handle)); BuiltinResult::Handled - } - "unobserve" => { - let handle = match stack.pop().unwrap_or(Value::Int(-1)) { Value::Int(i) => i, _ => -1 }; - OBSERVER_REGISTRY.with(|r| { - let mut registry = r.borrow_mut(); - for observers in registry.values_mut() { - observers.retain(|(id, _)| *id != handle); - } - }); - stack.push(Value::Nil); BuiltinResult::Handled - } - - // ── LLM native functions ────────────────────────────────────────────── - - // llm_call(model, prompt) -> String - "llm_call" => { - let prompt = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let model = match stack.pop().unwrap_or(Value::Str("claude".to_string())) { - Value::Str(s) => s, - _ => "claude".to_string(), - }; - // Automatic LLM response caching (TTL: 300 s). - // Disabled when NEURON_LLM_CACHE=0. - let llm_cache_enabled = std::env::var("NEURON_LLM_CACHE") - .map(|v| v != "0") - .unwrap_or(true); - let cache_key = format!("llm:{}:{}", model, prompt); - if llm_cache_enabled { - if let Some(cached) = cache::cache_get(&cache_key) { - stack.push(Value::Str(cached)); - return BuiltinResult::Handled; - } - } - let _llm_t0 = telemetry::now_ns(); - let result = call_llm_blocking(&model, None, &prompt); - let _llm_end_ns = telemetry::now_ns(); - let _llm_latency_ms = ((_llm_end_ns - _llm_t0) / 1_000_000) as i64; - let _llm_is_error = result.starts_with("{\"error\""); - { - let (trace_id, parent_id) = telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - let mut attrs = vec![ - ("llm.model".to_string(), telemetry::AttrValue::Str(model.clone())), - ("llm.prompt_length".to_string(), telemetry::AttrValue::Int(prompt.len() as i64)), - ("llm.latency_ms".to_string(), telemetry::AttrValue::Int(_llm_latency_ms)), - ]; - if _llm_is_error { attrs.push(("error".to_string(), telemetry::AttrValue::Bool(true))); } - let events = if _llm_is_error { - vec![telemetry::SpanEvent { - name: "exception".to_string(), time_ns: _llm_end_ns, - attrs: vec![("message".to_string(), telemetry::AttrValue::Str(result.clone()))], - }] - } else { vec![] }; - telemetry::emit_span(telemetry::Span { - trace_id, span_id: telemetry::new_span_id(), parent_id, - name: format!("llm.call {}", model), - start_ns: _llm_t0, end_ns: _llm_end_ns, - attrs, events, - status: if _llm_is_error { - telemetry::SpanStatus::Error(result.clone()) - } else { telemetry::SpanStatus::Ok }, - service: telemetry::service_name().to_string(), - }); - } - telemetry::log_info(&format!("llm_call: model={} latency={}ms", model, _llm_latency_ms)); - if llm_cache_enabled && !_llm_is_error { - cache::cache_set(cache_key, result.clone(), 300); - } - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // llm_call_system(model, system, prompt) -> String - "llm_call_system" => { - let prompt = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let system = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let model = match stack.pop().unwrap_or(Value::Str("claude".to_string())) { - Value::Str(s) => s, - _ => "claude".to_string(), - }; - // Automatic LLM response caching (TTL: 300 s). - let llm_cache_enabled = std::env::var("NEURON_LLM_CACHE") - .map(|v| v != "0") - .unwrap_or(true); - let cache_key = format!("llm:{}:{}:{}", model, system, prompt); - if llm_cache_enabled { - if let Some(cached) = cache::cache_get(&cache_key) { - stack.push(Value::Str(cached)); - return BuiltinResult::Handled; - } - } - let result = call_llm_blocking(&model, Some(&system), &prompt); - if llm_cache_enabled && !result.starts_with("{\"error\"") { - cache::cache_set(cache_key, result.clone(), 300); - } - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // llm_vision(model, system, message, image_base64) -> String - // Calls Anthropic vision API with a base64-encoded JPEG image and a text message. - // Returns the model's text description of the image. - "llm_vision" => { - let image_b64 = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let message = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let system = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let model = match stack.pop().unwrap_or(Value::Str("claude".to_string())) { - Value::Str(s) => s, - _ => "claude".to_string(), - }; - let result = call_llm_vision_blocking(&model, &system, &message, &image_b64); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // llm_call_agentic(model, system, message, tools_json) -> String - // Full agentic tool-use loop. Executes tools until end_turn. - // tools_json: Anthropic tool definitions as JSON array string. - // Returns the final text response. - "llm_call_agentic" => { - let tools_json = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => "[]".to_string(), - }; - let message = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let system = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let model = match stack.pop().unwrap_or(Value::Str("claude".to_string())) { - Value::Str(s) => s, - _ => "claude".to_string(), - }; - let result = call_llm_agentic_blocking(&model, &system, &message, &tools_json); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // llm_stream(model, prompt) -> String (blocking for now; streaming is future work) - "llm_stream" => { - let prompt = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let model = match stack.pop().unwrap_or(Value::Str("claude".to_string())) { - Value::Str(s) => s, - _ => "claude".to_string(), - }; - let _llms_t0 = telemetry::now_ns(); - let result = call_llm_blocking(&model, None, &prompt); - let _llms_end = telemetry::now_ns(); - { - let is_err = result.starts_with("{\"error\""); - let (trace_id, parent_id) = telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - let mut attrs = vec![ - ("llm.model".to_string(), telemetry::AttrValue::Str(model.clone())), - ("llm.prompt_length".to_string(), telemetry::AttrValue::Int(prompt.len() as i64)), - ("llm.latency_ms".to_string(), telemetry::AttrValue::Int(((_llms_end - _llms_t0) / 1_000_000) as i64)), - ]; - if is_err { attrs.push(("error".to_string(), telemetry::AttrValue::Bool(true))); } - telemetry::emit_span(telemetry::Span { - trace_id, span_id: telemetry::new_span_id(), parent_id, - name: format!("llm.stream {}", model), - start_ns: _llms_t0, end_ns: _llms_end, - attrs, events: vec![], - status: if is_err { telemetry::SpanStatus::Error(result.clone()) } else { telemetry::SpanStatus::Ok }, - service: telemetry::service_name().to_string(), - }); - } - telemetry::log_info(&format!("llm_stream: model={} latency={}ms", model, ((_llms_end - _llms_t0) / 1_000_000))); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // llm_parallel(models: List, prompt: String) -> Map - "llm_parallel" => { - let prompt = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let models: Vec = match stack.pop().unwrap_or(Value::List(vec![])) { - Value::List(l) => l.into_iter().filter_map(|v| { - if let Value::Str(s) = v { Some(s) } else { None } - }).collect(), - _ => vec![], - }; - let mut pairs: Vec<(String, Value)> = Vec::new(); - for model in models { - let response = call_llm_blocking(&model, None, &prompt); - pairs.push((model, Value::Str(response))); - } - stack.push(Value::Map(pairs)); - BuiltinResult::Handled - } - - // llm_models() -> List - "llm_models" => { - let mut model_names: Vec = LLM_CONFIG.with(|c| { - c.borrow().keys().cloned().collect() - }); - model_names.sort(); - let list = model_names.into_iter().map(Value::Str).collect(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - - // llm_configure(name, endpoint, api_key) -> Bool - "llm_configure" => { - let api_key = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let endpoint = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let name = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - LLM_CONFIG.with(|c| { - c.borrow_mut().insert(name, (endpoint, api_key)); - }); - stack.push(Value::Bool(true)); - BuiltinResult::Handled - } - - // ── GC / memory management builtins ────────────────────────────────── - - // gc_collect() — force a GC cycle. - // Since the El runtime uses clone-based value semantics (no heap graph), - // this resets the alloc counter and records the collect timestamp. - "gc_collect" => { - GC_ALLOC_COUNT.store(0, std::sync::atomic::Ordering::Relaxed); - GC_LAST_COLLECT_MS.store(now_ms_u64(), std::sync::atomic::Ordering::Relaxed); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // gc_stats() -> String — JSON: {heap_used, heap_total, objects_alive, last_collect_ms} - "gc_stats" => { - let heap_used = estimate_mem_usage_bytes(); - let alloc_count = GC_ALLOC_COUNT.load(std::sync::atomic::Ordering::Relaxed); - let last_collect = GC_LAST_COLLECT_MS.load(std::sync::atomic::Ordering::Relaxed); - let json = serde_json::json!({ - "heap_used": heap_used, - "heap_total": heap_used, // no separate total tracking - "objects_alive": alloc_count, - "last_collect_ms": last_collect - }); - stack.push(Value::Str(json.to_string())); - BuiltinResult::Handled - } - - // gc_set_threshold(bytes: Int) — trigger gc_collect when heap exceeds this. - "gc_set_threshold" => { - let bytes = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n.max(0) as u64, - _ => 0, - }; - GC_THRESHOLD_BYTES.store(bytes, std::sync::atomic::Ordering::Relaxed); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // mem_limit_set(bytes: Int) — set a soft memory limit. - "mem_limit_set" => { - let bytes = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n.max(0) as u64, - _ => 0, - }; - MEM_LIMIT_BYTES.store(bytes, std::sync::atomic::Ordering::Relaxed); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // mem_usage() -> Int — current estimated memory usage in bytes. - "mem_usage" => { - let usage = estimate_mem_usage_bytes(); - // Check threshold and GC if needed - let threshold = GC_THRESHOLD_BYTES.load(std::sync::atomic::Ordering::Relaxed); - if threshold > 0 && usage >= threshold { - GC_ALLOC_COUNT.store(0, std::sync::atomic::Ordering::Relaxed); - GC_LAST_COLLECT_MS.store(now_ms_u64(), std::sync::atomic::Ordering::Relaxed); - } - stack.push(Value::Int(usage as i64)); - BuiltinResult::Handled - } - - // ── Rate limiting builtins ──────────────────────────────────────────── - - // rate_limit_create(name, max_calls, window_ms) - "rate_limit_create" => { - let window_ms = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n.max(1) as u64, - _ => 1000, - }; - let max_calls = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n.max(0) as u64, - _ => 10, - }; - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - { - let mut cfg = rate_limiter_configs().lock().unwrap_or_else(|e| e.into_inner()); - cfg.insert(name.clone(), (max_calls, window_ms)); - } - { - let mut rl = rate_limiters().lock().unwrap_or_else(|e| e.into_inner()); - rl.entry(name).or_insert_with(std::collections::VecDeque::new); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // rate_limit_check(name) -> Bool — true if call is allowed. - "rate_limit_check" => { - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let now = now_ms_u64(); - let allowed = { - let cfg = rate_limiter_configs().lock().unwrap_or_else(|e| e.into_inner()); - let (max_calls, window_ms) = cfg.get(&name).cloned().unwrap_or((10, 1000)); - let mut rl = rate_limiters().lock().unwrap_or_else(|e| e.into_inner()); - let timestamps = rl.entry(name).or_insert_with(std::collections::VecDeque::new); - // Evict expired timestamps - timestamps.retain(|&ts| now.saturating_sub(ts) < window_ms); - if (timestamps.len() as u64) < max_calls { - timestamps.push_back(now); - true - } else { - false - } - }; - stack.push(Value::Bool(allowed)); - BuiltinResult::Handled - } - - // rate_limit_wait(name) — block until a slot is available. - "rate_limit_wait" => { - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - loop { - let now = now_ms_u64(); - let (allowed, sleep_ms) = { - let cfg = rate_limiter_configs().lock().unwrap_or_else(|e| e.into_inner()); - let (max_calls, window_ms) = cfg.get(&name).cloned().unwrap_or((10, 1000)); - let mut rl = rate_limiters().lock().unwrap_or_else(|e| e.into_inner()); - let timestamps = rl.entry(name.clone()).or_insert_with(std::collections::VecDeque::new); - timestamps.retain(|&ts| now.saturating_sub(ts) < window_ms); - if (timestamps.len() as u64) < max_calls { - timestamps.push_back(now); - (true, 0u64) - } else { - // Wait until oldest timestamp expires - let oldest = timestamps.front().cloned().unwrap_or(now); - let wait = window_ms.saturating_sub(now.saturating_sub(oldest)).max(1); - (false, wait) - } - }; - if allowed { break; } - std::thread::sleep(std::time::Duration::from_millis(sleep_ms)); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── Backpressure builtins ───────────────────────────────────────────── - - // backpressure_create(name, high_watermark, low_watermark) - "backpressure_create" => { - let low = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 10, - }; - let high = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 100, - }; - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let ctrl = BackpressureController { - high_watermark: high, - low_watermark: low, - state: "ok".to_string(), - }; - backpressure_map().lock().unwrap_or_else(|e| e.into_inner()) - .insert(name, ctrl); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // backpressure_signal(name, current_depth) - // Producer reports current queue depth; updates the state. - "backpressure_signal" => { - let depth = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n, - _ => 0, - }; - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - { - let mut map = backpressure_map().lock().unwrap_or_else(|e| e.into_inner()); - if let Some(ctrl) = map.get_mut(&name) { - ctrl.state = if depth >= ctrl.high_watermark { - "stop".to_string() - } else if depth > ctrl.low_watermark { - "slow".to_string() - } else { - "ok".to_string() - }; - } - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // backpressure_check(name) -> String — "ok" | "slow" | "stop" - "backpressure_check" => { - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let state = { - let map = backpressure_map().lock().unwrap_or_else(|e| e.into_inner()); - map.get(&name).map(|c| c.state.clone()).unwrap_or_else(|| "ok".to_string()) - }; - stack.push(Value::Str(state)); - BuiltinResult::Handled - } - - // ── Program lifecycle hooks ─────────────────────────────────────────── - - // on_shutdown(handler: String) — register handler for SIGTERM/SIGINT / exit_clean(). - "on_shutdown" => { - let handler = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - if !handler.is_empty() { - shutdown_hooks().lock().unwrap_or_else(|e| e.into_inner()).push(handler); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // on_error(handler: String) — register handler(error_message: String) for unhandled errors. - "on_error" => { - let handler = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - if !handler.is_empty() { - error_hooks().lock().unwrap_or_else(|e| e.into_inner()).push(handler); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // exit_clean() — run on_shutdown handlers then exit(0). - "exit_clean" => { - run_shutdown_hooks(); - BuiltinResult::Exit(0) - } - - // ── Cache builtins ──────────────────────────────────────────────────── - - // cache_set(key: String, value: String, ttl_secs: Int) - "cache_set" => { - let ttl = match stack.pop().unwrap_or(Value::Int(60)) { - Value::Int(n) => n.max(0) as u64, - _ => 60, - }; - let value = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - cache::cache_set(key, value, ttl); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // cache_get(key: String) -> String ("" if missing/expired) - "cache_get" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let value = cache::cache_get(&key).unwrap_or_default(); - stack.push(Value::Str(value)); - BuiltinResult::Handled - } - - // cache_invalidate(key: String) - "cache_invalidate" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - cache::cache_invalidate(&key); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // cache_clear() - "cache_clear" => { - cache::cache_clear(); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── HTTP retry builtins ─────────────────────────────────────────────── - - // http_get_retry(url: String, max_attempts: Int, backoff_ms: Int) -> String - "http_get_retry" => { - let backoff_ms = match stack.pop().unwrap_or(Value::Int(200)) { - Value::Int(n) => n.max(0) as u64, - _ => 200, - }; - let max_attempts = match stack.pop().unwrap_or(Value::Int(3)) { - Value::Int(n) => n.max(1) as u32, - _ => 3, - }; - let url = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = net::http_get_retry(&url, max_attempts, backoff_ms); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // http_post_retry(url: String, body: String, max_attempts: Int, backoff_ms: Int) -> String - "http_post_retry" => { - let backoff_ms = match stack.pop().unwrap_or(Value::Int(200)) { - Value::Int(n) => n.max(0) as u64, - _ => 200, - }; - let max_attempts = match stack.pop().unwrap_or(Value::Int(3)) { - Value::Int(n) => n.max(1) as u32, - _ => 3, - }; - 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 = net::http_post_retry(&url, &body, max_attempts, backoff_ms); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── Circuit breaker builtins ────────────────────────────────────────── - - // circuit_open(name: String, failure_threshold: Int, reset_secs: Int) - "circuit_open" => { - let reset_secs = match stack.pop().unwrap_or(Value::Int(30)) { - Value::Int(n) => n.max(1) as u64, - _ => 30, - }; - let failure_threshold = match stack.pop().unwrap_or(Value::Int(5)) { - Value::Int(n) => n.max(1) as u32, - _ => 5, - }; - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - net::circuit_open(name, failure_threshold, reset_secs); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // circuit_call(name: String, url: String, body: String) -> String - "circuit_call" => { - 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 name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let result = net::circuit_call(&name, &url, &body); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── WebSocket server builtins ───────────────────────────────────────── - - // ws_serve(port: Int, handler: String) - // Starts a WebSocket server in the background. Incoming messages are - // queued; call ws_serve_poll(port) in a loop to drain them. - "ws_serve" => { - // handler name is accepted for API compatibility; messages are - // delivered via ws_serve_poll, not by invoking the handler directly. - let _handler = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let port = match stack.pop().unwrap_or(Value::Int(8765)) { - Value::Int(n) => n as u16, - _ => 8765, - }; - net::ws_serve_start(port); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ws_serve_poll(port: Int) -> List of {client_id, message} maps - // Drain all pending incoming messages on the server for one iteration. - "ws_serve_poll" => { - let port = match stack.pop().unwrap_or(Value::Int(8765)) { - Value::Int(n) => n as u16, - _ => 8765, - }; - let msgs = net::ws_server_poll(port, 0); - let list: Vec = msgs.into_iter().map(|m| { - Value::Map(vec![ - ("client_id".to_string(), Value::Str(m.client_id)), - ("message".to_string(), Value::Str(m.message)), - ]) - }).collect(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - - // ws_server_send(port: Int, client_id: String, message: String) -> Bool - "ws_server_send" => { - let message = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let client_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let port = match stack.pop().unwrap_or(Value::Int(8765)) { - Value::Int(n) => n as u16, - _ => 8765, - }; - let ok = net::ws_server_send(port, &client_id, message); - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - - // ws_broadcast(port: Int, message: String) - "ws_broadcast" => { - let message = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let port = match stack.pop().unwrap_or(Value::Int(8765)) { - Value::Int(n) => n as u16, - _ => 8765, - }; - net::ws_server_broadcast(port, message); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ws_server_close(port: Int, client_id: String) - "ws_server_close" => { - let client_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let port = match stack.pop().unwrap_or(Value::Int(8765)) { - Value::Int(n) => n as u16, - _ => 8765, - }; - net::ws_server_close(port, &client_id); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── WebSocket client (background / poll-based) builtins ─────────────── - - // ws_connect_async(url: String) -> String (conn_id) - // Unlike the synchronous ws_connect, this runs on a background thread - // and queues incoming messages for ws_client_poll(). - "ws_connect_async" => { - let url = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let conn_id = net::ws_client_connect(&url); - stack.push(Value::Str(conn_id)); - BuiltinResult::Handled - } - - // ws_client_send(conn_id: String, message: String) - "ws_client_send" => { - let message = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let conn_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - net::ws_client_send(&conn_id, message); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ws_client_close(conn_id: String) - "ws_client_close" => { - let conn_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - net::ws_client_close(&conn_id); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ws_client_poll(conn_id: String) -> List of {conn_id, message} maps - // Pass "" to get messages from all connections. - "ws_client_poll" => { - let conn_id_filter = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let filter = if conn_id_filter.is_empty() { None } else { Some(conn_id_filter.as_str()) }; - let msgs = net::ws_client_poll(filter); - let list: Vec = msgs.into_iter().map(|m| { - Value::Map(vec![ - ("conn_id".to_string(), Value::Str(m.conn_id)), - ("message".to_string(), Value::Str(m.message)), - ]) - }).collect(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - - // ── Subprocess / shell execution ────────────────────────────────────── - - // sh(command: String) -> String - "sh" => { - let cmd = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Str(String::new())); return BuiltinResult::Handled; } - }; - let output = std::process::Command::new("sh").arg("-c").arg(&cmd).output(); - let result = match output { - Ok(o) => { - if o.status.success() { - String::from_utf8_lossy(&o.stdout).to_string() - } else { - let out = String::from_utf8_lossy(&o.stdout); - let err = String::from_utf8_lossy(&o.stderr); - if out.is_empty() { err.to_string() } - else if err.is_empty() { out.to_string() } - else { format!("{}{}", out, err) } - } - } - Err(e) => format!("sh error: {}", e), - }; - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // sh_async(command: String, callback: String) - "sh_async" => { - let callback = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let cmd = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let instr_arc = SERVE_INSTRUCTIONS.with(|si| si.borrow().clone()); - let fn_arc = SERVE_FN_TABLE.with(|sf| sf.borrow().clone()); - std::thread::spawn(move || { - let output = std::process::Command::new("sh").arg("-c").arg(&cmd).output(); - let result = match output { - Ok(o) => { - if o.status.success() { - String::from_utf8_lossy(&o.stdout).to_string() - } else { - let out = String::from_utf8_lossy(&o.stdout); - let err = String::from_utf8_lossy(&o.stderr); - if out.is_empty() { err.to_string() } else { out.to_string() } - } - } - Err(e) => format!("sh error: {}", e), - }; - if let (Some(instr), Some(fn_tbl)) = (instr_arc, fn_arc) { - if let Some(&entry) = fn_tbl.get(&callback) { - run_sub_interpreter_with_stack( - &instr, &fn_tbl, entry, - vec![el_compiler::Value::Str(result)], - ); - } - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── Cryptographic primitives ────────────────────────────────────────── - - // crypto_keygen_ed25519() -> String "private: public:" - "crypto_keygen_ed25519" => { - use ed25519_dalek::SigningKey; - let sk = SigningKey::generate(&mut rand::rngs::OsRng); - let vk = sk.verifying_key(); - stack.push(Value::Str(format!( - "private:{} public:{}", - hex::encode(sk.to_bytes()), - hex::encode(vk.to_bytes()) - ))); - BuiltinResult::Handled - } - - // crypto_sign_ed25519(private_key_hex, message) -> String (hex sig) - "crypto_sign_ed25519" => { - use ed25519_dalek::{SigningKey, Signer}; - let message = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let priv_hex = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let result = (|| -> Result> { - let kb = hex::decode(&priv_hex)?; - let ka: [u8; 32] = kb.try_into().map_err(|_| "key must be 32 bytes")?; - Ok(hex::encode(SigningKey::from_bytes(&ka).sign(message.as_bytes()).to_bytes())) - })(); - stack.push(Value::Str(result.unwrap_or_else(|e| format!("error: {e}")))); - BuiltinResult::Handled - } - - // crypto_verify_ed25519(public_key_hex, message, sig_hex) -> Bool - "crypto_verify_ed25519" => { - use ed25519_dalek::{VerifyingKey, Verifier, Signature}; - let sig_hex = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let message = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let pub_hex = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let ok = (|| -> Result> { - let pb = hex::decode(&pub_hex)?; - let pa: [u8; 32] = pb.try_into().map_err(|_| "pub key 32 bytes")?; - let vk = VerifyingKey::from_bytes(&pa)?; - let sb = hex::decode(&sig_hex)?; - let sa: [u8; 64] = sb.try_into().map_err(|_| "sig 64 bytes")?; - vk.verify(message.as_bytes(), &Signature::from_bytes(&sa))?; - Ok(true) - })(); - stack.push(Value::Bool(ok.unwrap_or(false))); - BuiltinResult::Handled - } - - // crypto_encrypt_aes(key, plaintext) -> String (base64 nonce+ciphertext) - "crypto_encrypt_aes" => { - use aes_gcm::{Aes256Gcm, KeyInit, aead::{Aead, AeadCore, OsRng as AesOsRng}}; - let plaintext = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let key_in = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let result = (|| -> Result> { - let key_bytes = if key_in.len() == 64 { - hex::decode(&key_in).unwrap_or_else(|_| derive_aes_key(&key_in)) - } else { derive_aes_key(&key_in) }; - let ka: [u8; 32] = key_bytes.try_into().map_err(|_| "key error")?; - let cipher = Aes256Gcm::new_from_slice(&ka)?; - let nonce = Aes256Gcm::generate_nonce(&mut AesOsRng); - let ct = cipher.encrypt(&nonce, plaintext.as_bytes()).map_err(|_| "enc failed")?; - let mut out = nonce.to_vec(); out.extend_from_slice(&ct); - Ok(base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &out)) - })(); - stack.push(Value::Str(result.unwrap_or_else(|e| format!("error: {e}")))); - BuiltinResult::Handled - } - - // crypto_decrypt_aes(key, ciphertext_b64) -> String - "crypto_decrypt_aes" => { - use aes_gcm::{Aes256Gcm, KeyInit, aead::{Aead, generic_array::GenericArray}}; - let ct_b64 = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let key_in = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let result = (|| -> Result> { - let key_bytes = if key_in.len() == 64 { - hex::decode(&key_in).unwrap_or_else(|_| derive_aes_key(&key_in)) - } else { derive_aes_key(&key_in) }; - let ka: [u8; 32] = key_bytes.try_into().map_err(|_| "key error")?; - let cipher = Aes256Gcm::new_from_slice(&ka)?; - let combined = base64::Engine::decode( - &base64::engine::general_purpose::STANDARD, &ct_b64)?; - if combined.len() < 12 { return Err("too short".into()); } - let nonce = GenericArray::from_slice(&combined[..12]); - let pt = cipher.decrypt(nonce, &combined[12..]).map_err(|_| "dec failed")?; - Ok(String::from_utf8_lossy(&pt).to_string()) - })(); - stack.push(Value::Str(result.unwrap_or_else(|e| format!("error: {e}")))); - BuiltinResult::Handled - } - - // crypto_hash_sha256(input) -> String (hex) - "crypto_hash_sha256" => { - use sha2::{Sha256, Digest}; - let input = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let mut h = Sha256::new(); h.update(input.as_bytes()); - stack.push(Value::Str(hex::encode(h.finalize()))); - BuiltinResult::Handled - } - - // crypto_hash_blake3(input) -> String (hex) - "crypto_hash_blake3" => { - let input = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(blake3::hash(input.as_bytes()).to_hex().to_string())); - BuiltinResult::Handled - } - - // crypto_random_bytes(n) -> String (hex) - "crypto_random_bytes" => { - use rand::RngCore; - let n = match stack.pop().unwrap_or(Value::Int(16)) { - Value::Int(i) => (i.max(0) as usize).min(4096), _ => 16, - }; - let mut bytes = vec![0u8; n]; - rand::rngs::OsRng.fill_bytes(&mut bytes); - stack.push(Value::Str(hex::encode(&bytes))); - BuiltinResult::Handled - } - - // crypto_uuid() -> String (UUID v4) - "crypto_uuid" => { - stack.push(Value::Str(uuid::Uuid::new_v4().to_string())); - BuiltinResult::Handled - } - - // ── Time extras ─────────────────────────────────────────────────────── - - // time_now_ms() -> Int - "time_now_ms" => { - let ms = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis() as i64).unwrap_or(0); - stack.push(Value::Int(ms)); - BuiltinResult::Handled - } - - // time_now_s() -> Int - "time_now_s" => { - let s = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs() as i64).unwrap_or(0); - stack.push(Value::Int(s)); - BuiltinResult::Handled - } - - // ── Timer / scheduling ──────────────────────────────────────────────── - - // timer_after(delay_ms, handler) - "timer_after" => { - let handler = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let delay_ms = match stack.pop().unwrap_or(Value::Int(0)) { - Value::Int(i) => i.max(0) as u64, _ => 0, - }; - let instr_arc = SERVE_INSTRUCTIONS.with(|si| si.borrow().clone()); - let fn_arc = SERVE_FN_TABLE.with(|sf| sf.borrow().clone()); - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_millis(delay_ms)); - if let (Some(instr), Some(fn_tbl)) = (instr_arc, fn_arc) { - if let Some(&entry) = fn_tbl.get(&handler) { - run_sub_interpreter(&instr, &fn_tbl, entry); - } - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // timer_every(interval_ms, handler) -> String (timer_id) - "timer_every" => { - let handler = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Str(String::new())); return BuiltinResult::Handled; } - }; - let interval_ms = match stack.pop().unwrap_or(Value::Int(1000)) { - Value::Int(i) => i.max(1) as u64, _ => 1000, - }; - let timer_id = next_timer_id(); - let (cancel_tx, cancel_rx) = crossbeam_channel::bounded::<()>(1); - timer_registry().lock().unwrap_or_else(|e| e.into_inner()) - .insert(timer_id.clone(), cancel_tx); - let instr_arc = SERVE_INSTRUCTIONS.with(|si| si.borrow().clone()); - let fn_arc = SERVE_FN_TABLE.with(|sf| sf.borrow().clone()); - let tid = timer_id.clone(); - std::thread::spawn(move || { - loop { - match cancel_rx.recv_timeout(std::time::Duration::from_millis(interval_ms)) { - Ok(_) | Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break, - Err(crossbeam_channel::RecvTimeoutError::Timeout) => { - if let (Some(ref instr), Some(ref fn_tbl)) = (&instr_arc, &fn_arc) { - if let Some(&entry) = fn_tbl.get(&handler) { - run_sub_interpreter(instr, fn_tbl, entry); - } - } - } - } - } - timer_registry().lock().unwrap_or_else(|e| e.into_inner()).remove(&tid); - }); - stack.push(Value::Str(timer_id)); - BuiltinResult::Handled - } - - // timer_cancel(timer_id) - "timer_cancel" => { - let timer_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - if let Some(tx) = timer_registry().lock().unwrap_or_else(|e| e.into_inner()) - .remove(&timer_id) { let _ = tx.send(()); } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // cron_schedule(expression, handler) -> String (job_id) - "cron_schedule" => { - let handler = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Str(String::new())); return BuiltinResult::Handled; } - }; - let expression = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Str(String::new())); return BuiltinResult::Handled; } - }; - let job_id = next_timer_id(); - let (cancel_tx, cancel_rx) = crossbeam_channel::bounded::<()>(1); - timer_registry().lock().unwrap_or_else(|e| e.into_inner()) - .insert(job_id.clone(), cancel_tx); - let instr_arc = SERVE_INSTRUCTIONS.with(|si| si.borrow().clone()); - let fn_arc = SERVE_FN_TABLE.with(|sf| sf.borrow().clone()); - let jid = job_id.clone(); - std::thread::spawn(move || { - loop { - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH).unwrap_or_default(); - let nanos = 1_000_000_000u64.saturating_sub(now.subsec_nanos() as u64); - match cancel_rx.recv_timeout(std::time::Duration::from_nanos(nanos)) { - Ok(_) | Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break, - Err(crossbeam_channel::RecvTimeoutError::Timeout) => {} - } - if cancel_rx.try_recv().is_ok() { break; } - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs(); - if cron_matches(&expression, ts) { - if let (Some(ref instr), Some(ref fn_tbl)) = (&instr_arc, &fn_arc) { - if let Some(&entry) = fn_tbl.get(&handler) { - run_sub_interpreter(instr, fn_tbl, entry); - } - } - match cancel_rx.recv_timeout(std::time::Duration::from_secs(60)) { - Ok(_) | Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break, - Err(crossbeam_channel::RecvTimeoutError::Timeout) => {} - } - } - } - timer_registry().lock().unwrap_or_else(|e| e.into_inner()).remove(&jid); - }); - stack.push(Value::Str(job_id)); - BuiltinResult::Handled - } - - // cron_cancel(job_id) - "cron_cancel" => { - let job_id = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - if let Some(tx) = timer_registry().lock().unwrap_or_else(|e| e.into_inner()) - .remove(&job_id) { let _ = tx.send(()); } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── Inter-thread channels ───────────────────────────────────────────── - - // chan_create(name, capacity) - "chan_create" => { - let capacity = match stack.pop().unwrap_or(Value::Int(64)) { - Value::Int(i) => i.max(1) as usize, _ => 64, - }; - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let (tx, rx) = crossbeam_channel::bounded::(capacity); - channel_registry().lock().unwrap_or_else(|e| e.into_inner()) - .insert(name, ChannelEntry { sender: tx, receiver: rx }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // chan_send(name, message) -> Bool - "chan_send" => { - let message = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, other => other.to_string(), - }; - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Bool(false)); return BuiltinResult::Handled; } - }; - let sender = channel_registry().lock().unwrap_or_else(|e| e.into_inner()) - .get(&name).map(|e| e.sender.clone()); - stack.push(Value::Bool( - sender.map(|tx| tx.try_send(message).is_ok()).unwrap_or(false) - )); - BuiltinResult::Handled - } - - // chan_recv(name, timeout_ms) -> String ("" on timeout) - "chan_recv" => { - let timeout_ms = match stack.pop().unwrap_or(Value::Int(0)) { - Value::Int(i) => i.max(0) as u64, _ => 0, - }; - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Str(String::new())); return BuiltinResult::Handled; } - }; - let receiver = channel_registry().lock().unwrap_or_else(|e| e.into_inner()) - .get(&name).map(|e| e.receiver.clone()); - let msg = match receiver { - Some(rx) => if timeout_ms == 0 { - rx.recv().unwrap_or_default() - } else { - rx.recv_timeout(std::time::Duration::from_millis(timeout_ms)) - .unwrap_or_default() - }, - None => String::new(), - }; - stack.push(Value::Str(msg)); - BuiltinResult::Handled - } - - // chan_close(name) - "chan_close" => { - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - channel_registry().lock().unwrap_or_else(|e| e.into_inner()).remove(&name); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // chan_len(name) -> Int - "chan_len" => { - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { stack.push(Value::Int(0)); return BuiltinResult::Handled; } - }; - let len = channel_registry().lock().unwrap_or_else(|e| e.into_inner()) - .get(&name).map(|e| e.receiver.len() as i64).unwrap_or(0); - stack.push(Value::Int(len)); - BuiltinResult::Handled - } - - // ── Explicit observability builtins ────────────────────────────────── - - // log_debug(msg) / log_info(msg) / log_warn(msg) / log_error(msg) - "log_debug" => { - let msg = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - v => format!("{:?}", v), - }; - telemetry::log_debug(&msg); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "log_info" => { - let msg = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - v => format!("{:?}", v), - }; - telemetry::log_info(&msg); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "log_warn" => { - let msg = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - v => format!("{:?}", v), - }; - telemetry::log_warn(&msg); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "log_error" => { - let msg = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - v => format!("{:?}", v), - }; - telemetry::log_error(&msg); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // trace_start(name) -> String (returns span_id) - "trace_start" => { - let name = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => "span".to_string(), - }; - let (trace_id, _parent_id) = telemetry::context::current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (telemetry::new_trace_id(), None)); - let span_id = telemetry::new_span_id(); - let start_ns = telemetry::now_ns(); - EXPLICIT_SPANS.with(|m| { - m.borrow_mut().insert(span_id.clone(), (trace_id, span_id.clone(), start_ns, name, vec![])); - }); - stack.push(Value::Str(span_id)); - BuiltinResult::Handled - } - - // trace_end(span_id) - "trace_end" => { - let span_id = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let entry = EXPLICIT_SPANS.with(|m| m.borrow_mut().remove(&span_id)); - if let Some((trace_id, sid, start_ns, name, attrs)) = entry { - let end_ns = telemetry::now_ns(); - let parent_id = telemetry::context::current_span().map(|(_, s)| s); - telemetry::emit_span(telemetry::Span { - trace_id, - span_id: sid, - parent_id, - name, - start_ns, - end_ns, - attrs, - events: vec![], - status: telemetry::SpanStatus::Ok, - service: telemetry::service_name().to_string(), - }); - } - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // trace_tag(span_id, key, value) - "trace_tag" => { - let value = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - v => format!("{:?}", v), - }; - let key = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let span_id = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - EXPLICIT_SPANS.with(|m| { - if let Some(entry) = m.borrow_mut().get_mut(&span_id) { - entry.4.push((key, telemetry::AttrValue::Str(value))); - } - }); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // metric_counter(name, value, tags) tags = "k=v,k2=v2" - "metric_counter" => { - let tags = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let value = match stack.pop().unwrap_or(Value::Int(1)) { - Value::Int(n) => n, - Value::Float(f) => f as i64, - _ => 1, - }; - let name = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let attrs = telemetry::parse_tags_string(&tags); - telemetry::counter(&name, value as f64, attrs); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // metric_gauge(name, value, tags) - "metric_gauge" => { - let tags = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => String::new(), - }; - let value = match stack.pop().unwrap_or(Value::Int(0)) { - Value::Int(n) => n, - Value::Float(f) => f as i64, - _ => 0, - }; - let name = match stack.pop().unwrap_or(Value::Str(String::new())) { - Value::Str(s) => s, - _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } - }; - let attrs = telemetry::parse_tags_string(&tags); - telemetry::gauge(&name, value as f64, attrs); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // graph_sql(query: String) -> String - // - // Execute a SQL query against the Engram graph via POST /api/sql and - // return the raw JSON response string. Uses ENGRAM_URL if set, - // falling back to http://localhost:8742. - "graph_sql" => { - let query = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => { - stack.push(Value::Str(format!( - "{{\"error\":\"graph_sql: expected string, got {:?}\"}}", - other - ))); - return BuiltinResult::Handled; - } - }; - let engram_url = std::env::var("ENGRAM_URL") - .unwrap_or_else(|_| "http://localhost:8742".to_string()); - let url = format!("{engram_url}/api/sql"); - let body = serde_json::json!({"query": query}).to_string(); - let result = net::with_single_retry(500, || { - reqwest::blocking::Client::new() - .post(&url) - .header("Content-Type", "application/json") - .body(body.clone()) - .send() - .and_then(|r| r.text()) - }); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── App block context builtins ─────────────────────────────────────────── - - "ctx_env" => { - let val = APP_ENV.with(|e| e.borrow().clone()); - let val = if val.is_empty() { - std::env::var("NEURON_ENV").unwrap_or_else(|_| "dev".to_string()) - } else { - val - }; - stack.push(Value::Str(val)); - BuiltinResult::Handled - } - - "ctx_service" => { - let val = APP_SERVICE.with(|s| s.borrow().clone()); - stack.push(Value::Str(val)); - BuiltinResult::Handled - } - - "ctx_version" => { - let val = APP_VERSION.with(|v| v.borrow().clone()); - stack.push(Value::Str(val)); - BuiltinResult::Handled - } - - "ctx_instance" => { - let val = APP_INSTANCE.with(|id| { - let current = id.borrow().clone(); - if current.is_empty() { - let new_id = uuid::Uuid::new_v4().to_string(); - *id.borrow_mut() = new_id.clone(); - new_id - } else { - current - } - }); - stack.push(Value::Str(val)); - BuiltinResult::Handled - } - - "config" => { - let default_val = if _arity >= 2 { - stack.pop().unwrap_or(Value::Nil) - } else { - Value::Nil - }; - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { - stack.push(Value::Nil); - return BuiltinResult::Handled; - } - }; - // env var overrides everything - if let Ok(env_val) = std::env::var(&key) { - stack.push(Value::Str(env_val)); - return BuiltinResult::Handled; - } - let val = APP_CONFIG.with(|c| c.borrow().get(&key).cloned()); - match val { - Some(v) => stack.push(v), - None => { - if matches!(default_val, Value::Nil) { - eprintln!("Error: config key '{}' not found", key); - std::process::exit(1); - } - stack.push(default_val) - } - } - BuiltinResult::Handled - } - - "secret" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { - stack.push(Value::Str(String::new())); - return BuiltinResult::Handled; - } - }; - // Check env var first - if let Ok(env_val) = std::env::var(&key) { - stack.push(Value::Str(env_val)); - return BuiltinResult::Handled; - } - let val = APP_SECRETS.with(|s| s.borrow().get(&key).cloned()); - match val { - Some(v) => stack.push(Value::Str(v)), - None => { - // Try secrets.json directly - let secrets_file = load_neuron_secrets_json(); - match secrets_file.get(&key).cloned() { - Some(v) => stack.push(Value::Str(v)), - None => { - eprintln!( - "Error: required secret '{}' not found. Set environment variable or add to ~/.neuron/secrets.json", - key - ); - std::process::exit(1); - } - } - } - } - BuiltinResult::Handled - } - - "flag" => { - let default_val = if _arity >= 2 { - match stack.pop().unwrap_or(Value::Bool(false)) { - Value::Bool(b) => b, - _ => false, - } - } else { - false - }; - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => { - stack.push(Value::Bool(false)); - return BuiltinResult::Handled; - } - }; - let val = APP_FLAGS.with(|f| f.borrow().get(&key).copied()); - stack.push(Value::Bool(val.unwrap_or(default_val))); - BuiltinResult::Handled - } - - // JSX rendering: __jsx__(tag, attrs_map, children_list) - // Renders a JSX element to an HTML string. - "__jsx__" => { - let children = stack.pop().unwrap_or(Value::Nil); - let attrs = stack.pop().unwrap_or(Value::Nil); - let tag_val = stack.pop().unwrap_or(Value::Nil); - - let tag = match &tag_val { - Value::Str(s) => s.clone(), - _ => "div".to_string(), - }; - - // Build attrs string - let attrs_str = match &attrs { - Value::Map(pairs) => { - pairs.iter().map(|(k, v)| { - let val_str = match v { - Value::Str(s) => s.clone(), - Value::Bool(b) => b.to_string(), - Value::Int(n) => n.to_string(), - _ => v.to_string(), - }; - // Skip event handlers (on:click etc.) in HTML output - if k.contains(':') || k.starts_with("on") { - String::new() - } else { - format!(" {}=\"{}\"", k, val_str) - } - }).collect::>().concat() - } - _ => String::new(), - }; - - // Build children string - let children_str = match children { - Value::List(items) => { - items.iter().map(|v| match v { - Value::Str(s) => s.clone(), - Value::Nil => String::new(), - other => other.to_string(), - }).collect::>().concat() - } - Value::Str(s) => s, - Value::Nil => String::new(), - other => other.to_string(), - }; - - // Self-closing tags - let is_void = matches!(tag.as_str(), "br" | "hr" | "img" | "input" | "link" | "meta"); - let html = if is_void || children_str.is_empty() && attrs_str.contains("/>") { - format!("<{tag}{attrs_str} />") - } else if tag == "fragment" { - children_str - } else { - format!("<{tag}{attrs_str}>{children_str}") - }; - - stack.push(Value::Str(html)); - BuiltinResult::Handled - } - - // Component render: __render_component__(name) — renders a named component - "__render_component__" => { - let name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => "App".to_string(), - }; - stack.push(Value::Str(format!("[component: {name}]"))); - BuiltinResult::NotBuiltin // Let NotBuiltin branch call __component_ - } - - _ => BuiltinResult::NotBuiltin, - } -} - -/// Derive a 32-byte AES key from an arbitrary string via SHA-256. -fn derive_aes_key(s: &str) -> Vec { - use sha2::{Sha256, Digest}; - let mut h = Sha256::new(); - h.update(s.as_bytes()); - h.finalize().to_vec() -} - -/// Check whether a Unix timestamp (seconds) matches a 5-field cron expression. -/// Fields: minute hour day-of-month month day-of-week -fn cron_matches(expression: &str, unix_secs: u64) -> bool { - let total_days = unix_secs / 86_400; - let tod = unix_secs % 86_400; - let hour = (tod / 3600) as u32; - let min = ((tod % 3600) / 60) as u32; - let wday = ((total_days + 4) % 7) as u32; // 0=Sun - let (_year, month, mday) = cron_days_to_ymd(total_days); - let fields: Vec<&str> = expression.split_whitespace().collect(); - if fields.len() < 5 { return false; } - cron_field_matches(fields[0], min) - && cron_field_matches(fields[1], hour) - && cron_field_matches(fields[2], mday) - && cron_field_matches(fields[3], month) - && cron_field_matches(fields[4], wday) -} - -fn cron_days_to_ymd(days: u64) -> (u32, u32, u32) { - let mut rem = days; - let mut year = 1970u32; - loop { - let leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); - let diy = if leap { 366 } else { 365 }; - if rem < diy { break; } - rem -= diy; - year += 1; - } - let leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); - let mdays: [u32; 12] = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - let mut month = 1u32; - let mut r = rem as u32; - for &md in &mdays { - if r < md { break; } - r -= md; month += 1; - } - (year, month, r + 1) -} - -fn cron_field_matches(field: &str, value: u32) -> bool { - if field == "*" { return true; } - if let Some(s) = field.strip_prefix("*/") { - let step: u32 = s.parse().unwrap_or(1); - return step > 0 && value % step == 0; - } - for part in field.split(',') { - let p = part.trim(); - if let Ok(n) = p.parse::() { - if n == value { return true; } - } else if let Some((s, st)) = p.split_once('/') { - let start: u32 = s.parse().unwrap_or(0); - let step: u32 = st.parse().unwrap_or(1); - if step > 0 && value >= start && (value - start) % step == 0 { return true; } - } - } - false -} - -/// Return terminal dimensions as (cols, rows). -/// Uses TIOCGWINSZ ioctl on Unix; falls back to (80, 24). -fn term_size() -> (u16, u16) { - #[cfg(unix)] - { - use std::os::unix::io::AsRawFd; - #[repr(C)] - struct WinSize { rows: u16, cols: u16, _xpix: u16, _ypix: u16 } - let ws = WinSize { rows: 0, cols: 0, _xpix: 0, _ypix: 0 }; - unsafe { - extern "C" { - fn ioctl(fd: std::ffi::c_int, request: std::ffi::c_ulong, ...) -> std::ffi::c_int; - } - // TIOCGWINSZ: macOS = 0x40087468, Linux = 0x5413 - #[cfg(target_os = "macos")] - ioctl(std::io::stdout().as_raw_fd(), 0x40087468_u64, &ws); - #[cfg(not(target_os = "macos"))] - ioctl(std::io::stdout().as_raw_fd(), 0x5413_u64, &ws); - if ws.cols > 0 && ws.rows > 0 { - return (ws.cols, ws.rows); - } - } - } - (80, 24) -} - -/// Format a Unix timestamp (seconds + millis) as an ISO 8601 string. -/// This avoids pulling in chrono while still producing a readable timestamp. -fn format_epoch_as_iso(secs: u64, millis: u32) -> String { - // Days since epoch, accounting for leap years - let mut days = secs / 86400; - let time_of_day = secs % 86400; - let hours = time_of_day / 3600; - let minutes = (time_of_day % 3600) / 60; - let seconds = time_of_day % 60; - - // Gregorian calendar calculation starting from 1970-01-01 - let mut year = 1970u64; - loop { - let days_in_year = if is_leap(year) { 366 } else { 365 }; - if days < days_in_year { - break; - } - days -= days_in_year; - year += 1; - } - let months = [31u64, if is_leap(year) { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - let mut month = 1u64; - for &m in &months { - if days < m { - break; - } - days -= m; - month += 1; - } - let day = days + 1; - format!("{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}.{millis:03}Z") -} - -fn is_leap(year: u64) -> bool { - (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) -} - -fn is_leap_i32(year: i32) -> bool { - (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) -} - -fn days_in_month(year: i32, month: u32) -> u32 { - match month { - 1|3|5|7|8|10|12 => 31, - 4|6|9|11 => 30, - 2 => if is_leap_i32(year) { 29 } else { 28 }, - _ => 30, - } -} - -fn epoch_ms_to_parts(ms: i64) -> (i32, u32, u32, u32, u32, u32, u32) { - let secs = ms / 1000; - let ms_frac = (ms % 1000).unsigned_abs() as u32; - let mut days = secs / 86400; - let mut time_of_day = secs % 86400; - if time_of_day < 0 { time_of_day += 86400; days -= 1; } - let secs_in_day = time_of_day as u32; - let hour = secs_in_day / 3600; - let minute = (secs_in_day % 3600) / 60; - let second = secs_in_day % 60; - let mut year = 1970i32; - let mut remaining = days; - loop { - let days_in_year = if is_leap_i32(year) { 366 } else { 365 }; - if remaining < days_in_year { break; } - remaining -= days_in_year; - year += 1; - } - let leap = is_leap_i32(year); - let month_days = [31u32, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - let mut month = 0u32; - let mut day_of_month = remaining as u32; - for (m, &d) in month_days.iter().enumerate() { - if day_of_month < d { month = m as u32 + 1; break; } - day_of_month -= d; - } - (year, month, day_of_month + 1, hour, minute, second, ms_frac) -} - -fn parts_to_epoch_ms(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, ms: u32) -> i64 { - let mut days: i64 = 0; - if year >= 1970 { - for y in 1970..year { - days += if is_leap_i32(y) { 366 } else { 365 }; - } - } else { - for y in year..1970 { - days -= if is_leap_i32(y) { 366 } else { 365 }; - } - } - let leap = is_leap_i32(year); - let month_days = [31u32, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - for m in 0..(month as usize - 1) { - days += month_days[m] as i64; - } - days += (day as i64) - 1; - let secs = days * 86400 + (hour as i64) * 3600 + (min as i64) * 60 + (sec as i64); - secs * 1000 + ms as i64 -} - -thread_local! { - static OBSERVER_REGISTRY: std::cell::RefCell>> = - std::cell::RefCell::new(std::collections::HashMap::new()); - static OBSERVER_ID_COUNTER: std::cell::Cell = std::cell::Cell::new(1); -} - -// ── GC / memory management state ───────────────────────────────────────────── - -/// Total allocations since process start (rough counter). -static GC_ALLOC_COUNT: std::sync::atomic::AtomicU64 = - std::sync::atomic::AtomicU64::new(0); -/// Timestamp (ms) of last gc_collect() call. -static GC_LAST_COLLECT_MS: std::sync::atomic::AtomicU64 = - std::sync::atomic::AtomicU64::new(0); -/// Soft memory limit in bytes (0 = no limit). -static MEM_LIMIT_BYTES: std::sync::atomic::AtomicU64 = - std::sync::atomic::AtomicU64::new(0); -/// GC threshold bytes: when estimated usage exceeds this, auto-trigger gc_collect (0 = off). -static GC_THRESHOLD_BYTES: std::sync::atomic::AtomicU64 = - std::sync::atomic::AtomicU64::new(0); - -fn now_ms_u64() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis() as u64) - .unwrap_or(0) -} - -/// Estimate current process RSS in bytes. -fn estimate_mem_usage_bytes() -> u64 { - #[cfg(target_os = "linux")] - { - if let Ok(s) = std::fs::read_to_string("/proc/self/status") { - for line in s.lines() { - if line.starts_with("VmRSS:") { - let kb: u64 = line.split_whitespace() - .nth(1) - .and_then(|n| n.parse().ok()) - .unwrap_or(0); - return kb * 1024; - } - } - } - } - GC_ALLOC_COUNT.load(std::sync::atomic::Ordering::Relaxed) * 64 -} - -// ── Rate limiter state ──────────────────────────────────────────────────────── - -use std::sync::{OnceLock, Mutex}; - -static RATE_LIMITERS: OnceLock>>> = - OnceLock::new(); -static RATE_LIMITER_CONFIGS: OnceLock>> = - OnceLock::new(); - -fn rate_limiters() -> &'static Mutex>> { - RATE_LIMITERS.get_or_init(|| Mutex::new(std::collections::HashMap::new())) -} -fn rate_limiter_configs() -> &'static Mutex> { - RATE_LIMITER_CONFIGS.get_or_init(|| Mutex::new(std::collections::HashMap::new())) -} - -// ── Backpressure state ──────────────────────────────────────────────────────── - -#[derive(Clone)] -struct BackpressureController { - high_watermark: i64, - low_watermark: i64, - state: String, -} - -static BACKPRESSURE: OnceLock>> = - OnceLock::new(); - -fn backpressure_map() -> &'static Mutex> { - BACKPRESSURE.get_or_init(|| Mutex::new(std::collections::HashMap::new())) -} - -// ── Shutdown / error hooks ──────────────────────────────────────────────────── - -static SHUTDOWN_HOOKS: OnceLock>> = OnceLock::new(); -static ERROR_HOOKS: OnceLock>> = OnceLock::new(); - -fn shutdown_hooks() -> &'static Mutex> { - SHUTDOWN_HOOKS.get_or_init(|| Mutex::new(Vec::new())) -} -fn error_hooks() -> &'static Mutex> { - ERROR_HOOKS.get_or_init(|| Mutex::new(Vec::new())) -} - -fn run_shutdown_hooks() { - let hooks: Vec = { - let guard = shutdown_hooks().lock().unwrap_or_else(|e: std::sync::PoisonError>>| e.into_inner()); - guard.iter().rev().cloned().collect() - }; - if hooks.is_empty() { return; } - let instr_arc = SERVE_INSTRUCTIONS.with(|si| si.borrow().clone()); - let fn_arc = SERVE_FN_TABLE.with(|sf| sf.borrow().clone()); - if let (Some(instr), Some(fns)) = (instr_arc, fn_arc) { - for handler in hooks { - if let Some(&entry) = fns.get(&handler) { - run_sub_interpreter(&instr, &fns, entry); - } - } - } -} - -thread_local! { - /// LLM model name → (endpoint_url, api_key) - static LLM_CONFIG: std::cell::RefCell> = - std::cell::RefCell::new({ - let mut m = std::collections::HashMap::new(); - // Claude via Anthropic API - m.insert("claude".to_string(), ( - "https://api.anthropic.com/v1/messages".to_string(), - std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(), - )); - m.insert("claude-3-5-sonnet".to_string(), ( - "https://api.anthropic.com/v1/messages".to_string(), - std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(), - )); - m.insert("claude-opus".to_string(), ( - "https://api.anthropic.com/v1/messages".to_string(), - std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(), - )); - // OpenAI - m.insert("gpt4o".to_string(), ( - "https://api.openai.com/v1/chat/completions".to_string(), - std::env::var("OPENAI_API_KEY").unwrap_or_default(), - )); - m.insert("gpt4".to_string(), ( - "https://api.openai.com/v1/chat/completions".to_string(), - std::env::var("OPENAI_API_KEY").unwrap_or_default(), - )); - // Local Ollama - m.insert("ollama".to_string(), ( - "http://localhost:11434/api/chat".to_string(), - String::new(), - )); - // Neuron inference — lives at neuron.neurontechnologies.ai, Anthropic Messages format - m.insert("neuron".to_string(), ( - std::env::var("NEURON_ENDPOINT") - .unwrap_or_else(|_| "https://neuron.neurontechnologies.ai/v1/messages".to_string()), - std::env::var("NEURON_API_KEY").unwrap_or_default(), - )); - m - }); -} - -// ── Inter-thread channel registry (global, cross-thread) ───────────────────── -// -// Named channels backed by crossbeam bounded channels. - -struct ChannelEntry { - sender: crossbeam_channel::Sender, - receiver: crossbeam_channel::Receiver, -} - -static CHANNEL_REGISTRY: std::sync::OnceLock< - std::sync::Mutex> -> = std::sync::OnceLock::new(); - -fn channel_registry() - -> &'static std::sync::Mutex> -{ - CHANNEL_REGISTRY.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())) -} - -// ── Timer/scheduler registry (global, cross-thread) ────────────────────────── - -static TIMER_REGISTRY: std::sync::OnceLock< - std::sync::Mutex>> -> = std::sync::OnceLock::new(); - -fn timer_registry() - -> &'static std::sync::Mutex>> -{ - TIMER_REGISTRY.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())) -} - -static TIMER_ID_COUNTER: std::sync::atomic::AtomicU64 = - std::sync::atomic::AtomicU64::new(1); - -fn next_timer_id() -> String { - let id = TIMER_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - format!("tmr:{}", id) -} - -// ── LLM helper functions ────────────────────────────────────────────────────── - -/// Map a short model name to the actual Anthropic model id string. -fn get_anthropic_model(name: &str) -> &'static str { - match name { - "claude-opus" | "claude-3-opus" => "claude-opus-4-5", - "claude-haiku" => "claude-haiku-4-5", - _ => "claude-sonnet-4-5", - } -} - -/// Call an LLM synchronously. Returns the response text (or an error string). -/// Supports Anthropic, Ollama, and OpenAI-compatible endpoints based on URL. -fn call_llm_blocking(model: &str, system: Option<&str>, prompt: &str) -> String { - let (endpoint, api_key) = LLM_CONFIG.with(|c| { - c.borrow() - .get(model) - .cloned() - .unwrap_or_else(|| ("http://localhost:11434/api/chat".to_string(), String::new())) - }); - - let client = match reqwest::blocking::Client::builder() - .timeout(std::time::Duration::from_secs(120)) - .build() - { - Ok(c) => c, - Err(e) => return format!("llm_call error: {}", e), - }; - - if endpoint.contains("anthropic.com") || endpoint.contains("neuron.neurontechnologies.ai") { - // Anthropic Messages API — also used by neuron.neurontechnologies.ai - let body = if let Some(sys) = system { - serde_json::json!({ - "model": get_anthropic_model(model), - "max_tokens": 4096, - "system": sys, - "messages": [{"role": "user", "content": prompt}] - }) - } else { - serde_json::json!({ - "model": get_anthropic_model(model), - "max_tokens": 4096, - "messages": [{"role": "user", "content": prompt}] - }) - }; - match client - .post(&endpoint) - .header("x-api-key", &api_key) - .header("anthropic-version", "2023-06-01") - .header("content-type", "application/json") - .json(&body) - .send() - { - Ok(r) => match r.json::() { - Ok(v) => v["content"][0]["text"].as_str().unwrap_or("").to_string(), - Err(e) => format!("llm_call error: {}", e), - }, - Err(e) => format!("llm_call error: {}", e), - } - } else if endpoint.contains("ollama") || endpoint.contains("11434") { - // Ollama chat API - let model_name = model.trim_start_matches("ollama:").to_string(); - let messages_arr = if let Some(sys) = system { - serde_json::json!([ - {"role": "system", "content": sys}, - {"role": "user", "content": prompt} - ]) - } else { - serde_json::json!([{"role": "user", "content": prompt}]) - }; - let body = serde_json::json!({ - "model": if model_name == "ollama" { "llama3.2".to_string() } else { model_name }, - "messages": messages_arr, - "stream": false - }); - match client.post(&endpoint).json(&body).send() { - Ok(r) => match r.json::() { - Ok(v) => v["message"]["content"].as_str().unwrap_or("").to_string(), - Err(e) => format!("llm_call error: {}", e), - }, - Err(e) => format!("llm_call error: {}", e), - } - } else { - // OpenAI-compatible format (OpenAI, Neuron, and most others) - let messages_arr = if let Some(sys) = system { - serde_json::json!([ - {"role": "system", "content": sys}, - {"role": "user", "content": prompt} - ]) - } else { - serde_json::json!([{"role": "user", "content": prompt}]) - }; - let model_id = model - .replace("gpt4o", "gpt-4o") - .replace("gpt4", "gpt-4-turbo"); - let body = serde_json::json!({ - "model": model_id, - "messages": messages_arr, - "max_tokens": 4096 - }); - match client - .post(&endpoint) - .header("Authorization", format!("Bearer {}", api_key)) - .header("content-type", "application/json") - .json(&body) - .send() - { - Ok(r) => match r.json::() { - Ok(v) => v["choices"][0]["message"]["content"] - .as_str() - .unwrap_or("") - .to_string(), - Err(e) => format!("llm_call error: {}", e), - }, - Err(e) => format!("llm_call error: {}", e), - } - } -} - -/// Calls Anthropic vision (multimodal) API with a base64-encoded image + text message. -/// Returns the model's text response, or an error string. -fn call_llm_vision_blocking(model: &str, system: &str, message: &str, image_b64: &str) -> String { - let (endpoint, api_key) = LLM_CONFIG.with(|c| { - c.borrow() - .get(model) - .cloned() - .unwrap_or_else(|| ( - "https://api.anthropic.com/v1/messages".to_string(), - std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(), - )) - }); - - let client = match reqwest::blocking::Client::builder() - .timeout(std::time::Duration::from_secs(120)) - .build() - { - Ok(c) => c, - Err(e) => return format!("llm_vision error: {}", e), - }; - - if !endpoint.contains("anthropic.com") && !endpoint.contains("neurontechnologies.ai") { - return "llm_vision: only Anthropic API supports vision".to_string(); - } - - let content = serde_json::json!([ - { - "type": "image", - "source": { - "type": "base64", - "media_type": "image/jpeg", - "data": image_b64 - } - }, - { - "type": "text", - "text": if message.is_empty() { "What do you see?" } else { message } - } - ]); - - let body = if system.is_empty() { - serde_json::json!({ - "model": get_anthropic_model(model), - "max_tokens": 1024, - "messages": [{"role": "user", "content": content}] - }) - } else { - serde_json::json!({ - "model": get_anthropic_model(model), - "max_tokens": 1024, - "system": system, - "messages": [{"role": "user", "content": content}] - }) - }; - - match client - .post(&endpoint) - .header("x-api-key", &api_key) - .header("anthropic-version", "2023-06-01") - .header("content-type", "application/json") - .json(&body) - .send() - { - Ok(r) => match r.json::() { - Ok(v) => v["content"][0]["text"].as_str().unwrap_or("").to_string(), - Err(e) => format!("llm_vision error: {}", e), - }, - Err(e) => format!("llm_vision error: {}", e), - } -} - -/// Full agentic tool-use loop for the Anthropic Messages API. -/// -/// Calls the model with tools defined. If the model returns tool_use blocks, -/// executes each tool locally and feeds the results back. Loops up to -/// `max_iter` times (default 5). Returns the final assistant text response. -fn call_llm_agentic_blocking( - model: &str, - system: &str, - user_message: &str, - tools_json: &str, -) -> String { - let (endpoint, api_key) = LLM_CONFIG.with(|c| { - c.borrow() - .get(model) - .cloned() - .unwrap_or_else(|| ( - "https://api.anthropic.com/v1/messages".to_string(), - std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(), - )) - }); - - if !endpoint.contains("anthropic.com") && !endpoint.contains("neurontechnologies.ai") { - return "llm_call_agentic: only Anthropic API supported".to_string(); - } - - let client = match reqwest::blocking::Client::builder() - .timeout(std::time::Duration::from_secs(120)) - .build() - { - Ok(c) => c, - Err(e) => return format!("llm_call_agentic error: {e}"), - }; - - // Parse tools JSON — default to empty array if invalid - let tools_val: serde_json::Value = serde_json::from_str(tools_json) - .unwrap_or(serde_json::json!([])); - - // Build initial messages array - let mut messages: Vec = vec![ - serde_json::json!({"role": "user", "content": user_message}) - ]; - - let max_iter = 5usize; - - for _iter in 0..max_iter { - let body = serde_json::json!({ - "model": get_anthropic_model(model), - "max_tokens": 4096, - "system": system, - "tools": tools_val, - "messages": messages, - }); - - let resp = match client - .post(&endpoint) - .header("x-api-key", &api_key) - .header("anthropic-version", "2023-06-01") - .header("content-type", "application/json") - .json(&body) - .send() - { - Ok(r) => match r.json::() { - Ok(v) => v, - Err(e) => return format!("llm_call_agentic: parse error: {e}"), - }, - Err(e) => return format!("llm_call_agentic: request error: {e}"), - }; - - let stop_reason = resp["stop_reason"].as_str().unwrap_or("end_turn"); - let content = match resp["content"].as_array() { - Some(c) => c.clone(), - None => { - // Error response from Anthropic - if let Some(err) = resp.get("error") { - return format!("llm_call_agentic: API error: {err}"); - } - return "llm_call_agentic: empty response".to_string(); - } - }; - - if stop_reason == "end_turn" || stop_reason == "stop_sequence" { - // Extract final text - for block in &content { - if block["type"].as_str() == Some("text") { - return block["text"].as_str().unwrap_or("").to_string(); - } - } - return String::new(); - } - - if stop_reason == "tool_use" { - // Add assistant message with tool use blocks - messages.push(serde_json::json!({ - "role": "assistant", - "content": content.clone() - })); - - // Execute each tool call and collect results - let mut tool_results: Vec = Vec::new(); - - for block in &content { - if block["type"].as_str() != Some("tool_use") { - continue; - } - let tool_id = block["id"].as_str().unwrap_or("").to_string(); - let tool_name = block["name"].as_str().unwrap_or("").to_string(); - let input = &block["input"]; - - let result = execute_tool(&tool_name, input); - - tool_results.push(serde_json::json!({ - "type": "tool_result", - "tool_use_id": tool_id, - "content": result, - })); - } - - // Add tool results as user message - messages.push(serde_json::json!({ - "role": "user", - "content": tool_results, - })); - - continue; - } - - // Unexpected stop reason — extract any text and return - for block in &content { - if block["type"].as_str() == Some("text") { - return block["text"].as_str().unwrap_or("").to_string(); - } - } - break; - } - - "llm_call_agentic: max iterations reached".to_string() -} - -/// Execute a named tool with the given input JSON. -/// Returns a string result (success or error message). -fn execute_tool(name: &str, input: &serde_json::Value) -> String { - match name { - "read_file" => { - let path = input["path"].as_str().unwrap_or(""); - if path.is_empty() { - return "error: path is required".to_string(); - } - std::fs::read_to_string(path) - .unwrap_or_else(|e| format!("error reading {path}: {e}")) - } - "write_file" => { - let path = input["path"].as_str().unwrap_or(""); - let content = input["content"].as_str().unwrap_or(""); - if path.is_empty() { - return "error: path is required".to_string(); - } - match std::fs::write(path, content) { - Ok(_) => format!("wrote {} bytes to {path}", content.len()), - Err(e) => format!("error writing {path}: {e}"), - } - } - "list_files" => { - let path = input["path"].as_str().unwrap_or("."); - match std::fs::read_dir(path) { - Ok(entries) => { - let names: Vec = entries - .filter_map(|e| e.ok()) - .map(|e| e.file_name().to_string_lossy().to_string()) - .collect(); - serde_json::to_string(&names).unwrap_or_default() - } - Err(e) => format!("error listing {path}: {e}"), - } - } - "web_get" => { - let url = input["url"].as_str().unwrap_or(""); - if url.is_empty() { - return "error: url is required".to_string(); - } - reqwest::blocking::get(url) - .and_then(|r| r.text()) - .unwrap_or_else(|e| format!("error fetching {url}: {e}")) - } - "web_post" => { - let url = input["url"].as_str().unwrap_or(""); - let body = input["body"].as_str().unwrap_or("{}"); - if url.is_empty() { - return "error: url is required".to_string(); - } - reqwest::blocking::Client::new() - .post(url) - .header("Content-Type", "application/json") - .body(body.to_owned()) - .send() - .and_then(|r| r.text()) - .unwrap_or_else(|e| format!("error posting to {url}: {e}")) - } - "search_memory" => { - let query = input["query"].as_str().unwrap_or(""); - if query.is_empty() { - return "error: query is required".to_string(); - } - let encoded = query.replace(' ', "%20"); - let url = format!("http://localhost:8742/api/search?q={encoded}&limit=10"); - reqwest::blocking::get(&url) - .and_then(|r| r.text()) - .unwrap_or_else(|e| format!("error searching memory: {e}")) - } - "run_command" => { - let cmd = input["command"].as_str().unwrap_or(""); - if cmd.is_empty() { - return "error: command is required".to_string(); - } - match std::process::Command::new("sh") - .arg("-c") - .arg(cmd) - .output() - { - Ok(out) => { - let stdout = String::from_utf8_lossy(&out.stdout).to_string(); - let stderr = String::from_utf8_lossy(&out.stderr).to_string(); - if stderr.is_empty() { - stdout - } else { - format!("stdout: {stdout}\nstderr: {stderr}") - } - } - Err(e) => format!("error running command: {e}"), - } - } - _ => format!("unknown tool: {name}"), - } -} - -/// Compare two runtime values for ordering (used by Lt/Gt/LtEq/GtEq). -fn cmp_values(a: &el_compiler::Value, b: &el_compiler::Value) -> std::cmp::Ordering { - use el_compiler::Value; - match (a, b) { - (Value::Int(x), Value::Int(y)) => x.cmp(y), - (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal), - (Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal), - (Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(std::cmp::Ordering::Equal), - (Value::Str(x), Value::Str(y)) => x.cmp(y), - _ => std::cmp::Ordering::Equal, - } -} - -/// Check if a function name is a known built-in (used by run_sub_interpreter). -fn is_builtin(name: &str) -> bool { - matches!(name, - "print" | "println" | "log" | "print_err" | "__build_list__" - | "base64_encode" | "base64_decode" | "base64_url_encode" | "base64_url_decode" - | "hash_sha256" | "blake3_hash" - | "float_to_str" | "int_to_str" | "str_to_int" | "str_to_float" | "parse_float" | "parse_int" | "bool_to_str" - | "bytes_to_str" | "str_to_bytes" - | "json_get" | "json_get_int" | "json_get_string" | "json_get_array" | "json_get_bool" | "json_get_float" | "json_get_raw" - | "json_set" | "json_stringify" | "json_parse" | "json_encode" | "json_decode" | "json_keys" - | "json_array_get" | "json_array_len" | "json_array_push" - | "str_eq" | "str_split" | "str_contains" | "str_starts_with" | "str_ends_with" | "str_replace" - | "str_slice" | "str_index_of" | "str_last_index_of" | "str_trim" | "str_upper" | "str_lower" - | "str_len" | "string_len" - | "list_get" | "list_len" | "list_join" - | "native_list_get" | "native_list_len" | "native_string_chars" - | "native_list_append" | "native_list_empty" | "native_int_to_str" - | "native_str_to_int" | "native_string_contains" - | "uuid_new" | "uuid_v4" - | "unix_timestamp" | "now_millis" | "timestamp" - | "env" | "state_get" | "state_set" | "state_del" | "state_keys" - | "http_get" | "http_post" | "http_put" | "http_delete" - | "http_get_auth" | "http_post_auth" | "http_put_auth" | "http_delete_auth" - | "http_post_form_auth" - | "http_post_engram" | "http_get_engram" - | "engram_relate" | "engram_neighbors" | "engram_activate" - | "fs_read" | "fs_write" | "fs_exists" | "fs_mkdir" | "fs_list" - | "sleep_ms" | "sleep_secs" | "getpid" | "exit" | "exec_bg" | "spawn_thread" - | "http_serve" | "color_bold" - | "tcp_connect" | "tcp_listen" | "tcp_accept" | "tcp_send" | "tcp_recv" | "tcp_close" - | "udp_bind" | "udp_send" | "udp_recv" | "udp_close" - | "ws_connect" | "ws_send" | "ws_recv" | "ws_close" - // Math additions - | "math_sin" | "math_cos" | "math_tan" | "math_asin" | "math_acos" | "math_atan2" - | "math_exp" | "math_ln" | "math_log2" | "math_log10" | "math_mod" | "math_pi" | "math_e" - | "int_to_float" | "float_to_int" | "is_nil" | "unwrap_or" - | "decimal_add" | "decimal_sub" | "decimal_mul" | "decimal_div" | "decimal_round" - // String additions - | "str_char_at" | "str_char_code" | "str_from_char_code" - | "str_pad_left" | "str_pad_right" | "format_float" | "str_format" - // List additions - | "list_push" | "list_pop" | "list_pop_front" | "list_peek_last" | "list_range" - | "list_new" | "list_empty" | "list_append" | "list_set" | "list_concat" - | "list_slice" | "list_reverse" | "list_contains" - | "stack_push" | "stack_pop" | "stack_peek" | "stack_new" - | "queue_enqueue" | "queue_dequeue" | "queue_peek" | "queue_new" - | "list_map" | "list_filter" | "list_reduce" | "fn_ref" - // String character mutation - | "str_set_char" - // Shell and TTY - | "shell_exec" | "native_tty_write" | "native_tty_size" - | "native_tty_read" // reserved for future raw key input - // Time - | "time_now_utc" | "time_to_parts" | "time_from_parts" | "time_format" | "time_parse" - | "time_add" | "time_diff" | "time_start_of" | "time_tz_offset" | "time_to_tz" - // Observer - | "observe" | "unobserve" - // LLM native functions - | "llm_call" | "llm_call_system" | "llm_vision" | "llm_call_agentic" | "llm_stream" | "llm_parallel" | "llm_models" | "llm_configure" - // GC / memory management - | "gc_collect" | "gc_stats" | "gc_set_threshold" | "mem_limit_set" | "mem_usage" - // Rate limiting - | "rate_limit_create" | "rate_limit_check" | "rate_limit_wait" - // Backpressure - | "backpressure_create" | "backpressure_signal" | "backpressure_check" - // App block context builtins - | "ctx_env" | "ctx_service" | "ctx_version" | "ctx_instance" - | "config" | "secret" | "flag" - // Lifecycle hooks - | "on_shutdown" | "on_error" | "exit_clean" - // Cache - | "cache_set" | "cache_get" | "cache_invalidate" | "cache_clear" - // HTTP retry - | "http_get_retry" | "http_post_retry" - // Circuit breaker - | "circuit_open" | "circuit_call" - // WebSocket server - | "ws_serve" | "ws_serve_poll" | "ws_server_send" | "ws_broadcast" | "ws_server_close" - // WebSocket client (async/poll-based) - | "ws_connect_async" | "ws_client_send" | "ws_client_close" | "ws_client_poll" - // Subprocess - | "sh" | "sh_async" - // Crypto - | "crypto_keygen_ed25519" | "crypto_sign_ed25519" | "crypto_verify_ed25519" - | "crypto_encrypt_aes" | "crypto_decrypt_aes" - | "crypto_hash_sha256" | "crypto_hash_blake3" - | "crypto_random_bytes" | "crypto_uuid" - // Time extras - | "time_now_ms" | "time_now_s" - // Timers - | "timer_after" | "timer_every" | "timer_cancel" - | "cron_schedule" | "cron_cancel" - // Channels - | "chan_create" | "chan_send" | "chan_recv" | "chan_close" | "chan_len" - // Explicit observability - | "log_debug" | "log_info" | "log_warn" | "log_error" - | "trace_start" | "trace_end" | "trace_tag" - | "metric_counter" | "metric_gauge" - ) -} - -/// Interpreter with debugger support — emits DebugEvents as it runs. -fn run_interpreter_debug(instructions: &[el_compiler::Bytecode], debugger: &mut el_compiler::Debugger) { - use el_compiler::{Bytecode, Value}; - let mut stack: Vec = Vec::new(); - let mut locals: std::collections::HashMap = std::collections::HashMap::new(); - let mut ip = 0usize; - let program_args: Vec = std::env::args().skip(2).collect(); // skip 'el' and 'debug' - - while ip < instructions.len() { - // Check if we should pause here - if debugger.should_pause(ip) { - debugger.on_pause(ip, locals.clone()); - for event in debugger.drain_events() { - match event { - el_compiler::DebugEvent::Breakpoint { offset, frame } => { - println!("[break] offset={offset} fn={} {}:{}:{}", frame.function_name, frame.source_file, frame.line, frame.col); - } - el_compiler::DebugEvent::Step { frame, locals: step_locals } => { - let var_list: Vec = step_locals.iter() - .map(|(k, v)| format!("{k}={v}")) - .collect(); - println!("[step] offset={ip} {}:{} vars=[{}]", frame.line, frame.col, var_list.join(", ")); - } - _ => {} - } - } - } - - match &instructions[ip] { - Bytecode::Push(v) => stack.push(v.clone()), - Bytecode::Pop => { stack.pop(); } - Bytecode::Dup => { - if let Some(top) = stack.last().cloned() { stack.push(top); } - } - Bytecode::Add => { - 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::Int(x + y), - (Value::Float(x), Value::Float(y)) => Value::Float(x + y), - (Value::Str(x), Value::Str(y)) => Value::Str(x + &y), - _ => Value::Nil, - }); - } - Bytecode::StoreLocal(name) => { - let v = stack.pop().unwrap_or(Value::Nil); - locals.insert(name.clone(), v); - } - Bytecode::LoadLocal(name) => { - let v = locals.get(name).cloned().unwrap_or(Value::Nil); - stack.push(v); - } - Bytecode::GetField(field) => { - let obj = stack.pop().unwrap_or(Value::Nil); - let result = match &obj { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - Value::Struct { fields, .. } => fields.iter() - .find(|(n, _)| n == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - _ => Value::Nil, - }; - stack.push(result); - } - Bytecode::BuildStruct { type_name, fields } => { - let n = fields.len(); - let mut field_values: Vec = (0..n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect(); - field_values.reverse(); - let struct_fields: Vec<(String, Value)> = fields.iter().cloned() - .zip(field_values.into_iter()) - .collect(); - stack.push(Value::Struct { - type_name: type_name.clone(), - fields: struct_fields, - }); - } - Bytecode::Call { name, arity } => { - let result = dispatch_builtin(name, *arity, &mut stack, &program_args); - match result { - BuiltinResult::Handled | BuiltinResult::NotBuiltin | BuiltinResult::HttpServe => {} - BuiltinResult::Exit(code) => std::process::exit(code), - } - } - Bytecode::Eq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(a == b)); - } - Bytecode::Jump(offset) => { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - Bytecode::JumpIf(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if matches!(cond, Value::Bool(true)) { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - } - Bytecode::JumpIfNot(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - let new_ip = (ip as i32 + 1 + offset) as usize; - ip = new_ip; - continue; - } - } - Bytecode::Return | Bytecode::Halt => { - debugger.pop_frame(stack.last().cloned().unwrap_or(Value::Nil)); - break; - } - _ => {} - } - ip += 1; - } -} diff --git a/_archive/rust-bootstrap/bin/el/src/net.rs b/_archive/rust-bootstrap/bin/el/src/net.rs deleted file mode 100644 index 0b93b99..0000000 --- a/_archive/rust-bootstrap/bin/el/src/net.rs +++ /dev/null @@ -1,730 +0,0 @@ -//! Networking enhancements: HTTP retry/backoff, circuit breaker, and WebSocket server. -//! -//! Everything here is synchronous / blocking to match the rest of the El runtime -//! (which uses `reqwest::blocking` throughout). WebSocket server connections run -//! on background OS threads; the interpreter thread itself never blocks waiting -//! for the server. - -use std::collections::HashMap; -use std::net::TcpListener; -use std::sync::{Arc, Mutex, OnceLock}; -use std::time::{Duration, Instant}; - -// ───────────────────────────────────────────────────────────────────────────── -// HTTP retry with exponential back-off -// ───────────────────────────────────────────────────────────────────────────── - -/// Perform an HTTP GET, retrying on 5xx responses or connection errors. -/// -/// `max_attempts` — total number of attempts (1 = no retry). -/// `backoff_ms` — initial back-off in milliseconds; doubles each attempt. -/// -/// Returns the response body on the first successful (non-5xx) response, or an -/// error JSON string after all attempts are exhausted. -pub fn http_get_retry(url: &str, max_attempts: u32, backoff_ms: u64) -> String { - let client = reqwest::blocking::Client::builder() - .timeout(Duration::from_secs(30)) - .build() - .unwrap_or_default(); - - let mut delay = backoff_ms; - for attempt in 0..max_attempts.max(1) { - match client.get(url).send() { - Ok(resp) => { - let status = resp.status().as_u16(); - let body = resp.text().unwrap_or_default(); - if status < 500 { - return body; - } - // 5xx — retry unless this was the last attempt - if attempt + 1 < max_attempts { - std::thread::sleep(Duration::from_millis(delay)); - delay *= 2; - } else { - return format!( - "{{\"error\":\"http_get_retry: server error {status} after {max_attempts} attempt(s)\",\"body\":{body:?}}}" - ); - } - } - Err(e) => { - if attempt + 1 < max_attempts { - std::thread::sleep(Duration::from_millis(delay)); - delay *= 2; - } else { - return format!( - "{{\"error\":\"http_get_retry: {e} after {max_attempts} attempt(s)\"}}" - ); - } - } - } - } - format!("{{\"error\":\"http_get_retry: no attempts executed\"}}") -} - -/// Perform an HTTP POST with JSON body, retrying on 5xx or connection errors. -/// -/// Same retry semantics as [`http_get_retry`]. -pub fn http_post_retry(url: &str, body: &str, max_attempts: u32, backoff_ms: u64) -> String { - let client = reqwest::blocking::Client::builder() - .timeout(Duration::from_secs(30)) - .build() - .unwrap_or_default(); - - let mut delay = backoff_ms; - for attempt in 0..max_attempts.max(1) { - match client - .post(url) - .header("Content-Type", "application/json") - .body(body.to_owned()) - .send() - { - Ok(resp) => { - let status = resp.status().as_u16(); - let resp_body = resp.text().unwrap_or_default(); - if status < 500 { - return resp_body; - } - if attempt + 1 < max_attempts { - std::thread::sleep(Duration::from_millis(delay)); - delay *= 2; - } else { - return format!( - "{{\"error\":\"http_post_retry: server error {status} after {max_attempts} attempt(s)\",\"body\":{resp_body:?}}}" - ); - } - } - Err(e) => { - if attempt + 1 < max_attempts { - std::thread::sleep(Duration::from_millis(delay)); - delay *= 2; - } else { - return format!( - "{{\"error\":\"http_post_retry: {e} after {max_attempts} attempt(s)\"}}" - ); - } - } - } - } - format!("{{\"error\":\"http_post_retry: no attempts executed\"}}") -} - -// ───────────────────────────────────────────────────────────────────────────── -// Circuit breaker -// ───────────────────────────────────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -enum CircuitState { - /// Passing requests through normally. - Closed, - /// Too many failures — reject immediately. - Open { - /// When the circuit may attempt to close again. - until: Instant, - }, - /// One probe request allowed through; waiting to see if it succeeds. - HalfOpen, -} - -struct CircuitBreaker { - state: CircuitState, - failure_count: u32, - failure_threshold: u32, - reset_duration: Duration, -} - -impl CircuitBreaker { - fn new(failure_threshold: u32, reset_secs: u64) -> Self { - CircuitBreaker { - state: CircuitState::Closed, - failure_count: 0, - failure_threshold, - reset_duration: Duration::from_secs(reset_secs), - } - } - - /// Returns `true` if the circuit should allow a call through right now. - fn allow(&mut self) -> bool { - match &self.state { - CircuitState::Closed => true, - CircuitState::Open { until } => { - if Instant::now() >= *until { - self.state = CircuitState::HalfOpen; - true - } else { - false - } - } - CircuitState::HalfOpen => true, - } - } - - fn record_success(&mut self) { - self.failure_count = 0; - self.state = CircuitState::Closed; - } - - fn record_failure(&mut self) { - self.failure_count += 1; - if self.failure_count >= self.failure_threshold - || self.state == CircuitState::HalfOpen - { - self.state = CircuitState::Open { - until: Instant::now() + self.reset_duration, - }; - self.failure_count = 0; - } - } -} - -// ── Global circuit-breaker registry ────────────────────────────────────────── - -static CIRCUITS: OnceLock>> = OnceLock::new(); - -fn circuits() -> &'static Mutex> { - CIRCUITS.get_or_init(|| Mutex::new(HashMap::new())) -} - -/// Register (or replace) a circuit breaker with the given name. -pub fn circuit_open(name: String, failure_threshold: u32, reset_secs: u64) { - if let Ok(mut map) = circuits().lock() { - map.insert(name, CircuitBreaker::new(failure_threshold, reset_secs)); - } -} - -/// Make an HTTP POST call through the named circuit breaker. -/// -/// * If the circuit is open: returns `{"error":"circuit open"}` immediately. -/// * If closed/half-open: makes the POST, records success or failure, and -/// returns the response body. -pub fn circuit_call(name: &str, url: &str, body: &str) -> String { - // Check + allow atomically under the lock, then drop the lock before the - // blocking network call (which could take seconds). - let allowed = circuits() - .lock() - .ok() - .and_then(|mut map| map.get_mut(name).map(|cb| cb.allow())) - .unwrap_or(true); // unknown circuit name → allow - - if !allowed { - return r#"{"error":"circuit open"}"#.to_owned(); - } - - let client = reqwest::blocking::Client::builder() - .timeout(Duration::from_secs(30)) - .build() - .unwrap_or_default(); - - match client - .post(url) - .header("Content-Type", "application/json") - .body(body.to_owned()) - .send() - { - Ok(resp) => { - let status = resp.status().as_u16(); - let resp_body = resp.text().unwrap_or_default(); - if status >= 500 { - if let Ok(mut map) = circuits().lock() { - if let Some(cb) = map.get_mut(name) { - cb.record_failure(); - } - } - format!("{{\"error\":\"circuit_call: server error {status}\",\"body\":{resp_body:?}}}") - } else { - if let Ok(mut map) = circuits().lock() { - if let Some(cb) = map.get_mut(name) { - cb.record_success(); - } - } - resp_body - } - } - Err(e) => { - if let Ok(mut map) = circuits().lock() { - if let Some(cb) = map.get_mut(name) { - cb.record_failure(); - } - } - format!("{{\"error\":\"circuit_call: {e}\"}}") - } - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// WebSocket server -// ───────────────────────────────────────────────────────────────────────────── -// -// Implementation strategy -// ─────────────────────── -// The El interpreter is single-threaded and synchronous. We need a WebSocket -// *server* that can accept multiple concurrent clients while the interpreter -// keeps running. -// -// Solution: each accepted connection runs on its own OS thread. Outbound -// messages are queued through a per-client `mpsc` channel. The interpreter -// calls `ws_serve`, `ws_send`, `ws_broadcast`, and `ws_close` — all of which -// return immediately and coordinate with the background threads via shared -// state. -// -// Handler callbacks are *not* invoked on the background threads; instead, -// incoming messages are placed in a global queue and the interpreter drains -// them by calling `ws_poll` (or they are dispatched automatically inside a -// future tight-loop variant of `ws_serve`). -// -// For simplicity this implementation uses `tungstenite` (synchronous), the -// same crate the existing `ws_connect` client already uses. - -use std::sync::mpsc; - -/// Message queued from a background connection thread to the interpreter. -#[derive(Debug)] -pub struct IncomingWsMessage { - pub client_id: String, - pub message: String, -} - -/// Represents one connected WebSocket client. -struct WsClient { - /// Channel sender used to push outbound messages to the background thread. - tx: mpsc::Sender>, // None = disconnect signal -} - -/// Shared server state accessible from both the interpreter thread and the -/// background connection threads. -pub struct WsServerState { - /// Connected clients, keyed by client_id. - clients: HashMap, - /// Messages received from clients waiting to be delivered to the handler. - inbox: Vec, - /// Counter for generating unique client IDs. - next_id: u64, -} - -impl WsServerState { - fn new() -> Self { - WsServerState { - clients: HashMap::new(), - inbox: Vec::new(), - next_id: 1, - } - } - - fn next_client_id(&mut self) -> String { - let id = format!("wsc:{}", self.next_id); - self.next_id += 1; - id - } -} - -// ── Global server state registry (one entry per listening port) ─────────────── - -static WS_SERVERS: OnceLock>>>> = OnceLock::new(); - -fn ws_servers() -> &'static Mutex>>> { - WS_SERVERS.get_or_init(|| Mutex::new(HashMap::new())) -} - -fn get_or_create_server(port: u16) -> Arc> { - let mut map = ws_servers().lock().unwrap(); - map.entry(port) - .or_insert_with(|| Arc::new(Mutex::new(WsServerState::new()))) - .clone() -} - -// ── Public API (called from dispatch_builtin) ───────────────────────────────── - -/// Start a WebSocket server on `port`. -/// -/// This function spawns a background acceptor thread and returns immediately. -/// Incoming connections are handled on per-connection threads. Messages -/// received are pushed into the server's inbox; call [`ws_poll`] to drain them. -pub fn ws_serve_start(port: u16) { - let state = get_or_create_server(port); - - // Spawn acceptor thread. - let state_clone = state.clone(); - std::thread::spawn(move || { - let listener = match TcpListener::bind(format!("0.0.0.0:{port}")) { - Ok(l) => l, - Err(e) => { - eprintln!("[ws_serve] failed to bind port {port}: {e}"); - return; - } - }; - eprintln!("[ws_serve] listening on port {port}"); - - for stream in listener.incoming() { - match stream { - Ok(tcp) => { - let state_for_conn = state_clone.clone(); - std::thread::spawn(move || { - handle_ws_connection(tcp, state_for_conn); - }); - } - Err(e) => { - eprintln!("[ws_serve] accept error: {e}"); - } - } - } - }); -} - -fn handle_ws_connection(stream: std::net::TcpStream, state: Arc>) { - let _ = stream.set_read_timeout(Some(Duration::from_millis(100))); - - let ws_result = tungstenite::accept(stream); - let mut ws = match ws_result { - Ok(w) => w, - Err(e) => { - eprintln!("[ws_serve] WebSocket handshake error: {e}"); - return; - } - }; - - // Assign a client ID. - let client_id = { - let mut s = state.lock().unwrap(); - let id = s.next_client_id(); - // We'll register the sender after we create the channel below. - id - }; - - // Create an outbound channel for this connection. - let (tx, rx) = mpsc::channel::>(); - - { - let mut s = state.lock().unwrap(); - s.clients.insert(client_id.clone(), WsClient { tx }); - } - - eprintln!("[ws_serve] client connected: {client_id}"); - - loop { - // --- Receive incoming messages (non-blocking with short timeout) --- - match ws.read() { - Ok(tungstenite::Message::Text(text)) => { - let mut s = state.lock().unwrap(); - s.inbox.push(IncomingWsMessage { - client_id: client_id.clone(), - message: text.to_string(), - }); - } - Ok(tungstenite::Message::Binary(bytes)) => { - let text = String::from_utf8_lossy(&bytes).into_owned(); - let mut s = state.lock().unwrap(); - s.inbox.push(IncomingWsMessage { - client_id: client_id.clone(), - message: text, - }); - } - Ok(tungstenite::Message::Close(_)) => { - break; - } - Ok(tungstenite::Message::Ping(data)) => { - let _ = ws.send(tungstenite::Message::Pong(data)); - } - Ok(_) => {} - Err(tungstenite::Error::Io(e)) - if e.kind() == std::io::ErrorKind::WouldBlock - || e.kind() == std::io::ErrorKind::TimedOut => - { - // No data yet — check the outbound channel. - } - Err(e) => { - eprintln!("[ws_serve] read error for {client_id}: {e}"); - break; - } - } - - // --- Send queued outbound messages --- - loop { - match rx.try_recv() { - Ok(Some(msg)) => { - if ws - .send(tungstenite::Message::Text(msg.into())) - .is_err() - { - break; - } - } - Ok(None) => { - // Disconnect signal. - let _ = ws.close(None); - let mut s = state.lock().unwrap(); - s.clients.remove(&client_id); - eprintln!("[ws_serve] client disconnected (server-side): {client_id}"); - return; - } - Err(mpsc::TryRecvError::Empty) => break, - Err(mpsc::TryRecvError::Disconnected) => { - break; - } - } - } - } - - // Clean up. - { - let mut s = state.lock().unwrap(); - s.clients.remove(&client_id); - } - eprintln!("[ws_serve] client disconnected: {client_id}"); -} - -/// Send a message to a specific connected client. -/// Returns `false` if the client is not found. -pub fn ws_server_send(port: u16, client_id: &str, message: String) -> bool { - let map = ws_servers().lock().unwrap(); - if let Some(state) = map.get(&port) { - let s = state.lock().unwrap(); - if let Some(client) = s.clients.get(client_id) { - return client.tx.send(Some(message)).is_ok(); - } - } - false -} - -/// Broadcast a message to all connected clients on a port. -pub fn ws_server_broadcast(port: u16, message: String) { - let map = ws_servers().lock().unwrap(); - if let Some(state) = map.get(&port) { - let s = state.lock().unwrap(); - for client in s.clients.values() { - let _ = client.tx.send(Some(message.clone())); - } - } -} - -/// Disconnect a specific client. -pub fn ws_server_close(port: u16, client_id: &str) { - let map = ws_servers().lock().unwrap(); - if let Some(state) = map.get(&port) { - let s = state.lock().unwrap(); - if let Some(client) = s.clients.get(client_id) { - let _ = client.tx.send(None); - } - } -} - -/// Drain pending incoming messages for a given server port. -/// -/// Returns up to `max` messages (or all if `max == 0`). The caller is -/// responsible for invoking the El handler function for each message. -pub fn ws_server_poll(port: u16, max: usize) -> Vec { - let map = ws_servers().lock().unwrap(); - if let Some(state) = map.get(&port) { - let mut s = state.lock().unwrap(); - if max == 0 || s.inbox.len() <= max { - let msgs = std::mem::take(&mut s.inbox); - msgs - } else { - s.inbox.drain(..max).collect() - } - } else { - vec![] - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// WebSocket client with handler callback support -// ───────────────────────────────────────────────────────────────────────────── -// -// `ws_connect` already exists in `main.rs` using synchronous tungstenite. -// This new variant (`ws_connect_handler`) runs the connection on a background -// thread and queues incoming messages so `ws_client_poll` can deliver them to -// the El handler. - -/// Pending message from a background WS client connection. -#[derive(Debug)] -pub struct IncomingClientMessage { - pub conn_id: String, - pub message: String, -} - -struct WsClientConn { - tx: mpsc::Sender>, -} - -static WS_CLIENT_CONNS: OnceLock>> = OnceLock::new(); -static WS_CLIENT_INBOX: OnceLock>> = OnceLock::new(); -static WS_CLIENT_COUNTER: OnceLock> = OnceLock::new(); - -fn ws_client_conns() -> &'static Mutex> { - WS_CLIENT_CONNS.get_or_init(|| Mutex::new(HashMap::new())) -} - -fn ws_client_inbox() -> &'static Mutex> { - WS_CLIENT_INBOX.get_or_init(|| Mutex::new(Vec::new())) -} - -fn next_conn_id() -> String { - let mut ctr = WS_CLIENT_COUNTER - .get_or_init(|| Mutex::new(1)) - .lock() - .unwrap(); - let id = format!("wsconn:{}", *ctr); - *ctr += 1; - id -} - -/// Connect to a WebSocket server in the background. -/// -/// Returns a `conn_id` string immediately. Incoming messages are queued and -/// can be retrieved with [`ws_client_poll`]. -pub fn ws_client_connect(url: &str) -> String { - let conn_id = next_conn_id(); - let (tx, rx) = mpsc::channel::>(); - - { - let mut map = ws_client_conns().lock().unwrap(); - map.insert(conn_id.clone(), WsClientConn { tx }); - } - - let url_owned = url.to_owned(); - let conn_id_clone = conn_id.clone(); - - std::thread::spawn(move || { - let ws_result = tungstenite::connect(&url_owned); - let (mut ws, _) = match ws_result { - Ok(pair) => pair, - Err(e) => { - eprintln!("[ws_client] connect error for {conn_id_clone}: {e}"); - let mut map = ws_client_conns().lock().unwrap(); - map.remove(&conn_id_clone); - return; - } - }; - - // Set a short read timeout so the loop stays responsive to outbound messages. - // MaybeTlsStream exposes the underlying stream via get_ref/get_mut. - { - use tungstenite::stream::MaybeTlsStream; - match ws.get_mut() { - MaybeTlsStream::Plain(tcp) => { - let _ = tcp.set_read_timeout(Some(Duration::from_millis(50))); - } - #[cfg(feature = "native-tls")] - MaybeTlsStream::NativeTls(tls) => { - let _ = tls.get_ref().set_read_timeout(Some(Duration::from_millis(50))); - } - _ => {} - } - } - - loop { - // Receive from server. - match ws.read() { - Ok(tungstenite::Message::Text(text)) => { - let mut inbox = ws_client_inbox().lock().unwrap(); - inbox.push(IncomingClientMessage { - conn_id: conn_id_clone.clone(), - message: text.to_string(), - }); - } - Ok(tungstenite::Message::Binary(bytes)) => { - let text = String::from_utf8_lossy(&bytes).into_owned(); - let mut inbox = ws_client_inbox().lock().unwrap(); - inbox.push(IncomingClientMessage { - conn_id: conn_id_clone.clone(), - message: text, - }); - } - Ok(tungstenite::Message::Close(_)) => break, - Ok(tungstenite::Message::Ping(data)) => { - let _ = ws.send(tungstenite::Message::Pong(data)); - } - Ok(_) => {} - Err(tungstenite::Error::Io(e)) - if e.kind() == std::io::ErrorKind::WouldBlock - || e.kind() == std::io::ErrorKind::TimedOut => - { - // No data yet. - } - Err(e) => { - eprintln!("[ws_client] read error for {conn_id_clone}: {e}"); - break; - } - } - - // Send queued outbound messages. - loop { - match rx.try_recv() { - Ok(Some(msg)) => { - if ws.send(tungstenite::Message::Text(msg.into())).is_err() { - break; - } - } - Ok(None) => { - let _ = ws.close(None); - let mut map = ws_client_conns().lock().unwrap(); - map.remove(&conn_id_clone); - return; - } - Err(mpsc::TryRecvError::Empty) => break, - Err(mpsc::TryRecvError::Disconnected) => break, - } - } - } - - let mut map = ws_client_conns().lock().unwrap(); - map.remove(&conn_id_clone); - }); - - conn_id -} - -/// Send a message on an existing client connection. -pub fn ws_client_send(conn_id: &str, message: String) { - let map = ws_client_conns().lock().unwrap(); - if let Some(conn) = map.get(conn_id) { - let _ = conn.tx.send(Some(message)); - } -} - -/// Close a client connection. -pub fn ws_client_close(conn_id: &str) { - let map = ws_client_conns().lock().unwrap(); - if let Some(conn) = map.get(conn_id) { - let _ = conn.tx.send(None); - } -} - -/// Drain pending incoming messages for client connections. -/// -/// Returns all queued messages (all connections combined). Pass `conn_id` as -/// `Some(&str)` to filter to a specific connection, or `None` for all. -pub fn ws_client_poll(conn_id_filter: Option<&str>) -> Vec { - let mut inbox = ws_client_inbox().lock().unwrap(); - if let Some(filter) = conn_id_filter { - let (matching, rest): (Vec<_>, Vec<_>) = - std::mem::take(&mut *inbox) - .into_iter() - .partition(|m| m.conn_id == filter); - *inbox = rest; - matching - } else { - std::mem::take(&mut *inbox) - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// Transient retry wrapper (for enhancing existing http_get / http_post) -// ───────────────────────────────────────────────────────────────────────────── - -/// Try `f` once; on connection error, wait `delay_ms` and try once more. -/// -/// This is the "automatic single retry on transient network error" behaviour -/// layered over the existing `http_get` / `http_post` builtins. -pub fn with_single_retry(delay_ms: u64, f: F) -> String -where - F: Fn() -> Result, -{ - match f() { - Ok(body) => body, - Err(e) if e.is_connect() || e.is_timeout() => { - std::thread::sleep(Duration::from_millis(delay_ms)); - f().unwrap_or_else(|e2| format!("{{\"error\":\"{e2}\"}}")) - } - Err(e) => format!("{{\"error\":\"{e}\"}}"), - } -} diff --git a/_archive/rust-bootstrap/bin/el/src/telemetry.rs b/_archive/rust-bootstrap/bin/el/src/telemetry.rs deleted file mode 100644 index 5ff502c..0000000 --- a/_archive/rust-bootstrap/bin/el/src/telemetry.rs +++ /dev/null @@ -1,641 +0,0 @@ -//! Automatic, zero-config observability for the El runtime. -//! -//! This module implements: -//! - A background OTLP/HTTP exporter (spans + logs + metrics) -//! - Thread-local span context for propagation -//! - Helper functions called by the interpreter's builtin dispatch -//! -//! Developers never need to call anything here directly. The interpreter -//! instruments everything automatically. Optional `log_*`, `trace_*`, and -//! `metric_*` builtins are also wired through this module for explicit use. -//! -//! ## Graceful degradation -//! -//! If the OTLP endpoint is unreachable, a single warning is emitted to stderr -//! on the first failure, then telemetry is silently dropped. Programs never -//! fail because observability is down. - -use std::sync::{ - OnceLock, - atomic::{AtomicBool, Ordering}, -}; -use std::sync::mpsc::{self, SyncSender}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::collections::HashMap; - -// ── Public re-export ────────────────────────────────────────────────────────── - -pub use context::SpanGuard; - -// ── Constants ───────────────────────────────────────────────────────────────── - -const DEFAULT_OTLP_ENDPOINT: &str = "http://alloy.neuralplatform.ai:4318"; -const BATCH_SIZE: usize = 64; -const BATCH_TIMEOUT_MS: u64 = 5_000; - -// ── Telemetry payload types ─────────────────────────────────────────────────── - -#[derive(Clone, Debug)] -pub struct Span { - pub trace_id: String, - pub span_id: String, - pub parent_id: Option, - pub name: String, - pub start_ns: u64, - pub end_ns: u64, - pub status: SpanStatus, - pub attrs: Vec<(String, AttrValue)>, - pub events: Vec, - pub service: String, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum SpanStatus { - Ok, - Error(String), -} - -#[derive(Clone, Debug)] -pub struct SpanEvent { - pub name: String, - pub time_ns: u64, - pub attrs: Vec<(String, AttrValue)>, -} - -#[derive(Clone, Debug)] -pub enum AttrValue { - Str(String), - Int(i64), - Float(f64), - Bool(bool), -} - -#[derive(Clone, Debug)] -pub struct LogRecord { - pub time_ns: u64, - pub severity: LogSeverity, - pub body: String, - pub attrs: Vec<(String, AttrValue)>, - pub service: String, - pub trace_id: Option, - pub span_id: Option, -} - -#[derive(Clone, Debug, Copy)] -pub enum LogSeverity { - Debug, - Info, - Warn, - Error, -} - -impl LogSeverity { - fn number(self) -> u32 { - match self { - LogSeverity::Debug => 5, - LogSeverity::Info => 9, - LogSeverity::Warn => 13, - LogSeverity::Error => 17, - } - } - fn text(self) -> &'static str { - match self { - LogSeverity::Debug => "DEBUG", - LogSeverity::Info => "INFO", - LogSeverity::Warn => "WARN", - LogSeverity::Error => "ERROR", - } - } -} - -#[derive(Clone, Debug)] -pub struct Metric { - pub name: String, - pub kind: MetricKind, - pub value: f64, - pub attrs: Vec<(String, AttrValue)>, - pub time_ns: u64, - pub service: String, -} - -#[derive(Clone, Debug)] -pub enum MetricKind { - Counter, - Gauge, -} - -#[derive(Clone, Debug)] -enum TelemetryItem { - Span(Span), - Log(LogRecord), - Metric(Metric), -} - -// ── Global telemetry sender ─────────────────────────────────────────────────── - -static TELEMETRY_TX: OnceLock>> = OnceLock::new(); -static OTLP_WARNED: AtomicBool = AtomicBool::new(false); - -/// Initialise the telemetry background thread. Called once on interpreter start. -/// `service_name` is the El program filename (without extension). -pub fn init(service_name: &str) { - let endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT") - .unwrap_or_else(|_| DEFAULT_OTLP_ENDPOINT.to_string()); - let svc = service_name.to_string(); - - // Bounded channel — if the exporter falls behind, new items are dropped. - let (tx, rx) = mpsc::sync_channel::(4096); - - // Store the sender before starting the thread so callers can send immediately. - TELEMETRY_TX.get_or_init(|| Some(tx)); - - std::thread::Builder::new() - .name("el-telemetry".to_string()) - .spawn(move || { - exporter_loop(rx, &endpoint, &svc); - }) - .ok(); // If the thread fails to spawn, we degrade silently. -} - -/// Send one telemetry item. Never panics. Drops if channel is full or uninitialised. -fn send(item: TelemetryItem) { - if let Some(Some(tx)) = TELEMETRY_TX.get() { - let _ = tx.try_send(item); - } -} - -// ── Span builder / emitter ──────────────────────────────────────────────────── - -pub fn emit_span(span: Span) { - send(TelemetryItem::Span(span)); -} - -pub fn emit_log(record: LogRecord) { - send(TelemetryItem::Log(record)); -} - -pub fn emit_metric(metric: Metric) { - send(TelemetryItem::Metric(metric)); -} - -// ── Thread-local context ────────────────────────────────────────────────────── - -pub mod context { - use super::*; - - thread_local! { - /// Stack of active span IDs for the current thread. - static SPAN_STACK: std::cell::RefCell> = - std::cell::RefCell::new(Vec::new()); - } - - /// Get the current (innermost) span context: (trace_id, span_id). - pub fn current_span() -> Option<(String, String)> { - SPAN_STACK.with(|s| s.borrow().last().cloned()) - } - - /// Push a span context onto the thread-local stack. - pub fn push_span(trace_id: String, span_id: String) { - SPAN_STACK.with(|s| s.borrow_mut().push((trace_id, span_id))); - } - - /// Pop the innermost span context. - pub fn pop_span() { - SPAN_STACK.with(|s| { s.borrow_mut().pop(); }); - } - - /// A RAII guard that closes the span when it goes out of scope. - pub struct SpanGuard { - pub span: Span, - } - - impl SpanGuard { - pub fn new(name: &str, service: &str) -> Self { - let (trace_id, parent_id) = current_span() - .map(|(t, s)| (t, Some(s))) - .unwrap_or_else(|| (new_trace_id(), None)); - let span_id = new_span_id(); - push_span(trace_id.clone(), span_id.clone()); - SpanGuard { - span: Span { - trace_id, - span_id, - parent_id, - name: name.to_string(), - start_ns: now_ns(), - end_ns: 0, - status: SpanStatus::Ok, - attrs: Vec::new(), - events: Vec::new(), - service: service.to_string(), - }, - } - } - - pub fn attr(mut self, k: &str, v: AttrValue) -> Self { - self.span.attrs.push((k.to_string(), v)); - self - } - - pub fn error(mut self, msg: &str) -> Self { - self.span.status = SpanStatus::Error(msg.to_string()); - self.span.events.push(SpanEvent { - name: "exception".to_string(), - time_ns: now_ns(), - attrs: vec![("exception.message".to_string(), AttrValue::Str(msg.to_string()))], - }); - self - } - - /// Finish the span without dropping the guard (for manual control). - pub fn finish(mut self) -> Span { - pop_span(); - self.span.end_ns = now_ns(); - self.span.clone() - } - } - - impl Drop for SpanGuard { - fn drop(&mut self) { - // Only pop if not already finished manually. - // We detect this by checking if end_ns is still 0. - if self.span.end_ns == 0 { - pop_span(); - self.span.end_ns = now_ns(); - emit_span(self.span.clone()); - } - } - } -} - -// ── ID generation ───────────────────────────────────────────────────────────── - -pub fn new_trace_id() -> String { - let id = uuid::Uuid::new_v4(); - hex::encode(id.as_bytes()) -} - -pub fn new_span_id() -> String { - let id = uuid::Uuid::new_v4(); - // Span ID is 8 bytes - hex::encode(&id.as_bytes()[..8]) -} - -// ── Timing ──────────────────────────────────────────────────────────────────── - -pub fn now_ns() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_nanos() as u64) - .unwrap_or(0) -} - -pub fn now_ms() -> u64 { - now_ns() / 1_000_000 -} - -// ── Service name global ─────────────────────────────────────────────────────── - -static SERVICE_NAME: OnceLock = OnceLock::new(); - -pub fn service_name() -> &'static str { - SERVICE_NAME.get().map(|s| s.as_str()).unwrap_or("el-program") -} - -pub fn set_service_name(name: &str) { - let _ = SERVICE_NAME.set(name.to_string()); -} - -// ── High-level tracing helpers ──────────────────────────────────────────────── - -/// Instrument a function call. Returns a SpanGuard; emit it when done. -pub fn start_fn_span(fn_name: &str) -> context::SpanGuard { - context::SpanGuard::new(fn_name, service_name()) -} - -/// Emit a log at the given severity. -pub fn log(severity: LogSeverity, body: &str) { - let (trace_id, span_id) = context::current_span() - .map(|(t, s)| (Some(t), Some(s))) - .unwrap_or((None, None)); - emit_log(LogRecord { - time_ns: now_ns(), - severity, - body: body.to_string(), - attrs: Vec::new(), - service: service_name().to_string(), - trace_id, - span_id, - }); -} - -pub fn log_debug(msg: &str) { log(LogSeverity::Debug, msg); } -pub fn log_info(msg: &str) { log(LogSeverity::Info, msg); } -pub fn log_warn(msg: &str) { log(LogSeverity::Warn, msg); } -pub fn log_error(msg: &str) { log(LogSeverity::Error, msg); } - -/// Convenience: emit a metric counter. -pub fn counter(name: &str, value: f64, attrs: Vec<(String, AttrValue)>) { - emit_metric(Metric { - name: name.to_string(), - kind: MetricKind::Counter, - value, - attrs, - time_ns: now_ns(), - service: service_name().to_string(), - }); -} - -/// Convenience: emit a metric gauge. -pub fn gauge(name: &str, value: f64, attrs: Vec<(String, AttrValue)>) { - emit_metric(Metric { - name: name.to_string(), - kind: MetricKind::Gauge, - value, - attrs, - time_ns: now_ns(), - service: service_name().to_string(), - }); -} - -// ── OTLP/HTTP JSON exporter ─────────────────────────────────────────────────── -// Uses the OTLP/HTTP JSON format (not protobuf) which Alloy accepts. - -fn exporter_loop( - rx: mpsc::Receiver, - endpoint: &str, - service: &str, -) { - let client = reqwest::blocking::Client::builder() - .timeout(std::time::Duration::from_secs(10)) - .build() - .unwrap_or_else(|_| reqwest::blocking::Client::new()); - - let spans_url = format!("{}/v1/traces", endpoint); - let logs_url = format!("{}/v1/logs", endpoint); - let metrics_url = format!("{}/v1/metrics", endpoint); - - let mut spans: Vec = Vec::with_capacity(BATCH_SIZE); - let mut logs: Vec = Vec::with_capacity(BATCH_SIZE); - let mut metrics: Vec = Vec::with_capacity(BATCH_SIZE); - - let timeout = std::time::Duration::from_millis(BATCH_TIMEOUT_MS); - - loop { - // Try to receive one item with a timeout, then drain available items. - match rx.recv_timeout(timeout) { - Ok(item) => { - enqueue_item(item, &mut spans, &mut logs, &mut metrics); - // Drain any immediately available items. - while let Ok(item) = rx.try_recv() { - enqueue_item(item, &mut spans, &mut logs, &mut metrics); - if spans.len() + logs.len() + metrics.len() >= BATCH_SIZE { - break; - } - } - } - Err(mpsc::RecvTimeoutError::Timeout) => { - // Flush whatever we have. - } - Err(mpsc::RecvTimeoutError::Disconnected) => { - // Channel closed — flush and exit. - flush(&client, &spans_url, &logs_url, &metrics_url, - service, &spans, &logs, &metrics); - break; - } - } - - if !spans.is_empty() || !logs.is_empty() || !metrics.is_empty() { - flush(&client, &spans_url, &logs_url, &metrics_url, - service, &spans, &logs, &metrics); - spans.clear(); - logs.clear(); - metrics.clear(); - } - } -} - -fn enqueue_item( - item: TelemetryItem, - spans: &mut Vec, - logs: &mut Vec, - metrics: &mut Vec, -) { - match item { - TelemetryItem::Span(s) => spans.push(s), - TelemetryItem::Log(l) => logs.push(l), - TelemetryItem::Metric(m) => metrics.push(m), - } -} - -fn flush( - client: &reqwest::blocking::Client, - spans_url: &str, - logs_url: &str, - metrics_url: &str, - service: &str, - spans: &[Span], - logs: &[LogRecord], - metrics: &[Metric], -) { - if !spans.is_empty() { - let body = build_traces_json(service, spans); - post_otlp(client, spans_url, &body); - } - if !logs.is_empty() { - let body = build_logs_json(service, logs); - post_otlp(client, logs_url, &body); - } - if !metrics.is_empty() { - let body = build_metrics_json(service, metrics); - post_otlp(client, metrics_url, &body); - } -} - -fn post_otlp(client: &reqwest::blocking::Client, url: &str, body: &str) { - let res = client.post(url) - .header("Content-Type", "application/json") - .body(body.to_string()) - .send(); - match res { - Ok(r) if r.status().is_success() => {} - Ok(r) => { - if !OTLP_WARNED.swap(true, Ordering::Relaxed) { - eprintln!("[el-telemetry] OTLP export failed: HTTP {}", r.status()); - } - } - Err(e) => { - if !OTLP_WARNED.swap(true, Ordering::Relaxed) { - eprintln!("[el-telemetry] OTLP endpoint unreachable ({}), telemetry will be dropped", e); - } - } - } -} - -// ── JSON serialisation for OTLP/HTTP ───────────────────────────────────────── - -fn attr_value_json(v: &AttrValue) -> serde_json::Value { - match v { - AttrValue::Str(s) => serde_json::json!({"stringValue": s}), - AttrValue::Int(n) => serde_json::json!({"intValue": n.to_string()}), - AttrValue::Float(f) => serde_json::json!({"doubleValue": f}), - AttrValue::Bool(b) => serde_json::json!({"boolValue": b}), - } -} - -fn attrs_json(attrs: &[(String, AttrValue)]) -> serde_json::Value { - serde_json::Value::Array( - attrs.iter().map(|(k, v)| { - serde_json::json!({"key": k, "value": attr_value_json(v)}) - }).collect() - ) -} - -fn resource_json(service: &str) -> serde_json::Value { - serde_json::json!({ - "attributes": [ - {"key": "service.name", "value": {"stringValue": service}} - ] - }) -} - -fn build_traces_json(service: &str, spans: &[Span]) -> String { - // Group spans by trace_id for correct OTLP structure - let mut by_trace: HashMap<&str, Vec> = HashMap::new(); - for span in spans { - let js = span_json(span); - by_trace.entry(&span.trace_id).or_default().push(js); - } - - let scope_spans: Vec = by_trace.values().map(|sps| { - serde_json::json!({ - "scope": {"name": "el-runtime", "version": "0.1.0"}, - "spans": sps - }) - }).collect(); - - let payload = serde_json::json!({ - "resourceSpans": [{ - "resource": resource_json(service), - "scopeSpans": scope_spans - }] - }); - payload.to_string() -} - -fn span_json(span: &Span) -> serde_json::Value { - let status = match &span.status { - SpanStatus::Ok => serde_json::json!({"code": 1}), - SpanStatus::Error(msg) => serde_json::json!({"code": 2, "message": msg}), - }; - let events: Vec = span.events.iter().map(|e| { - serde_json::json!({ - "name": e.name, - "timeUnixNano": e.time_ns.to_string(), - "attributes": attrs_json(&e.attrs) - }) - }).collect(); - - let mut js = serde_json::json!({ - "traceId": span.trace_id, - "spanId": span.span_id, - "name": span.name, - "startTimeUnixNano": span.start_ns.to_string(), - "endTimeUnixNano": span.end_ns.to_string(), - "attributes": attrs_json(&span.attrs), - "events": events, - "status": status, - "kind": 1 // INTERNAL - }); - if let Some(pid) = &span.parent_id { - js["parentSpanId"] = serde_json::Value::String(pid.clone()); - } - js -} - -fn build_logs_json(service: &str, logs: &[LogRecord]) -> String { - let records: Vec = logs.iter().map(|l| { - let mut r = serde_json::json!({ - "timeUnixNano": l.time_ns.to_string(), - "severityNumber": l.severity.number(), - "severityText": l.severity.text(), - "body": {"stringValue": l.body}, - "attributes": attrs_json(&l.attrs) - }); - if let Some(tid) = &l.trace_id { - r["traceId"] = serde_json::Value::String(tid.clone()); - } - if let Some(sid) = &l.span_id { - r["spanId"] = serde_json::Value::String(sid.clone()); - } - r - }).collect(); - - let payload = serde_json::json!({ - "resourceLogs": [{ - "resource": resource_json(service), - "scopeLogs": [{ - "scope": {"name": "el-runtime", "version": "0.1.0"}, - "logRecords": records - }] - }] - }); - payload.to_string() -} - -fn build_metrics_json(service: &str, metrics: &[Metric]) -> String { - let metric_items: Vec = metrics.iter().map(|m| { - let data_point = serde_json::json!({ - "timeUnixNano": m.time_ns.to_string(), - "asDouble": m.value, - "attributes": attrs_json(&m.attrs) - }); - match m.kind { - MetricKind::Counter => serde_json::json!({ - "name": m.name, - "sum": { - "dataPoints": [data_point], - "aggregationTemporality": 2, // CUMULATIVE - "isMonotonic": true - } - }), - MetricKind::Gauge => serde_json::json!({ - "name": m.name, - "gauge": { - "dataPoints": [data_point] - } - }), - } - }).collect(); - - let payload = serde_json::json!({ - "resourceMetrics": [{ - "resource": resource_json(service), - "scopeMetrics": [{ - "scope": {"name": "el-runtime", "version": "0.1.0"}, - "metrics": metric_items - }] - }] - }); - payload.to_string() -} - -// ── parse_tags_string: "k=v,k2=v2" → Vec<(String, AttrValue)> ──────────────── - -pub fn parse_tags_string(tags: &str) -> Vec<(String, AttrValue)> { - if tags.is_empty() { - return Vec::new(); - } - tags.split(',') - .filter_map(|pair| { - let mut parts = pair.splitn(2, '='); - let k = parts.next()?.trim().to_string(); - let v = parts.next().unwrap_or("").trim().to_string(); - if k.is_empty() { - None - } else { - Some((k, AttrValue::Str(v))) - } - }) - .collect() -} diff --git a/_archive/rust-bootstrap/bin/elvm/Cargo.toml b/_archive/rust-bootstrap/bin/elvm/Cargo.toml deleted file mode 100644 index 4a705f8..0000000 --- a/_archive/rust-bootstrap/bin/elvm/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "elvm" -description = "El Virtual Machine — executes compiled El bytecode (.elc) natively" -version.workspace = true -edition.workspace = true -license.workspace = true - -[[bin]] -name = "elvm" -path = "src/main.rs" - -[dependencies] -el-compiler = { workspace = true } -el-vm = { workspace = true } -clap = { workspace = true } - -# Native window / WebView (macOS: WKWebView via wry) -wry = { version = "0.47", default-features = false } -winit = { version = "0.29", default-features = false, features = ["rwh_05", "rwh_06"] } -dpi = "0.1" diff --git a/_archive/rust-bootstrap/bin/elvm/src/main.rs b/_archive/rust-bootstrap/bin/elvm/src/main.rs deleted file mode 100644 index ade007a..0000000 --- a/_archive/rust-bootstrap/bin/elvm/src/main.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! elvm — El Virtual Machine -//! -//! The standalone El VM binary. Loads and executes compiled El bytecode (.elc) -//! files produced by `el compile` or `el build-file`. -//! -//! # Usage -//! -//! elvm [args...] -//! elvm --version -//! elvm --help -//! -//! If the environment variable `NEURON_WINDOW_URL` is set, elvm opens a native -//! macOS window (WKWebView via wry) at that URL instead of executing bytecode. -//! This allows UI apps to be launched as proper desktop windows: -//! -//! NEURON_WINDOW_URL="http://localhost:7749" elvm dist/neuron-ui.elc - -use clap::Parser; -use std::path::PathBuf; - -#[derive(Parser, Debug)] -#[command( - name = "elvm", - about = "El Virtual Machine — execute compiled El bytecode (.elc)", - long_about = "The El VM is the native El execution substrate.\n\ - Run .elc files produced by `el compile` or `el build-file`.\n\n\ - Set NEURON_WINDOW_URL= to open a native WebView window instead.", - version -)] -struct Cli { - /// Compiled El bytecode file to execute (*.elc). - file: PathBuf, - - /// Arguments forwarded to the program (accessible via `args()`). - #[arg(trailing_var_arg = true)] - args: Vec, -} - -fn main() { - let cli = Cli::parse(); - - // If NEURON_WINDOW_URL is set, open a native WebView window at that URL. - if let Ok(url) = std::env::var("NEURON_WINDOW_URL") { - if let Err(e) = open_window(&url) { - eprintln!("elvm: window error: {e}"); - std::process::exit(1); - } - return; - } - - if let Err(e) = run(cli) { - eprintln!("elvm: error: {e}"); - std::process::exit(1); - } -} - -fn run(cli: Cli) -> Result<(), Box> { - let bytes = std::fs::read(&cli.file) - .map_err(|e| format!("cannot read {}: {e}", cli.file.display()))?; - - let instructions = el_compiler::Bytecode::deserialize_all(&bytes) - .map_err(|e| format!("cannot load bytecode from {}: {e}", cli.file.display()))?; - - // Detect format and print diagnostic. - let is_elvm_container = bytes.starts_with(el_compiler::ELVM_MAGIC); - if is_elvm_container { - // Normal path — ELVM container. - } else { - eprintln!("elvm: warning: {} does not have an ELVM header — treating as legacy JSON bytecode", cli.file.display()); - } - - let mut vm = el_vm::ElVm::new(); - vm.run(&instructions, &cli.args); - - Ok(()) -} - -/// Opens a native WebView window at `url`. -/// -/// On macOS: uses wry (WKWebView) + winit for a proper native desktop window. -/// On other platforms: prints the URL (fallback). -fn open_window(url: &str) -> Result<(), Box> { - #[cfg(target_os = "macos")] - return open_native_window(url); - - #[cfg(not(target_os = "macos"))] - { - eprintln!("elvm: native window not supported on this platform"); - println!("elvm: open {url} in your browser"); - Ok(()) - } -} - -#[cfg(target_os = "macos")] -fn open_native_window(url: &str) -> Result<(), Box> { - use dpi::LogicalSize; - use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, - }; - use wry::{Rect, WebViewBuilder}; - - let event_loop = EventLoop::new().map_err(|e| format!("event loop: {e}"))?; - - let window = WindowBuilder::new() - .with_title("Neuron") - .with_inner_size(winit::dpi::LogicalSize::new(1600u32, 1000u32)) - .with_min_inner_size(winit::dpi::LogicalSize::new(900u32, 600u32)) - .with_resizable(true) - .build(&event_loop) - .map_err(|e| format!("window: {e}"))?; - - let url_owned = url.to_string(); - let webview = WebViewBuilder::new() - .with_url(&url_owned) - .build_as_child(&window) - .map_err(|e| format!("webview: {e}"))?; - - event_loop - .run(move |event, evl| { - evl.set_control_flow(ControlFlow::Wait); - - match event { - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - let scale = window.scale_factor(); - let logical = size.to_logical::(scale); - let _ = webview.set_bounds(Rect { - position: dpi::LogicalPosition::new(0, 0).into(), - size: LogicalSize::new(logical.width, logical.height).into(), - }); - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => evl.exit(), - _ => {} - } - }) - .map_err(|e| format!("event loop run: {e}"))?; - - Ok(()) -} diff --git a/_archive/rust-bootstrap/build-targets.sh b/_archive/rust-bootstrap/build-targets.sh deleted file mode 100755 index cc6feaa..0000000 --- a/_archive/rust-bootstrap/build-targets.sh +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env bash -# build-targets.sh — Build the El VM (elvm) for all supported platforms. -# -# Usage: -# ./build-targets.sh # build all targets -# ./build-targets.sh --list # list targets and prerequisites -# ./build-targets.sh x86_64-apple-darwin # build a specific target -# -# Prerequisites: -# - Rust toolchain (rustup) with cross-compilation targets installed -# - For Linux targets: cross-linkers (see comments below) -# -# Install a target: -# rustup target add -# -# For cross-compilation from macOS to Linux: -# brew install FiloSottile/musl-cross/musl-cross -# # or use `cross` (https://github.com/cross-rs/cross): -# cargo install cross --git https://github.com/cross-rs/cross - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -OUTPUT_DIR="${SCRIPT_DIR}/dist/elvm" - -# ── Target definitions ──────────────────────────────────────────────────────── -# Format: "target:description:linker_hint" -declare -a TARGETS=( - "x86_64-unknown-linux-gnu:Linux x86_64 (glibc):brew install FiloSottile/musl-cross/musl-cross or use 'cross'" - "aarch64-unknown-linux-gnu:Linux ARM64 (glibc):brew install FiloSottile/musl-cross/musl-cross or use 'cross'" - "x86_64-unknown-linux-musl:Linux x86_64 (musl, static):brew install FiloSottile/musl-cross/musl-cross" - "aarch64-unknown-linux-musl:Linux ARM64 (musl, static):brew install aarch64-unknown-linux-musl cross-linker" - "x86_64-apple-darwin:macOS Intel:native (no cross tooling needed on macOS)" - "aarch64-apple-darwin:macOS Apple Silicon:native (no cross tooling needed on macOS)" - "x86_64-pc-windows-gnu:Windows x86_64:brew install mingw-w64" - "aarch64-pc-windows-msvc:Windows ARM64:requires Windows MSVC SDK (Windows only)" -) - -# ── Helpers ─────────────────────────────────────────────────────────────────── - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -ok() { echo -e " ${GREEN}✓${NC} $*"; } -warn() { echo -e " ${YELLOW}⚠${NC} $*"; } -fail() { echo -e " ${RED}✗${NC} $*"; } - -# ── List mode ───────────────────────────────────────────────────────────────── - -if [[ "${1:-}" == "--list" ]]; then - echo "" - echo "El VM cross-compilation targets" - echo "================================" - echo "" - for entry in "${TARGETS[@]}"; do - IFS=':' read -r target desc hint <<< "$entry" - printf " %-42s %s\n" "$target" "$desc" - printf " %-42s Prereq: %s\n" "" "$hint" - echo "" - done - echo "Install a target: rustup target add " - echo "List installed: rustup target list --installed" - exit 0 -fi - -# ── Filter targets ──────────────────────────────────────────────────────────── - -if [[ $# -gt 0 && "${1:-}" != "--list" ]]; then - # Build only the specified target(s). - SELECTED=("$@") -else - # Build all targets. - SELECTED=() - for entry in "${TARGETS[@]}"; do - IFS=':' read -r target _ _ <<< "$entry" - SELECTED+=("$target") - done -fi - -# ── Build ───────────────────────────────────────────────────────────────────── - -echo "" -echo -e "${BLUE}El VM — cross-platform build${NC}" -echo "Output directory: $OUTPUT_DIR" -echo "" - -mkdir -p "$OUTPUT_DIR" - -BUILT=0 -SKIPPED=0 -FAILED=0 - -for target in "${SELECTED[@]}"; do - # Find description for this target. - desc="$target" - for entry in "${TARGETS[@]}"; do - IFS=':' read -r t d _ <<< "$entry" - if [[ "$t" == "$target" ]]; then desc="$d"; break; fi - done - - echo -e "${BLUE}→ Building $target${NC} ($desc)" - - # Check if the Rust target is installed. - if ! rustup target list --installed 2>/dev/null | grep -q "^$target\b"; then - warn "Target $target not installed — installing..." - if rustup target add "$target" 2>/dev/null; then - ok "Installed $target" - else - fail "Cannot install $target — skipping" - ((SKIPPED++)) - continue - fi - fi - - # Attempt to build. - binary_suffix="" - if [[ "$target" == *"windows"* ]]; then - binary_suffix=".exe" - fi - - binary_name="elvm-${target}${binary_suffix}" - output_path="$OUTPUT_DIR/$binary_name" - - if (cd "$SCRIPT_DIR" && cargo build --release --target "$target" -p elvm 2>&1 | tail -5); then - src="$SCRIPT_DIR/target/$target/release/elvm${binary_suffix}" - if [[ -f "$src" ]]; then - cp "$src" "$output_path" - size=$(du -h "$output_path" | cut -f1) - ok "Built $binary_name ($size)" - ((BUILT++)) - else - fail "Binary not found at $src" - ((FAILED++)) - fi - else - fail "Build failed for $target (cross-linker may be missing — see --list)" - ((FAILED++)) - fi - echo "" -done - -# ── Summary ─────────────────────────────────────────────────────────────────── - -echo "────────────────────────────────" -echo "Built: $BUILT" -echo "Skipped: $SKIPPED" -echo "Failed: $FAILED" - -if [[ $BUILT -gt 0 ]]; then - echo "" - echo "Artifacts:" - ls -lh "$OUTPUT_DIR/" -fi - -echo "" -if [[ $FAILED -gt 0 ]]; then - echo -e "${YELLOW}Some targets failed. Run './build-targets.sh --list' for prerequisites.${NC}" - exit 1 -fi diff --git a/_archive/rust-bootstrap/engrams/el-arch/Cargo.toml b/_archive/rust-bootstrap/engrams/el-arch/Cargo.toml deleted file mode 100644 index 49f0396..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "el-arch" -version = "0.1.0" -edition = "2021" - -[dependencies] -el-parser = { path = "../el-parser" } -el-lexer = { path = "../el-lexer" } - -[dev-dependencies] -el-lexer = { path = "../el-lexer" } -el-parser = { path = "../el-parser" } diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/checker.rs b/_archive/rust-bootstrap/engrams/el-arch/src/checker.rs deleted file mode 100644 index 80a08d0..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/checker.rs +++ /dev/null @@ -1,487 +0,0 @@ -//! Main architectural checker — walks the AST and applies all rules. - -use std::collections::HashMap; - -use el_parser::{Expr, Program, Stmt, TypeExpr}; - -use crate::error::{ArchDiagnostic, Severity}; -use crate::rule::{ArchRule, CallInfo, FnContext}; -use crate::rules::{ - graph::{DuplicateActivateType, N1Detection}, - security::{AuthnWithoutAuthz, PublicFnWithActivate, SealedInLoop}, - swarm::{SwarmAgentIsolation, SwarmAgentNoSharedState, SwarmAgentNoSpawn}, - vbd::{AccessorMustNotCallManager, ExperienceMustNotCallExperience, ExperienceShouldReturnResult}, -}; - -/// The main architectural checker. Instantiate once, call `check` per program. -pub struct ArchChecker { - rules: Vec>, -} - -impl ArchChecker { - /// Create an `ArchChecker` with all built-in rules registered. - pub fn new() -> Self { - Self { - rules: vec![ - Box::new(AccessorMustNotCallManager), - Box::new(ExperienceMustNotCallExperience), - Box::new(ExperienceShouldReturnResult), - Box::new(PublicFnWithActivate), - Box::new(SealedInLoop), - Box::new(AuthnWithoutAuthz), - Box::new(N1Detection), - Box::new(DuplicateActivateType), - Box::new(SwarmAgentIsolation), - Box::new(SwarmAgentNoSpawn), - Box::new(SwarmAgentNoSharedState), - ], - } - } - - /// Returns a reference to the registered rules (useful for introspection in tests). - pub fn rules(&self) -> &[Box] { - &self.rules - } - - /// Run all rules against a parsed program and collect all diagnostics. - pub fn check(&self, program: &Program) -> Vec { - // Step 1: build global fn_name → decorator names map (including impl methods). - let all_fn_annotations = collect_fn_annotations(&program.stmts); - - // Step 2: gather all top-level + impl FnDef statements. - let fn_defs = collect_fn_defs(&program.stmts); - - // Step 3: for each function, build FnContext and run every rule. - let mut diagnostics = Vec::new(); - for (fn_name, decorators, return_type, body) in &fn_defs { - let annotations: Vec = - decorators.iter().map(|d| d.name.clone()).collect(); - let body_calls = extract_calls(body, false); - let activate_types = extract_activate_types(body, false); - let has_sealed_in_loop = has_sealed_in_loop_body(body, false); - let return_type_name = type_expr_name(return_type); - - let ctx = FnContext { - fn_name: fn_name.as_str(), - annotations: &annotations, - body_calls: &body_calls, - all_fn_annotations: &all_fn_annotations, - activate_types: &activate_types, - has_sealed_in_loop, - return_type_name: &return_type_name, - }; - - for rule in &self.rules { - diagnostics.extend(rule.check(&ctx)); - } - } - - diagnostics - } - - /// Returns true if any of the given diagnostics are errors. - pub fn has_errors(diagnostics: &[ArchDiagnostic]) -> bool { - diagnostics.iter().any(|d| d.severity == Severity::Error) - } -} - -impl Default for ArchChecker { - fn default() -> Self { - Self::new() - } -} - -// ── AST traversal helpers ───────────────────────────────────────────────────── - -/// A collected function definition: (name, decorators, return_type, body). -type FnDef<'a> = ( - &'a String, - &'a Vec, - &'a TypeExpr, - &'a Vec, -); - -/// Collect all FnDef statements from top-level and impl blocks. -fn collect_fn_defs<'a>(stmts: &'a [Stmt]) -> Vec> { - let mut out = Vec::new(); - for stmt in stmts { - match stmt { - Stmt::FnDef { name, decorators, return_type, body, .. } => { - out.push((name, decorators, return_type, body)); - } - Stmt::ImplDef { methods, .. } => { - for m in methods { - if let Stmt::FnDef { name, decorators, return_type, body, .. } = m { - out.push((name, decorators, return_type, body)); - } - } - } - _ => {} - } - } - out -} - -/// Build a map from function name → list of decorator names, for all functions in the program. -fn collect_fn_annotations(stmts: &[Stmt]) -> HashMap> { - let mut map = HashMap::new(); - for stmt in stmts { - match stmt { - Stmt::FnDef { name, decorators, .. } => { - let anns: Vec = decorators.iter().map(|d| d.name.clone()).collect(); - map.insert(name.clone(), anns); - } - Stmt::ImplDef { methods, .. } => { - for m in methods { - if let Stmt::FnDef { name, decorators, .. } = m { - let anns: Vec = decorators.iter().map(|d| d.name.clone()).collect(); - map.insert(name.clone(), anns); - } - } - } - _ => {} - } - } - map -} - -/// Extract all function calls from a statement list. -/// `in_loop` tracks whether we are currently inside a for/while loop body. -fn extract_calls(stmts: &[Stmt], in_loop: bool) -> Vec { - let mut calls = Vec::new(); - for stmt in stmts { - extract_calls_from_stmt(stmt, in_loop, &mut calls); - } - calls -} - -fn extract_calls_from_stmt(stmt: &Stmt, in_loop: bool, out: &mut Vec) { - match stmt { - Stmt::Let { value, .. } => extract_calls_from_expr(value, in_loop, out), - Stmt::Return(expr, _) | Stmt::Expr(expr, _) | Stmt::Assert(expr, _) => { - extract_calls_from_expr(expr, in_loop, out); - } - Stmt::FnDef { body, .. } => { - // Nested function defs: walk but don't count as callee of the outer fn. - for s in body { - extract_calls_from_stmt(s, false, out); - } - } - _ => {} - } -} - -fn extract_calls_from_expr(expr: &Expr, in_loop: bool, out: &mut Vec) { - match expr { - Expr::Call { func, args } => { - // Extract callee name - let callee = expr_as_call_name(func); - if let Some(name) = callee { - out.push(CallInfo { callee: name, is_in_loop: in_loop }); - } - // Recurse into func expression and args - extract_calls_from_expr(func, in_loop, out); - for a in args { - extract_calls_from_expr(a, in_loop, out); - } - } - Expr::Activate { type_name, .. } => { - // Encode activate as a synthetic call so GRAPH-001 can detect in-loop activates. - out.push(CallInfo { - callee: format!("__activate__{type_name}"), - is_in_loop: in_loop, - }); - } - Expr::BinOp { left, right, .. } => { - extract_calls_from_expr(left, in_loop, out); - extract_calls_from_expr(right, in_loop, out); - } - Expr::UnaryNot(inner) | Expr::Try(inner) => { - extract_calls_from_expr(inner, in_loop, out); - } - Expr::Block(stmts) => { - for s in stmts { - extract_calls_from_stmt(s, in_loop, out); - } - } - Expr::Sealed(stmts) => { - // Sealed blocks are scanned but tracked separately for sealed-in-loop. - for s in stmts { - extract_calls_from_stmt(s, in_loop, out); - } - } - Expr::If { cond, then, else_ } => { - extract_calls_from_expr(cond, in_loop, out); - extract_calls_from_expr(then, in_loop, out); - if let Some(e) = else_ { - extract_calls_from_expr(e, in_loop, out); - } - } - Expr::Match { subject, arms } => { - extract_calls_from_expr(subject, in_loop, out); - for arm in arms { - extract_calls_from_expr(&arm.body, in_loop, out); - } - } - Expr::Field { object, .. } => extract_calls_from_expr(object, in_loop, out), - Expr::Array(elems) => { - for e in elems { - extract_calls_from_expr(e, in_loop, out); - } - } - Expr::Index { object, index } => { - extract_calls_from_expr(object, in_loop, out); - extract_calls_from_expr(index, in_loop, out); - } - Expr::Closure { body, .. } => { - extract_calls_from_expr(body, in_loop, out); - } - Expr::MapLiteral(pairs) => { - for (k, v) in pairs { - extract_calls_from_expr(k, in_loop, out); - extract_calls_from_expr(v, in_loop, out); - } - } - Expr::Literal(_) | Expr::Ident(_) | Expr::Path { .. } => {} - Expr::StructLit { fields, .. } => { - for (_, e) in fields { - extract_calls_from_expr(e, in_loop, out); - } - } - Expr::With { base, updates } => { - extract_calls_from_expr(base, in_loop, out); - for (_, e) in updates { - extract_calls_from_expr(e, in_loop, out); - } - } - Expr::Reason { .. } => {} - Expr::Parallel { entries } => { - for (_, e) in entries { - extract_calls_from_expr(e, in_loop, out); - } - } - Expr::Trace { body, .. } => { - for s in body { - extract_calls_from_stmt(s, in_loop, out); - } - } - Expr::UnaryBitNot(inner) => extract_calls_from_expr(inner, in_loop, out), - // JSX expressions — traverse children. - Expr::JsxElement { children, .. } => { - for child in children { extract_calls_from_expr(child, in_loop, out); } - } - Expr::JsxExpr(inner) => extract_calls_from_expr(inner, in_loop, out), - Expr::JsxText(_) => {} - } -} - -/// Try to extract a simple callee name from a Call's `func` expression. -fn expr_as_call_name(expr: &Expr) -> Option { - match expr { - Expr::Ident(name) => Some(name.clone()), - Expr::Field { field, .. } => Some(field.clone()), - Expr::Path { segments } => segments.last().cloned(), - _ => None, - } -} - -/// Collect all `activate TypeName` type names from a statement list. -/// `in_loop` indicates whether we're currently inside a loop. -fn extract_activate_types(stmts: &[Stmt], in_loop: bool) -> Vec { - let mut types = Vec::new(); - for stmt in stmts { - extract_activate_types_stmt(stmt, in_loop, &mut types); - } - types -} - -fn extract_activate_types_stmt(stmt: &Stmt, in_loop: bool, out: &mut Vec) { - match stmt { - Stmt::Let { value, .. } => extract_activate_types_expr(value, in_loop, out), - Stmt::Return(expr, _) | Stmt::Expr(expr, _) | Stmt::Assert(expr, _) => { - extract_activate_types_expr(expr, in_loop, out); - } - Stmt::FnDef { body, .. } => { - for s in body { - extract_activate_types_stmt(s, false, out); - } - } - _ => {} - } -} - -fn extract_activate_types_expr(expr: &Expr, in_loop: bool, out: &mut Vec) { - match expr { - Expr::Activate { type_name, .. } => { - out.push(type_name.clone()); - } - Expr::Call { func, args } => { - extract_activate_types_expr(func, in_loop, out); - for a in args { - extract_activate_types_expr(a, in_loop, out); - } - } - Expr::BinOp { left, right, .. } => { - extract_activate_types_expr(left, in_loop, out); - extract_activate_types_expr(right, in_loop, out); - } - Expr::UnaryNot(inner) | Expr::Try(inner) => { - extract_activate_types_expr(inner, in_loop, out); - } - Expr::Block(stmts) => { - for s in stmts { - extract_activate_types_stmt(s, in_loop, out); - } - } - Expr::Sealed(stmts) => { - for s in stmts { - extract_activate_types_stmt(s, in_loop, out); - } - } - Expr::If { cond, then, else_ } => { - extract_activate_types_expr(cond, in_loop, out); - extract_activate_types_expr(then, in_loop, out); - if let Some(e) = else_ { - extract_activate_types_expr(e, in_loop, out); - } - } - Expr::Match { subject, arms } => { - extract_activate_types_expr(subject, in_loop, out); - for arm in arms { - extract_activate_types_expr(&arm.body, in_loop, out); - } - } - Expr::Field { object, .. } => extract_activate_types_expr(object, in_loop, out), - Expr::Array(elems) => { - for e in elems { - extract_activate_types_expr(e, in_loop, out); - } - } - Expr::Index { object, index } => { - extract_activate_types_expr(object, in_loop, out); - extract_activate_types_expr(index, in_loop, out); - } - Expr::Closure { body, .. } => { - extract_activate_types_expr(body, in_loop, out); - } - Expr::MapLiteral(pairs) => { - for (k, v) in pairs { - extract_activate_types_expr(k, in_loop, out); - extract_activate_types_expr(v, in_loop, out); - } - } - Expr::Literal(_) | Expr::Ident(_) | Expr::Path { .. } => {} - Expr::StructLit { fields, .. } => { - for (_, e) in fields { - extract_activate_types_expr(e, in_loop, out); - } - } - Expr::With { base, updates } => { - extract_activate_types_expr(base, in_loop, out); - for (_, e) in updates { - extract_activate_types_expr(e, in_loop, out); - } - } - Expr::Reason { .. } => {} - Expr::Parallel { entries } => { - for (_, e) in entries { - extract_activate_types_expr(e, in_loop, out); - } - } - Expr::Trace { body, .. } => { - for s in body { - extract_activate_types_stmt(s, in_loop, out); - } - } - Expr::UnaryBitNot(inner) => extract_activate_types_expr(inner, in_loop, out), - // JSX expressions — traverse children. - Expr::JsxElement { children, .. } => { - for child in children { extract_activate_types_expr(child, in_loop, out); } - } - Expr::JsxExpr(inner) => extract_activate_types_expr(inner, in_loop, out), - Expr::JsxText(_) => {} - } -} - -/// Returns true if any `sealed { }` block appears inside a loop in the given body. -fn has_sealed_in_loop_body(stmts: &[Stmt], in_loop: bool) -> bool { - stmts.iter().any(|s| has_sealed_in_loop_stmt(s, in_loop)) -} - -fn has_sealed_in_loop_stmt(stmt: &Stmt, in_loop: bool) -> bool { - match stmt { - Stmt::Let { value, .. } => has_sealed_in_loop_expr(value, in_loop), - Stmt::Return(expr, _) | Stmt::Expr(expr, _) | Stmt::Assert(expr, _) => { - has_sealed_in_loop_expr(expr, in_loop) - } - Stmt::FnDef { body, .. } => { - // Inner function defs reset loop context. - body.iter().any(|s| has_sealed_in_loop_stmt(s, false)) - } - _ => false, - } -} - -fn has_sealed_in_loop_expr(expr: &Expr, in_loop: bool) -> bool { - match expr { - Expr::Sealed(_) => in_loop, - Expr::Call { func, args } => { - has_sealed_in_loop_expr(func, in_loop) - || args.iter().any(|a| has_sealed_in_loop_expr(a, in_loop)) - } - Expr::BinOp { left, right, .. } => { - has_sealed_in_loop_expr(left, in_loop) || has_sealed_in_loop_expr(right, in_loop) - } - Expr::UnaryNot(inner) | Expr::Try(inner) => has_sealed_in_loop_expr(inner, in_loop), - Expr::Block(stmts) => stmts.iter().any(|s| has_sealed_in_loop_stmt(s, in_loop)), - Expr::If { cond, then, else_ } => { - has_sealed_in_loop_expr(cond, in_loop) - || has_sealed_in_loop_expr(then, in_loop) - || else_.as_deref().is_some_and(|e| has_sealed_in_loop_expr(e, in_loop)) - } - Expr::Match { subject, arms } => { - has_sealed_in_loop_expr(subject, in_loop) - || arms.iter().any(|a| has_sealed_in_loop_expr(&a.body, in_loop)) - } - Expr::Field { object, .. } => has_sealed_in_loop_expr(object, in_loop), - Expr::Array(elems) => elems.iter().any(|e| has_sealed_in_loop_expr(e, in_loop)), - Expr::Index { object, index } => { - has_sealed_in_loop_expr(object, in_loop) || has_sealed_in_loop_expr(index, in_loop) - } - Expr::Closure { body, .. } => has_sealed_in_loop_expr(body, in_loop), - Expr::MapLiteral(pairs) => pairs - .iter() - .any(|(k, v)| has_sealed_in_loop_expr(k, in_loop) || has_sealed_in_loop_expr(v, in_loop)), - Expr::Activate { .. } | Expr::Literal(_) | Expr::Ident(_) | Expr::Path { .. } => false, - Expr::StructLit { fields, .. } => fields - .iter() - .any(|(_, e)| has_sealed_in_loop_expr(e, in_loop)), - Expr::With { base, updates } => { - has_sealed_in_loop_expr(base, in_loop) - || updates.iter().any(|(_, e)| has_sealed_in_loop_expr(e, in_loop)) - } - Expr::Reason { .. } => false, - Expr::Parallel { entries } => entries.iter().any(|(_, e)| has_sealed_in_loop_expr(e, in_loop)), - Expr::Trace { body, .. } => body.iter().any(|s| has_sealed_in_loop_stmt(s, in_loop)), - Expr::UnaryBitNot(inner) => has_sealed_in_loop_expr(inner, in_loop), - // JSX expressions. - Expr::JsxElement { children, .. } => children.iter().any(|c| has_sealed_in_loop_expr(c, in_loop)), - Expr::JsxExpr(inner) => has_sealed_in_loop_expr(inner, in_loop), - Expr::JsxText(_) => false, - } -} - -/// Convert a `TypeExpr` to a display string for the return-type name check. -fn type_expr_name(te: &TypeExpr) -> String { - match te { - TypeExpr::Named(n) => n.clone(), - TypeExpr::Result { .. } => "Result".to_string(), - TypeExpr::Array(inner) => format!("[{}]", type_expr_name(inner)), - TypeExpr::Optional(inner) => format!("{}?", type_expr_name(inner)), - TypeExpr::Map { key, value } => { - format!("Map<{}, {}>", type_expr_name(key), type_expr_name(value)) - } - TypeExpr::Fn { .. } => "fn".to_string(), - TypeExpr::TypeParam(n) => n.clone(), - } -} diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/error.rs b/_archive/rust-bootstrap/engrams/el-arch/src/error.rs deleted file mode 100644 index cfce687..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Diagnostic types for the architectural checker. - -/// Severity level of an architectural diagnostic. -#[derive(Debug, Clone, PartialEq)] -pub enum Severity { - Error, - Warning, -} - -/// A single architectural diagnostic (error or warning). -#[derive(Debug, Clone)] -pub struct ArchDiagnostic { - pub severity: Severity, - /// Rule identifier, e.g. "VBD-001". - pub rule: String, - pub message: String, - /// Function name or other location hint. - pub location: Option, -} - -/// Type alias — an ArchError is an ArchDiagnostic with Severity::Error. -pub type ArchError = ArchDiagnostic; - -/// Type alias — an ArchWarning is an ArchDiagnostic with Severity::Warning. -pub type ArchWarning = ArchDiagnostic; - -impl ArchDiagnostic { - pub fn error(rule: impl Into, message: impl Into, location: Option) -> Self { - Self { - severity: Severity::Error, - rule: rule.into(), - message: message.into(), - location, - } - } - - pub fn warning(rule: impl Into, message: impl Into, location: Option) -> Self { - Self { - severity: Severity::Warning, - rule: rule.into(), - message: message.into(), - location, - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/lib.rs b/_archive/rust-bootstrap/engrams/el-arch/src/lib.rs deleted file mode 100644 index 1830c85..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! el-arch — Architectural rule checker for the Engram language. -//! -//! Runs after type-checking and enforces: -//! - VBD (Volatility-Based Decomposition) layer rules -//! - EBD (Experience-Based Decomposition) experience rules -//! - Swarm containment rules -//! - Security rules -//! - Graph access patterns (N+1, duplicate activate) - -pub mod checker; -pub mod error; -pub mod rule; -pub mod rules; - -pub use checker::ArchChecker; -pub use error::{ArchDiagnostic, ArchError, ArchWarning, Severity}; - -#[cfg(test)] -mod tests; diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/rule.rs b/_archive/rust-bootstrap/engrams/el-arch/src/rule.rs deleted file mode 100644 index fae54a6..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/rule.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Core trait and context types for architectural rules. - -use std::collections::HashMap; -use crate::error::ArchDiagnostic; - -/// Information about a single function call within a function body. -#[derive(Debug, Clone)] -pub struct CallInfo { - /// The name of the function being called. - pub callee: String, - /// True if this call appears inside a loop body (for or while). - pub is_in_loop: bool, -} - -/// Full context about a single function being checked by arch rules. -pub struct FnContext<'a> { - /// Name of the function under analysis. - pub fn_name: &'a str, - /// Decorator names applied directly to this function (e.g. "accessor", "public"). - pub annotations: &'a [String], - /// All calls made from within this function body. - pub body_calls: &'a [CallInfo], - /// Global map of function name → decorator names for the whole program. - pub all_fn_annotations: &'a HashMap>, - /// TypeNames that appear in `activate TypeName where ...` calls in this function. - pub activate_types: &'a [String], - /// Whether this function contains a `sealed { }` block inside a loop. - pub has_sealed_in_loop: bool, - /// The return type of the function as a string (e.g. "Result", "Void", "String"). - pub return_type_name: &'a str, -} - -impl<'a> FnContext<'a> { - /// Returns true if this function has the given annotation/decorator. - pub fn has_annotation(&self, name: &str) -> bool { - self.annotations.iter().any(|a| a == name) - } - - /// Returns true if the named callee has the given annotation in the program. - pub fn callee_has_annotation(&self, callee: &str, ann: &str) -> bool { - self.all_fn_annotations - .get(callee) - .map(|anns| anns.iter().any(|a| a == ann)) - .unwrap_or(false) - } -} - -/// An architectural rule that can be checked against a function context. -pub trait ArchRule: Send + Sync { - /// Short unique identifier, e.g. "VBD-001". - fn name(&self) -> &str; - /// Human-readable description of what this rule enforces. - fn description(&self) -> &str; - /// Run the rule against a function context, returning any diagnostics. - fn check(&self, ctx: &FnContext<'_>) -> Vec; -} diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/rules/graph.rs b/_archive/rust-bootstrap/engrams/el-arch/src/rules/graph.rs deleted file mode 100644 index 21eb75a..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/rules/graph.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Graph access pattern rules (N+1 detection, duplicate activate). - -use crate::error::ArchDiagnostic; -use crate::rule::{ArchRule, FnContext}; - -// ── GRAPH-001: N+1 detection ────────────────────────────────────────────────── - -/// GRAPH-001: An activate call inside a loop is an N+1 graph access pattern. -/// Each loop iteration performs a separate graph traversal; consolidate into one query. -pub struct N1Detection; - -impl ArchRule for N1Detection { - fn name(&self) -> &str { "GRAPH-001" } - - fn description(&self) -> &str { - "activate inside a loop creates an N+1 graph access pattern — hoist outside the loop" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - // We detect activate-in-loop via the body_calls with is_in_loop = true - // AND via the activate_types combined with loop context tracked by the checker. - // The checker sets a separate field for this. - let has_activate_in_loop = ctx.body_calls - .iter() - .any(|c| c.is_in_loop && c.callee.starts_with("__activate__")); - - if has_activate_in_loop { - vec![ArchDiagnostic::warning( - self.name(), - format!( - "function '{}' performs activate inside a loop — N+1 graph access pattern", - ctx.fn_name - ), - Some(ctx.fn_name.to_string()), - )] - } else { - vec![] - } - } -} - -// ── GRAPH-002: Duplicate activate on same type ──────────────────────────────── - -/// GRAPH-002: Multiple activate calls on the same type within one function -/// should be consolidated into a single query for efficiency. -pub struct DuplicateActivateType; - -impl ArchRule for DuplicateActivateType { - fn name(&self) -> &str { "GRAPH-002" } - - fn description(&self) -> &str { - "multiple activate calls on the same type in one function — consolidate into one query" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - let mut seen = std::collections::HashMap::new(); - for type_name in ctx.activate_types { - *seen.entry(type_name.as_str()).or_insert(0u32) += 1; - } - - seen.iter() - .filter(|(_, &count)| count > 1) - .map(|(type_name, count)| ArchDiagnostic::warning( - self.name(), - format!( - "function '{}' activates type '{}' {} times — consolidate into a single query", - ctx.fn_name, type_name, count - ), - Some(ctx.fn_name.to_string()), - )) - .collect() - } -} diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/rules/mod.rs b/_archive/rust-bootstrap/engrams/el-arch/src/rules/mod.rs deleted file mode 100644 index 0a5c036..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/rules/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Individual architectural rule implementations. - -pub mod graph; -pub mod security; -pub mod swarm; -pub mod vbd; diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/rules/security.rs b/_archive/rust-bootstrap/engrams/el-arch/src/rules/security.rs deleted file mode 100644 index e587631..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/rules/security.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Security architectural rules. - -use crate::error::ArchDiagnostic; -use crate::rule::{ArchRule, FnContext}; - -// ── SEC-001: Public function with activate inside ───────────────────────────── - -/// SEC-001: A @public function must not contain activate expressions. -/// Unauthenticated callers could trigger graph reads, potentially leaking data. -pub struct PublicFnWithActivate; - -impl ArchRule for PublicFnWithActivate { - fn name(&self) -> &str { "SEC-001" } - - fn description(&self) -> &str { - "@public functions must not contain activate — unauthenticated callers could trigger data reads" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("public") { - return vec![]; - } - if !ctx.activate_types.is_empty() { - return vec![ArchDiagnostic::error( - self.name(), - format!( - "@public function '{}' contains activate — data leak risk for unauthenticated callers (types: {})", - ctx.fn_name, - ctx.activate_types.join(", ") - ), - Some(ctx.fn_name.to_string()), - )]; - } - vec![] - } -} - -// ── SEC-002: sealed block inside a loop ────────────────────────────────────── - -/// SEC-002: A sealed block inside a loop incurs encryption overhead per iteration. -pub struct SealedInLoop; - -impl ArchRule for SealedInLoop { - fn name(&self) -> &str { "SEC-002" } - - fn description(&self) -> &str { - "sealed blocks inside loops cause encryption overhead on every iteration" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if ctx.has_sealed_in_loop { - return vec![ArchDiagnostic::warning( - self.name(), - format!( - "function '{}' contains a sealed block inside a loop — encryption overhead in hot path", - ctx.fn_name - ), - Some(ctx.fn_name.to_string()), - )]; - } - vec![] - } -} - -// ── SEC-003: @authenticate without @authorize on mutations ──────────────────── - -/// SEC-003: Functions whose name suggests mutation (create_*, update_*, delete_*) -/// and carry @authenticate should also carry @authorize, otherwise authn without authz. -pub struct AuthnWithoutAuthz; - -impl ArchRule for AuthnWithoutAuthz { - fn name(&self) -> &str { "SEC-003" } - - fn description(&self) -> &str { - "@authenticate without @authorize on mutation functions — authentication without authorization" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("authenticate") { - return vec![]; - } - if ctx.has_annotation("authorize") { - return vec![]; - } - // Heuristic: mutation function names - let is_mutation = ctx.fn_name.starts_with("create_") - || ctx.fn_name.starts_with("update_") - || ctx.fn_name.starts_with("delete_") - || ctx.fn_name.starts_with("write_") - || ctx.fn_name.starts_with("mutate_"); - - if is_mutation { - return vec![ArchDiagnostic::warning( - self.name(), - format!( - "function '{}' has @authenticate but not @authorize — authn without authz on a mutation", - ctx.fn_name - ), - Some(ctx.fn_name.to_string()), - )]; - } - vec![] - } -} diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/rules/swarm.rs b/_archive/rust-bootstrap/engrams/el-arch/src/rules/swarm.rs deleted file mode 100644 index cc8b4c1..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/rules/swarm.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Swarm containment rules — agents are isolated and must not cross boundaries. - -use crate::error::ArchDiagnostic; -use crate::rule::{ArchRule, FnContext}; - -// ── SWARM-001: Swarm agent calling another swarm agent ──────────────────────── - -/// SWARM-001: @swarm_agent functions must not call other @swarm_agent functions. -/// Agents are isolated units; cross-agent calls break containment. -pub struct SwarmAgentIsolation; - -impl ArchRule for SwarmAgentIsolation { - fn name(&self) -> &str { "SWARM-001" } - - fn description(&self) -> &str { - "@swarm_agent must not call another @swarm_agent (agents are isolated)" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("swarm_agent") { - return vec![]; - } - ctx.body_calls - .iter() - .filter(|call| ctx.callee_has_annotation(&call.callee, "swarm_agent")) - .map(|call| ArchDiagnostic::error( - self.name(), - format!( - "@swarm_agent '{}' calls @swarm_agent '{}' — agents must be isolated", - ctx.fn_name, call.callee - ), - Some(ctx.fn_name.to_string()), - )) - .collect() - } -} - -// ── SWARM-002: Swarm agent initiating a spawn/swarm ────────────────────────── - -/// SWARM-002: @swarm_agent must not initiate spawning of other agents -/// (calls to functions named `spawn` or containing "swarm" in the name). -pub struct SwarmAgentNoSpawn; - -impl ArchRule for SwarmAgentNoSpawn { - fn name(&self) -> &str { "SWARM-002" } - - fn description(&self) -> &str { - "@swarm_agent must not call spawn/swarm functions (agents cannot initiate sub-swarms)" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("swarm_agent") { - return vec![]; - } - ctx.body_calls - .iter() - .filter(|call| { - let c = call.callee.as_str(); - c == "spawn" || c.contains("swarm") || c.starts_with("spawn_") - }) - .map(|call| ArchDiagnostic::error( - self.name(), - format!( - "@swarm_agent '{}' calls '{}' — agents cannot initiate spawning", - ctx.fn_name, call.callee - ), - Some(ctx.fn_name.to_string()), - )) - .collect() - } -} - -// ── SWARM-003: Swarm agent accessing shared mutable state ───────────────────── - -/// SWARM-003: @swarm_agent must not access shared mutable state. -/// Heuristic: calls to functions with "shared" in the name suggest shared state access. -pub struct SwarmAgentNoSharedState; - -impl ArchRule for SwarmAgentNoSharedState { - fn name(&self) -> &str { "SWARM-003" } - - fn description(&self) -> &str { - "@swarm_agent must not access shared mutable state (functions with 'shared' in name)" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("swarm_agent") { - return vec![]; - } - ctx.body_calls - .iter() - .filter(|call| call.callee.contains("shared")) - .map(|call| ArchDiagnostic::error( - self.name(), - format!( - "@swarm_agent '{}' accesses shared state via '{}' — agents must not touch shared mutable state", - ctx.fn_name, call.callee - ), - Some(ctx.fn_name.to_string()), - )) - .collect() - } -} diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/rules/vbd.rs b/_archive/rust-bootstrap/engrams/el-arch/src/rules/vbd.rs deleted file mode 100644 index 234fcda..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/rules/vbd.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! VBD (Volatility-Based Decomposition) and EBD (Experience-Based Decomposition) layer rules. - -use crate::error::ArchDiagnostic; -use crate::rule::{ArchRule, FnContext}; - -// ── VBD-001: Accessor must not call Manager ─────────────────────────────────── - -/// VBD-001: @accessor functions must not call @manager functions. -/// Accessors are read-only, stable-interface components; they must not depend -/// on manager-layer orchestration logic. -pub struct AccessorMustNotCallManager; - -impl ArchRule for AccessorMustNotCallManager { - fn name(&self) -> &str { "VBD-001" } - - fn description(&self) -> &str { - "@accessor must not call @manager functions (accessor must not depend on manager layer)" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("accessor") { - return vec![]; - } - ctx.body_calls - .iter() - .filter(|call| ctx.callee_has_annotation(&call.callee, "manager")) - .map(|call| ArchDiagnostic::error( - self.name(), - format!( - "accessor '{}' calls manager '{}' — accessors must not depend on the manager layer", - ctx.fn_name, call.callee - ), - Some(ctx.fn_name.to_string()), - )) - .collect() - } -} - -// ── VBD-002: Experience must not directly call Experience ───────────────────── - -/// VBD-002 / EBD-001: @experience functions must not call other @experience functions directly. -/// Experiences should communicate via events, not direct calls, to preserve -/// loose coupling between user-facing features. -pub struct ExperienceMustNotCallExperience; - -impl ArchRule for ExperienceMustNotCallExperience { - fn name(&self) -> &str { "VBD-002" } - - fn description(&self) -> &str { - "@experience must not call another @experience directly (use events instead)" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("experience") { - return vec![]; - } - ctx.body_calls - .iter() - .filter(|call| ctx.callee_has_annotation(&call.callee, "experience")) - .map(|call| ArchDiagnostic::error( - self.name(), - format!( - "experience '{}' directly calls experience '{}' — use an event instead", - ctx.fn_name, call.callee - ), - Some(ctx.fn_name.to_string()), - )) - .collect() - } -} - -// ── VBD-003: Experience should return Result ──────────────────────────── - -/// VBD-003: @experience functions should return Result for proper error propagation. -pub struct ExperienceShouldReturnResult; - -impl ArchRule for ExperienceShouldReturnResult { - fn name(&self) -> &str { "VBD-003" } - - fn description(&self) -> &str { - "@experience functions should return Result for proper error handling" - } - - fn check(&self, ctx: &FnContext<'_>) -> Vec { - if !ctx.has_annotation("experience") { - return vec![]; - } - // Warn if return type doesn't include "Result" - if !ctx.return_type_name.contains("Result") { - return vec![ArchDiagnostic::warning( - self.name(), - format!( - "experience '{}' returns '{}' instead of Result — experiences should propagate errors", - ctx.fn_name, ctx.return_type_name - ), - Some(ctx.fn_name.to_string()), - )]; - } - vec![] - } -} diff --git a/_archive/rust-bootstrap/engrams/el-arch/src/tests.rs b/_archive/rust-bootstrap/engrams/el-arch/src/tests.rs deleted file mode 100644 index 51c8127..0000000 --- a/_archive/rust-bootstrap/engrams/el-arch/src/tests.rs +++ /dev/null @@ -1,448 +0,0 @@ -//! Comprehensive tests for the el-arch architectural checker. - -use crate::{ArchChecker, ArchDiagnostic, Severity}; - -// ── Test helpers ────────────────────────────────────────────────────────────── - -fn check(src: &str) -> Vec { - let tokens = el_lexer::tokenize(src).expect("lex failed"); - let prog = el_parser::parse(tokens, src.to_string()).expect("parse failed"); - ArchChecker::new().check(&prog) -} - -fn errors(src: &str) -> Vec { - check(src).into_iter().filter(|d| d.severity == Severity::Error).collect() -} - -fn warnings(src: &str) -> Vec { - check(src).into_iter().filter(|d| d.severity == Severity::Warning).collect() -} - -fn has_rule(diags: &[ArchDiagnostic], rule: &str) -> bool { - diags.iter().any(|d| d.rule == rule) -} - -// ── 1. VBD-001: @accessor calling @manager → error ────────────────────────── - -#[test] -fn test_accessor_calls_manager_error() { - let src = r#" -@manager fn orchestrate(x: String) -> String { return x } -@accessor fn fetch_user(id: String) -> String { return orchestrate(id) } -"#; - let errs = errors(src); - assert!(!errs.is_empty(), "expected error when @accessor calls @manager"); - assert!(has_rule(&errs, "VBD-001"), "expected VBD-001 rule"); -} - -// ── 2. @accessor calling @accessor → no VBD-001 error ──────────────────────── - -#[test] -fn test_accessor_calls_accessor_no_error() { - let src = r#" -@accessor fn get_name(id: String) -> String { return id } -@accessor fn get_user(id: String) -> String { return get_name(id) } -"#; - let errs = errors(src); - let vbd001: Vec<_> = errs.iter().filter(|d| d.rule == "VBD-001").collect(); - assert!(vbd001.is_empty(), "accessor->accessor should not trigger VBD-001"); -} - -// ── 3. @experience calling @experience → VBD-002 error ─────────────────────── - -#[test] -fn test_experience_calls_experience_error() { - let src = r#" -@experience fn checkout(cart: String) -> Result { return cart } -@experience fn payment(amount: String) -> Result { return checkout(amount) } -"#; - let errs = errors(src); - assert!(!errs.is_empty(), "expected error when @experience calls @experience"); - assert!(has_rule(&errs, "VBD-002"), "expected VBD-002 rule"); -} - -// ── 4. @experience calling non-experience → no VBD-002 error ───────────────── - -#[test] -fn test_experience_calls_non_experience_no_error() { - let src = r#" -@accessor fn load_cart(id: String) -> String { return id } -@experience fn checkout(cart: String) -> Result { return load_cart(cart) } -"#; - let errs = errors(src); - let vbd002: Vec<_> = errs.iter().filter(|d| d.rule == "VBD-002").collect(); - assert!(vbd002.is_empty(), "experience->non-experience should not trigger VBD-002"); -} - -// ── 5. @public function with activate inside → SEC-001 error ───────────────── - -#[test] -fn test_public_fn_with_activate_error() { - let src = r#" -@public fn list_users() -> String { - let users: String = activate User where "all users" - return users -} -"#; - let errs = errors(src); - assert!(!errs.is_empty(), "expected error for @public fn with activate"); - assert!(has_rule(&errs, "SEC-001"), "expected SEC-001 rule"); -} - -// ── 6. @public function without activate → no SEC-001 error ────────────────── - -#[test] -fn test_public_fn_without_activate_no_error() { - let src = r#" -@public fn greet(name: String) -> String { return name } -"#; - let errs = errors(src); - let sec001: Vec<_> = errs.iter().filter(|d| d.rule == "SEC-001").collect(); - assert!(sec001.is_empty(), "@public without activate should not trigger SEC-001"); -} - -// ── 7. N+1: activate inside a for loop → GRAPH-001 warning ─────────────────── - -#[test] -fn test_activate_in_for_loop_n1_warning() { - let src = r#" -fn process_ids(ids: String) -> String { - for id in ids { - let u: String = activate User where "user by id" - } - return "done" -} -"#; - let warns = warnings(src); - assert!(!warns.is_empty(), "expected N+1 warning for activate inside loop"); - assert!(has_rule(&warns, "GRAPH-001"), "expected GRAPH-001 rule"); -} - -// ── 8. activate NOT in loop → no GRAPH-001 warning ─────────────────────────── - -#[test] -fn test_activate_not_in_loop_no_n1_warning() { - let src = r#" -fn fetch_users() -> String { - let users: String = activate User where "recent users" - return users -} -"#; - let warns = warnings(src); - let graph001: Vec<_> = warns.iter().filter(|d| d.rule == "GRAPH-001").collect(); - assert!(graph001.is_empty(), "activate outside loop should not trigger GRAPH-001"); -} - -// ── 9. Duplicate activate same type → GRAPH-002 warning ────────────────────── - -#[test] -fn test_duplicate_activate_same_type_warning() { - let src = r#" -fn inefficient_fn(x: String) -> String { - let a: String = activate User where "active users" - let b: String = activate User where "recent users" - return a -} -"#; - let warns = warnings(src); - assert!(!warns.is_empty(), "expected warning for duplicate activate on same type"); - assert!(has_rule(&warns, "GRAPH-002"), "expected GRAPH-002 rule"); -} - -// ── 10. Different activate types → no GRAPH-002 warning ────────────────────── - -#[test] -fn test_different_activate_types_no_duplicate_warning() { - let src = r#" -fn fetch_all(x: String) -> String { - let users: String = activate User where "users" - let orders: String = activate Order where "orders" - return users -} -"#; - let warns = warnings(src); - let graph002: Vec<_> = warns.iter().filter(|d| d.rule == "GRAPH-002").collect(); - assert!(graph002.is_empty(), "different activate types should not trigger GRAPH-002"); -} - -// ── 11. @swarm_agent calling @swarm_agent → SWARM-001 error ────────────────── - -#[test] -fn test_swarm_agent_calls_swarm_agent_error() { - let src = r#" -@swarm_agent fn worker_b(x: String) -> String { return x } -@swarm_agent fn worker_a(x: String) -> String { return worker_b(x) } -"#; - let errs = errors(src); - assert!(!errs.is_empty(), "expected error when @swarm_agent calls @swarm_agent"); - assert!(has_rule(&errs, "SWARM-001"), "expected SWARM-001 rule"); -} - -// ── 12. @swarm_agent in isolation → no SWARM-001 error ─────────────────────── - -#[test] -fn test_swarm_agent_isolation_no_error() { - let src = r#" -fn utility(x: String) -> String { return x } -@swarm_agent fn worker(x: String) -> String { return utility(x) } -"#; - let errs = errors(src); - let swarm001: Vec<_> = errs.iter().filter(|d| d.rule == "SWARM-001").collect(); - assert!(swarm001.is_empty(), "isolated @swarm_agent should not trigger SWARM-001"); -} - -// ── 13. Multiple rules fire on same function ────────────────────────────────── - -#[test] -fn test_multiple_rules_fire_same_function() { - // @swarm_agent calling a swarm_agent AND accessing shared state - let src = r#" -@swarm_agent fn peer(x: String) -> String { return x } -fn get_shared_cache(x: String) -> String { return x } -@swarm_agent fn violator(x: String) -> String { - let a: String = peer(x) - let b: String = get_shared_cache(x) - return a -} -"#; - let errs = errors(src); - // Should fire both SWARM-001 (calls peer) and SWARM-003 (calls get_shared_cache) - assert!(errs.len() >= 2, "expected multiple errors: {:?}", errs.iter().map(|e| &e.rule).collect::>()); -} - -// ── 14. ArchChecker::has_errors() → true when errors present ───────────────── - -#[test] -fn test_has_errors_true_when_errors_present() { - let src = r#" -@manager fn do_manage(x: String) -> String { return x } -@accessor fn bad_fetch(x: String) -> String { return do_manage(x) } -"#; - let diags = check(src); - assert!(ArchChecker::has_errors(&diags), "has_errors should be true"); -} - -// ── 15. ArchChecker::has_errors() → false when only warnings ───────────────── - -#[test] -fn test_has_errors_false_when_only_warnings() { - let src = r#" -@experience fn sign_up(email: String) -> String { return email } -"#; - // sign_up doesn't return Result so triggers VBD-003 warning - let diags = check(src); - let has_warn = diags.iter().any(|d| d.severity == Severity::Warning); - assert!(has_warn || diags.is_empty(), "expected either warnings or empty"); - assert!(!ArchChecker::has_errors(&diags.iter().filter(|d| d.severity == Severity::Warning).cloned().collect::>()), "has_errors should be false for warnings"); -} - -// ── 16. Clean function → empty diagnostics ──────────────────────────────────── - -#[test] -fn test_clean_function_no_diagnostics() { - let src = r#" -fn pure_add(a: String, b: String) -> String { return a } -"#; - let diags = check(src); - assert!(diags.is_empty(), "clean function should produce no diagnostics, got: {:?}", diags.iter().map(|d| &d.rule).collect::>()); -} - -// ── 17. @engine function → runs without panic ───────────────────────────────── - -#[test] -fn test_engine_function_no_panic() { - let src = r#" -@engine fn compute(data: String) -> String { return data } -"#; - // Just verify no panic — engine rules only produce warnings in some impls - let _diags = check(src); -} - -// ── 18. sealed { } not in loop → no SEC-002 warning ────────────────────────── - -#[test] -fn test_sealed_not_in_loop_no_warning() { - let src = r#" -fn encrypt_data(secret: String) -> String { - sealed { let key: String = secret } - return secret -} -"#; - let warns = warnings(src); - let sec002: Vec<_> = warns.iter().filter(|d| d.rule == "SEC-002").collect(); - assert!(sec002.is_empty(), "sealed not in loop should not trigger SEC-002"); -} - -// ── 19. Rule names are unique ───────────────────────────────────────────────── - -#[test] -fn test_rule_names_are_unique() { - let checker = ArchChecker::new(); - let mut names = std::collections::HashSet::new(); - for rule in checker.rules() { - let inserted = names.insert(rule.name().to_string()); - assert!(inserted, "duplicate rule name: {}", rule.name()); - } -} - -// ── 20. All rules implement ArchRule (compile-time check) ───────────────────── - -#[test] -fn test_all_rules_implement_arch_rule() { - use crate::rule::ArchRule; - use crate::rules::{ - vbd::{AccessorMustNotCallManager, ExperienceMustNotCallExperience, ExperienceShouldReturnResult}, - security::{PublicFnWithActivate, SealedInLoop, AuthnWithoutAuthz}, - graph::{N1Detection, DuplicateActivateType}, - swarm::{SwarmAgentIsolation, SwarmAgentNoSpawn, SwarmAgentNoSharedState}, - }; - - fn assert_arch_rule() {} - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); - assert_arch_rule::(); -} - -// ── 21. FnContext builds correctly for a decorated function ─────────────────── - -#[test] -fn test_fn_context_from_decorated_function() { - // Run checker and verify the location field is set to the function name - let src = r#" -@accessor fn fetch_item(id: String) -> String { return id } -"#; - let diags = check(src); - // No violations — but verify the checker doesn't panic and processes it - // (accessor with no calls should produce no errors) - let _: Vec<_> = diags; -} - -// ── 22. @experience without Result return type → VBD-003 warning ───────────── - -#[test] -fn test_experience_non_result_return_warning() { - let src = r#" -@experience fn show_profile(user: String) -> String { return user } -"#; - let warns = warnings(src); - assert!(!warns.is_empty(), "expected warning for @experience not returning Result"); - assert!(has_rule(&warns, "VBD-003"), "expected VBD-003 rule"); -} - -// ── 23. @authenticate without @authorize on mutation → SEC-003 warning ──────── - -#[test] -fn test_authenticate_without_authorize_on_mutation_warning() { - let src = r#" -@authenticate fn create_account(email: String) -> String { return email } -"#; - let warns = warnings(src); - assert!(!warns.is_empty(), "expected SEC-003 warning"); - assert!(has_rule(&warns, "SEC-003"), "expected SEC-003 rule"); -} - -// ── 24. Two @experience functions checked independently ─────────────────────── - -#[test] -fn test_two_experience_functions_checked_independently() { - let src = r#" -@experience fn sign_up(email: String) -> Result { return email } -@experience fn log_in(token: String) -> String { return token } -"#; - let warns = warnings(src); - // sign_up returns Result — no VBD-003 for it - // log_in returns String — VBD-003 fires for it - let vbd003: Vec<_> = warns.iter().filter(|d| d.rule == "VBD-003").collect(); - assert_eq!(vbd003.len(), 1, "only log_in should trigger VBD-003, got {:?}", vbd003.iter().map(|d| &d.location).collect::>()); - assert!(vbd003[0].location.as_deref() == Some("log_in"), "VBD-003 should point to log_in"); -} - -// ── 25. Mixed errors and warnings → has_errors() returns true ───────────────── - -#[test] -fn test_mixed_errors_and_warnings_has_errors_true() { - let src = r#" -@manager fn manage_data(x: String) -> String { return x } -@accessor fn bad_read(x: String) -> String { return manage_data(x) } -@experience fn display(x: String) -> String { return x } -"#; - let diags = check(src); - assert!(ArchChecker::has_errors(&diags), "should have errors (VBD-001)"); - let has_warn = diags.iter().any(|d| d.severity == Severity::Warning); - assert!(has_warn, "should also have warnings (VBD-003 for display)"); -} - -// ── 26. @swarm_agent calling spawn → SWARM-002 error ───────────────────────── - -#[test] -fn test_swarm_agent_no_spawn() { - let src = r#" -fn spawn(agent: String) -> String { return agent } -@swarm_agent fn initiator(x: String) -> String { return spawn(x) } -"#; - let errs = errors(src); - assert!(!errs.is_empty(), "expected SWARM-002 error for calling spawn"); - assert!(has_rule(&errs, "SWARM-002"), "expected SWARM-002 rule"); -} - -// ── 27. @swarm_agent accessing shared state → SWARM-003 error ──────────────── - -#[test] -fn test_swarm_agent_no_shared_state() { - let src = r#" -fn get_shared_counter(x: String) -> String { return x } -@swarm_agent fn agent(x: String) -> String { return get_shared_counter(x) } -"#; - let errs = errors(src); - assert!(!errs.is_empty(), "expected SWARM-003 error for shared state access"); - assert!(has_rule(&errs, "SWARM-003"), "expected SWARM-003 rule"); -} - -// ── 28. sealed block inside for loop → SEC-002 warning ─────────────────────── - -#[test] -fn test_sealed_in_for_loop_warning() { - let src = r#" -fn encrypt_many(items: String) -> String { - for item in items { - sealed { let x: String = item } - } - return items -} -"#; - let warns = warnings(src); - assert!(!warns.is_empty(), "expected SEC-002 warning for sealed in loop"); - assert!(has_rule(&warns, "SEC-002"), "expected SEC-002 rule"); -} - -// ── 29. @authenticate with @authorize → no SEC-003 warning ─────────────────── - -#[test] -fn test_authenticate_with_authorize_no_warning() { - let src = r#" -@authenticate @authorize fn create_post(content: String) -> String { return content } -"#; - let warns = warnings(src); - let sec003: Vec<_> = warns.iter().filter(|d| d.rule == "SEC-003").collect(); - assert!(sec003.is_empty(), "@authenticate + @authorize should not trigger SEC-003"); -} - -// ── 30. @experience returning Result → no VBD-003 warning ──────────────────── - -#[test] -fn test_experience_returns_result_no_warning() { - let src = r#" -@experience fn register(email: String) -> Result { return email } -"#; - let warns = warnings(src); - let vbd003: Vec<_> = warns.iter().filter(|d| d.rule == "VBD-003").collect(); - assert!(vbd003.is_empty(), "@experience returning Result should not trigger VBD-003"); -} diff --git a/_archive/rust-bootstrap/engrams/el-build/Cargo.toml b/_archive/rust-bootstrap/engrams/el-build/Cargo.toml deleted file mode 100644 index cceff6d..0000000 --- a/_archive/rust-bootstrap/engrams/el-build/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "el-build" -description = "Build orchestrator and incremental build system for the Engram language toolchain" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-manifest = { path = "../el-manifest" } -el-registry = { path = "../el-registry" } -el-compiler = { path = "../el-compiler" } -el-lexer = { workspace = true } -el-parser = { workspace = true } -el-types = { workspace = true } -el-seal = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -thiserror = { workspace = true } -blake3 = { workspace = true } -tokio = { version = "1", features = ["fs", "io-util", "rt", "macros", "process"] } -semver = { version = "1", features = ["serde"] } - -[dev-dependencies] -tempfile = "3" diff --git a/_archive/rust-bootstrap/engrams/el-build/src/build.rs b/_archive/rust-bootstrap/engrams/el-build/src/build.rs deleted file mode 100644 index c2a6cdd..0000000 --- a/_archive/rust-bootstrap/engrams/el-build/src/build.rs +++ /dev/null @@ -1,589 +0,0 @@ -//! The core build orchestrator. - -use std::path::{Path, PathBuf}; -use std::time::Instant; - -use el_compiler::{Compiler, CompilerOptions, Target}; -use el_manifest::{BuildTarget, CrossTarget, Manifest, NativeTarget, SealKeySource}; -use el_seal::{DeploymentBinding, SealAlgorithm, SealConfig}; -use semver::Version; - -use crate::cache::BuildCache; -use crate::error::{BuildError, BuildResult}; - -// ── Output types ────────────────────────────────────────────────────────────── - -/// The output of a single successful build. -#[derive(Debug, Clone)] -pub struct BuildOutput { - /// Path to the produced artifact. - pub artifact_path: PathBuf, - /// Compilation target used. - pub target: BuildTarget, - /// Cross-compilation target, if this was a cross build. - pub cross_target: Option, - /// Whether the artifact is quantum-sealed. - pub sealed: bool, - /// Size of the artifact in bytes. - pub size_bytes: u64, - /// Wall-clock compilation time in milliseconds. - pub compile_time_ms: u64, -} - -/// A resolved dependency (after registry lookup / path resolution). -#[derive(Debug, Clone)] -pub struct ResolvedDep { - pub name: String, - pub version: Version, - pub source: DepSource, - /// Local path to the package (either downloaded cache or local path dep). - pub path: PathBuf, -} - -/// Where a resolved dependency came from. -#[derive(Debug, Clone)] -pub enum DepSource { - /// Downloaded from a registry URL. - Registry(String), - /// A local path dependency. - Path(PathBuf), - /// A git source (not yet implemented; reserved for future use). - Git(String), -} - -/// Report returned by `BuildSystem::test()`. -#[derive(Debug, Clone)] -pub struct TestReport { - pub total: usize, - pub passed: usize, - pub failed: usize, - /// Descriptions of each failing test. - pub failures: Vec, -} - -impl TestReport { - pub fn success(&self) -> bool { - self.failed == 0 - } -} - -// ── Build system ────────────────────────────────────────────────────────────── - -/// Orchestrates the full build pipeline for an Engram project. -pub struct BuildSystem { - pub manifest: Manifest, - pub workspace_root: PathBuf, -} - -impl BuildSystem { - /// Create a new build system from a manifest and workspace root path. - pub fn new(manifest: Manifest, workspace_root: PathBuf) -> Self { - Self { manifest, workspace_root } - } - - /// Load from a manifest file, setting the workspace root to the manifest's directory. - pub fn from_manifest_file(manifest_path: &Path) -> BuildResult { - let manifest = Manifest::from_file(manifest_path)?; - let workspace_root = manifest_path - .parent() - .unwrap_or(manifest_path) - .to_path_buf(); - Ok(Self { manifest, workspace_root }) - } - - // ── Build ───────────────────────────────────────────────────────────────── - - /// Full build: resolve deps (skipped for now — no live registry), compile, produce artifact. - /// - /// `target` overrides the manifest's `[build].target` setting. - pub async fn build(&self, target: Option) -> BuildResult { - let effective_target = target.unwrap_or(self.manifest.build.target.clone()); - self.build_for_target(&effective_target, None).await - } - - /// Build for all cross-compilation targets declared in `[cross]`. - pub async fn build_all_targets( - &self, - ) -> BuildResult> { - let mut results = Vec::new(); - for cross_target in &self.manifest.cross.targets { - let build_target = self.manifest.build.target.clone(); - let output = self.build_for_target(&build_target, Some(cross_target)).await?; - results.push((cross_target.clone(), output)); - } - Ok(results) - } - - async fn build_for_target( - &self, - build_target: &BuildTarget, - cross_target: Option<&CrossTarget>, - ) -> BuildResult { - let start = Instant::now(); - - // Locate entry point - let entry = self.workspace_root.join(&self.manifest.build.entry); - if !entry.exists() { - return Err(BuildError::EntryNotFound(entry.display().to_string())); - } - - // Check incremental cache - let mut cache = BuildCache::load(&self.workspace_root); - let rel_entry = self - .manifest - .build - .entry - .to_string_lossy() - .to_string(); - let current_hash = BuildCache::hash_file(&entry)?; - let is_cached = cache.is_up_to_date(&rel_entry, ¤t_hash); - - // Build output path - let output_dir = self.workspace_root.join(&self.manifest.build.output); - std::fs::create_dir_all(&output_dir)?; - - let artifact_name = artifact_name( - &self.manifest.package.name, - build_target, - cross_target, - ); - let artifact_path = output_dir.join(&artifact_name); - - if is_cached && artifact_path.exists() { - let size_bytes = std::fs::metadata(&artifact_path)?.len(); - return Ok(BuildOutput { - artifact_path, - target: build_target.clone(), - cross_target: cross_target.cloned(), - sealed: matches!(build_target, BuildTarget::Prod), - size_bytes, - compile_time_ms: start.elapsed().as_millis() as u64, - }); - } - - // Read source (resolving imports recursively) - let source = resolve_imports_recursive(&entry) - .map_err(|e| BuildError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?; - - // Build seal config for prod builds - let seal_config = self.build_seal_config()?; - - // Compile - let compiler_target = match build_target { - BuildTarget::Debug => Target::Debug, - BuildTarget::Release => Target::Release, - BuildTarget::Prod => Target::Prod, - }; - - let opts = CompilerOptions { - target: compiler_target, - output_path: artifact_path.clone(), - source_path: entry.clone(), - engram_db_path: None, - seal_config, - }; - - let output = Compiler::compile(&source, opts)?; - - // Emit diagnostics - for diag in &output.diagnostics { - eprintln!("warning: {diag}"); - } - - // Write artifact - std::fs::write(&artifact_path, &output.artifact)?; - - // Annotate artifact with cross-target triple (stub — in LLVM backend this - // selects the code generation target). - if let Some(ct) = cross_target { - let native = NativeTarget::from_cross(ct); - let annotation_path = artifact_path.with_extension("target"); - std::fs::write(&annotation_path, native.triple())?; - } - - // Update cache - cache.record(&rel_entry, ¤t_hash); - cache.save(&self.workspace_root)?; - - let size_bytes = std::fs::metadata(&artifact_path)?.len(); - let compile_time_ms = start.elapsed().as_millis() as u64; - - Ok(BuildOutput { - artifact_path, - target: build_target.clone(), - cross_target: cross_target.cloned(), - sealed: output.sealed, - size_bytes, - compile_time_ms, - }) - } - - // ── Dependencies ────────────────────────────────────────────────────────── - - /// Resolve and download all registry dependencies. - pub async fn resolve_deps(&self) -> BuildResult> { - let mut resolved = Vec::new(); - - for (name, dep) in &self.manifest.dependencies { - match dep { - el_manifest::Dependency::Path(path) => { - let abs_path = if path.is_absolute() { - path.clone() - } else { - self.workspace_root.join(path) - }; - resolved.push(ResolvedDep { - name: name.clone(), - version: Version::new(0, 0, 0), - source: DepSource::Path(abs_path.clone()), - path: abs_path, - }); - } - el_manifest::Dependency::VersionReq(_req) => { - // In a live environment this would call the registry. - // For now, record the dep with the local cache path. - let cache_base = el_registry::cache_dir().join(name); - resolved.push(ResolvedDep { - name: name.clone(), - version: Version::new(0, 0, 0), // placeholder until registry is live - source: DepSource::Registry( - el_registry::DEFAULT_REGISTRY_URL.to_string(), - ), - path: cache_base, - }); - } - el_manifest::Dependency::Registry { version: _version, registry } => { - let cache_base = el_registry::cache_dir().join(name); - resolved.push(ResolvedDep { - name: name.clone(), - version: Version::new(0, 0, 0), - source: DepSource::Registry(registry.clone()), - path: cache_base, - }); - } - } - } - - Ok(resolved) - } - - // ── Test ────────────────────────────────────────────────────────────────── - - /// Discover and run all test files. - pub async fn test(&self) -> BuildResult { - let src_root = self.workspace_root.join("src"); - crate::test_runner::run_tests(&src_root, &self.workspace_root).await - } - - // ── Format ──────────────────────────────────────────────────────────────── - - /// Format all source files (stub — delegates to `el-fmt` plugin when available). - pub fn fmt(&self) -> BuildResult<()> { - let sources = BuildCache::collect_sources(&self.workspace_root.join("src")); - for file in &sources { - // TODO: invoke el-fmt plugin or built-in formatter. - let _ = file; - } - println!("fmt: {} source file(s) checked", sources.len()); - Ok(()) - } - - // ── Check ───────────────────────────────────────────────────────────────── - - /// Type-check source files without producing artifacts. - pub fn check(&self) -> BuildResult> { - let entry = self.workspace_root.join(&self.manifest.build.entry); - if !entry.exists() { - return Err(BuildError::EntryNotFound(entry.display().to_string())); - } - - let source = resolve_imports_recursive(&entry) - .map_err(|e| BuildError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?; - let tokens = el_lexer::tokenize(&source) - .map_err(el_compiler::CompileError::Lex)?; - let program = el_parser::parse(tokens, source.clone()) - .map_err(el_compiler::CompileError::Parse)?; - let mut checker = el_types::TypeChecker::with_builtins(); - let diags = checker.check(&program); - Ok(diags.iter().map(|d| d.message.clone()).collect()) - } - - // ── Clean ───────────────────────────────────────────────────────────────── - - /// Remove build artifacts and the build cache. - pub fn clean(&self) -> BuildResult<()> { - let output_dir = self.workspace_root.join(&self.manifest.build.output); - if output_dir.exists() { - std::fs::remove_dir_all(&output_dir)?; - } - let cache_dir = self.workspace_root.join(".el"); - if cache_dir.exists() { - std::fs::remove_dir_all(&cache_dir)?; - } - println!("clean: removed build artifacts and cache"); - Ok(()) - } - - // ── Helpers ─────────────────────────────────────────────────────────────── - - fn build_seal_config(&self) -> BuildResult { - let binding = match &self.manifest.build.seal_key { - Some(SealKeySource::EnvVar(var)) => DeploymentBinding::EnvironmentKey(var.clone()), - Some(SealKeySource::File(_)) | Some(SealKeySource::Literal(_)) => { - // For file/literal keys, fall back to machine fingerprint in prod. - DeploymentBinding::None - } - None => DeploymentBinding::None, - }; - Ok(SealConfig { - algorithm: SealAlgorithm::Aes256Gcm, - deployment_binding: binding, - }) - } -} - -/// Resolve `import "path.el"` directives by reading and concatenating source files. -/// Imports are resolved relative to the directory of the importing file. -/// Circular imports are detected via a visited set. -fn resolve_imports_recursive(file: &std::path::Path) -> Result { - let mut visited = std::collections::HashSet::new(); - resolve_imports_inner(file, &mut visited) -} - -fn resolve_imports_inner( - file: &std::path::Path, - visited: &mut std::collections::HashSet, -) -> Result { - let canonical = file.canonicalize().unwrap_or_else(|_| file.to_path_buf()); - if visited.contains(&canonical) { - return Ok(String::new()); // circular — skip - } - visited.insert(canonical.clone()); - - let dir = file.parent().unwrap_or(std::path::Path::new(".")); - let source = std::fs::read_to_string(file) - .map_err(|e| format!("cannot read {}: {e}", file.display()))?; - - let mut out = String::new(); - let mut lines_iter = source.lines().peekable(); - while let Some(line) = lines_iter.next() { - let trimmed = line.trim(); - if let Some(rest) = trimmed.strip_prefix("import ") { - let rest = rest.trim(); - if rest.starts_with('"') && rest.ends_with('"') { - // import "relative/path.el" - let import_path_str = &rest[1..rest.len() - 1]; - let import_path = dir.join(import_path_str); - let imported = resolve_imports_inner(&import_path, visited)?; - out.push_str(&imported); - out.push('\n'); - } else { - out.push_str(line); - out.push('\n'); - } - } else if let Some(rest) = trimmed.strip_prefix("from ") { - // from ModuleName import { ... } — resolve ModuleName.el in the same dir - // Extract the module name (everything before " import") - if let Some(module_part) = rest.split(" import").next() { - let module_name = module_part.trim(); - // Only resolve bare identifiers (not quoted paths or dotted names with /) - if !module_name.contains('"') && !module_name.contains('/') && !module_name.is_empty() { - let module_file = format!("{}.el", module_name); - let import_path = dir.join(&module_file); - - // Consume any continuation lines of a multi-line import: - // `from X import {\n Y,\n Z,\n}` - // The opening line may or may not contain `{`. If it does not end - // with `}`, skip lines until we see one that ends with `}`. - let import_rest = rest.splitn(2, " import").nth(1).unwrap_or("").trim(); - let is_multiline = import_rest.contains('{') && !import_rest.contains('}'); - if is_multiline { - // Skip continuation lines until we see `}` - for cont in lines_iter.by_ref() { - if cont.trim().contains('}') { - break; - } - } - } - - if import_path.exists() { - // Inline the module, omitting the import line itself - let imported = resolve_imports_inner(&import_path, visited)?; - out.push_str(&imported); - out.push('\n'); - } else { - // Module file not found — leave the line for the compiler to handle - out.push_str(line); - out.push('\n'); - } - } else { - out.push_str(line); - out.push('\n'); - } - } else { - out.push_str(line); - out.push('\n'); - } - } else { - out.push_str(line); - out.push('\n'); - } - } - Ok(out) -} - -fn artifact_name( - pkg_name: &str, - build_target: &BuildTarget, - cross_target: Option<&CrossTarget>, -) -> String { - let ext = match (build_target, cross_target) { - (BuildTarget::Prod, _) => ".sealed", - (_, Some(CrossTarget::Wasm32)) => ".wasm", - _ => ".elc", - }; - - if let Some(ct) = cross_target { - format!("{pkg_name}-{ct}{ext}") - } else { - format!("{pkg_name}{ext}") - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use el_manifest::{BuildConfig, CrossConfig, PackageInfo}; - use semver::Version; - use std::collections::HashMap; - use tempfile::TempDir; - - fn temp_dir() -> TempDir { - tempfile::TempDir::new().unwrap() - } - - fn simple_manifest(dir: &Path) -> Manifest { - Manifest { - package: PackageInfo { - name: "test-pkg".to_string(), - version: Version::new(0, 1, 0), - description: None, - authors: vec![], - license: None, - edition: "2026".to_string(), - }, - dependencies: HashMap::new(), - dev_dependencies: HashMap::new(), - build: BuildConfig { - target: BuildTarget::Debug, - entry: PathBuf::from("src/main.el"), - output: PathBuf::from("dist/"), - seal_key: None, - }, - cross: CrossConfig { targets: vec![] }, - plugins: HashMap::new(), - app: None, - } - } - - #[tokio::test] - async fn test_build_debug() { - let dir = temp_dir(); - // Create entry file - let src = dir.path().join("src"); - std::fs::create_dir(&src).unwrap(); - std::fs::write(src.join("main.el"), b"let x: Int = 42").unwrap(); - - let manifest = simple_manifest(dir.path()); - let bs = BuildSystem::new(manifest, dir.path().to_path_buf()); - let output = bs.build(None).await.unwrap(); - assert!(output.artifact_path.exists()); - assert!(!output.sealed); - assert_eq!(output.target, BuildTarget::Debug); - assert!(output.size_bytes > 0); - } - - #[tokio::test] - async fn test_build_incremental_skips_rebuild() { - let dir = temp_dir(); - let src = dir.path().join("src"); - std::fs::create_dir(&src).unwrap(); - std::fs::write(src.join("main.el"), b"let x: Int = 1").unwrap(); - - let manifest = simple_manifest(dir.path()); - let bs = BuildSystem::new(manifest, dir.path().to_path_buf()); - - let out1 = bs.build(None).await.unwrap(); - let t1 = out1.compile_time_ms; - let out2 = bs.build(None).await.unwrap(); - // Second build should be very fast (cache hit) - assert_eq!(out1.artifact_path, out2.artifact_path); - } - - #[tokio::test] - async fn test_build_missing_entry_errors() { - let dir = temp_dir(); - let manifest = simple_manifest(dir.path()); - let bs = BuildSystem::new(manifest, dir.path().to_path_buf()); - let err = bs.build(None).await.unwrap_err(); - assert!(matches!(err, BuildError::EntryNotFound(_))); - } - - #[tokio::test] - async fn test_clean() { - let dir = temp_dir(); - let src = dir.path().join("src"); - std::fs::create_dir(&src).unwrap(); - std::fs::write(src.join("main.el"), b"let x = 1").unwrap(); - - let manifest = simple_manifest(dir.path()); - let bs = BuildSystem::new(manifest, dir.path().to_path_buf()); - bs.build(None).await.unwrap(); - bs.clean().unwrap(); - - let dist = dir.path().join("dist"); - assert!(!dist.exists()); - } - - #[tokio::test] - async fn test_resolve_path_dep() { - let dir = temp_dir(); - let mut manifest = simple_manifest(dir.path()); - manifest.dependencies.insert( - "local-lib".to_string(), - el_manifest::Dependency::Path(PathBuf::from("../local-lib")), - ); - - let bs = BuildSystem::new(manifest, dir.path().to_path_buf()); - let deps = bs.resolve_deps().await.unwrap(); - assert_eq!(deps.len(), 1); - assert_eq!(deps[0].name, "local-lib"); - assert!(matches!(deps[0].source, DepSource::Path(_))); - } - - #[test] - fn test_artifact_name_debug() { - let name = artifact_name("my-pkg", &BuildTarget::Debug, None); - assert_eq!(name, "my-pkg.elc"); - } - - #[test] - fn test_artifact_name_prod() { - let name = artifact_name("my-pkg", &BuildTarget::Prod, None); - assert_eq!(name, "my-pkg.sealed"); - } - - #[test] - fn test_artifact_name_wasm_cross() { - let name = artifact_name("my-pkg", &BuildTarget::Debug, Some(&CrossTarget::Wasm32)); - assert_eq!(name, "my-pkg-wasm32.wasm"); - } - - #[test] - fn test_artifact_name_linux_cross() { - let name = artifact_name("my-pkg", &BuildTarget::Release, Some(&CrossTarget::X86_64Linux)); - assert_eq!(name, "my-pkg-x86_64-linux.elc"); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-build/src/cache.rs b/_archive/rust-bootstrap/engrams/el-build/src/cache.rs deleted file mode 100644 index aacde31..0000000 --- a/_archive/rust-bootstrap/engrams/el-build/src/cache.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Incremental build cache. -//! -//! Stores BLAKE3 hashes of source files in `.el/build-cache.json` so the -//! build system can skip recompiling files that haven't changed. - -use std::collections::HashMap; -use std::path::{Path, PathBuf}; - -use serde::{Deserialize, Serialize}; - -/// The on-disk structure of the build cache. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct BuildCache { - /// Map of file path (relative to workspace root) → BLAKE3 hex hash. - pub file_hashes: HashMap, -} - -impl BuildCache { - /// Load the build cache from `.el/build-cache.json`. - /// - /// Returns an empty cache if the file doesn't exist yet. - pub fn load(workspace_root: &Path) -> Self { - let path = cache_path(workspace_root); - if !path.exists() { - return Self::default(); - } - let text = std::fs::read_to_string(&path).unwrap_or_default(); - serde_json::from_str(&text).unwrap_or_default() - } - - /// Persist the cache back to disk. - pub fn save(&self, workspace_root: &Path) -> Result<(), std::io::Error> { - let path = cache_path(workspace_root); - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - let json = serde_json::to_string_pretty(self)?; - std::fs::write(&path, json) - } - - /// Hash a single file and return the BLAKE3 hex string. - pub fn hash_file(path: &Path) -> Result { - let bytes = std::fs::read(path)?; - Ok(hex_encode(blake3::hash(&bytes).as_bytes())) - } - - /// Returns `true` if the file's current hash matches the cached hash. - pub fn is_up_to_date(&self, rel_path: &str, current_hash: &str) -> bool { - self.file_hashes - .get(rel_path) - .map(|cached| cached == current_hash) - .unwrap_or(false) - } - - /// Update the cached hash for a file. - pub fn record(&mut self, rel_path: impl Into, hash: impl Into) { - self.file_hashes.insert(rel_path.into(), hash.into()); - } - - /// Collect all `.el` source files under a directory recursively. - pub fn collect_sources(root: &Path) -> Vec { - let mut sources = Vec::new(); - collect_el_files(root, &mut sources); - sources.sort(); - sources - } -} - -fn cache_path(workspace_root: &Path) -> PathBuf { - workspace_root.join(".el").join("build-cache.json") -} - -fn collect_el_files(dir: &Path, out: &mut Vec) { - let Ok(entries) = std::fs::read_dir(dir) else { - return; - }; - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - // Skip hidden dirs and build outputs - let name = path.file_name().unwrap_or_default().to_string_lossy(); - if name.starts_with('.') || name == "dist" || name == "target" { - continue; - } - collect_el_files(&path, out); - } else if path.extension().map(|e| e == "el").unwrap_or(false) { - out.push(path); - } - } -} - -fn hex_encode(bytes: &[u8]) -> String { - bytes.iter().map(|b| format!("{b:02x}")).collect() -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use std::io::Write; - use tempfile::TempDir; - - fn temp_dir() -> TempDir { - tempfile::TempDir::new().unwrap() - } - - #[test] - fn test_cache_empty_by_default() { - let dir = temp_dir(); - let cache = BuildCache::load(dir.path()); - assert!(cache.file_hashes.is_empty()); - } - - #[test] - fn test_cache_save_and_load() { - let dir = temp_dir(); - let mut cache = BuildCache::default(); - cache.record("src/main.el", "abc123"); - cache.save(dir.path()).unwrap(); - - let loaded = BuildCache::load(dir.path()); - assert_eq!(loaded.file_hashes.get("src/main.el").map(|s| s.as_str()), Some("abc123")); - } - - #[test] - fn test_is_up_to_date() { - let mut cache = BuildCache::default(); - cache.record("src/main.el", "deadbeef"); - - assert!(cache.is_up_to_date("src/main.el", "deadbeef")); - assert!(!cache.is_up_to_date("src/main.el", "different")); - assert!(!cache.is_up_to_date("src/other.el", "deadbeef")); - } - - #[test] - fn test_hash_file() { - let dir = temp_dir(); - let path = dir.path().join("test.el"); - std::fs::write(&path, b"let x = 1").unwrap(); - let hash1 = BuildCache::hash_file(&path).unwrap(); - let hash2 = BuildCache::hash_file(&path).unwrap(); - assert_eq!(hash1, hash2); // deterministic - - std::fs::write(&path, b"let x = 2").unwrap(); - let hash3 = BuildCache::hash_file(&path).unwrap(); - assert_ne!(hash1, hash3); // different content → different hash - } - - #[test] - fn test_collect_sources() { - let dir = temp_dir(); - let src = dir.path().join("src"); - std::fs::create_dir(&src).unwrap(); - std::fs::write(src.join("main.el"), b"fn main() {}").unwrap(); - std::fs::write(src.join("lib.el"), b"fn helper() {}").unwrap(); - std::fs::write(src.join("README.md"), b"# README").unwrap(); - - let sources = BuildCache::collect_sources(dir.path()); - assert_eq!(sources.len(), 2); - assert!(sources.iter().any(|p| p.file_name().unwrap() == "main.el")); - assert!(sources.iter().any(|p| p.file_name().unwrap() == "lib.el")); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-build/src/error.rs b/_archive/rust-bootstrap/engrams/el-build/src/error.rs deleted file mode 100644 index a2485e2..0000000 --- a/_archive/rust-bootstrap/engrams/el-build/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Build system error types. - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum BuildError { - #[error("io error: {0}")] - Io(#[from] std::io::Error), - - #[error("manifest error: {0}")] - Manifest(#[from] el_manifest::ManifestError), - - #[error("registry error: {0}")] - Registry(#[from] el_registry::RegistryError), - - #[error("compile error: {0}")] - Compile(#[from] el_compiler::CompileError), - - #[error("json error: {0}")] - Json(#[from] serde_json::Error), - - #[error("plugin error: {0}")] - Plugin(#[from] crate::plugin::PluginError), - - #[error("entry point not found: {0}")] - EntryNotFound(String), - - #[error("build failed: {0}")] - BuildFailed(String), - - #[error("test failed: {count} test(s) failed")] - TestsFailed { count: usize }, -} - -pub type BuildResult = Result; diff --git a/_archive/rust-bootstrap/engrams/el-build/src/lib.rs b/_archive/rust-bootstrap/engrams/el-build/src/lib.rs deleted file mode 100644 index 3714cb4..0000000 --- a/_archive/rust-bootstrap/engrams/el-build/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! el-build — Build orchestrator for the Engram language toolchain. -//! -//! Reads `manifest.el`, resolves dependencies, compiles source files, and produces -//! artifacts. Supports incremental builds via BLAKE3 file hashes stored in -//! `.el/build-cache.json`. -//! -//! # Usage -//! ```rust,no_run -//! use el_build::BuildSystem; -//! use el_manifest::Manifest; -//! -//! # async fn example() -> Result<(), Box> { -//! let manifest = Manifest::from_file(std::path::Path::new("manifest.el"))?; -//! let bs = BuildSystem::new(manifest, std::env::current_dir()?); -//! let output = bs.build(None).await?; -//! println!("artifact: {}", output.artifact_path.display()); -//! # Ok(()) -//! # } -//! ``` - -mod build; -mod cache; -mod error; -mod plugin; -mod test_runner; - -pub use build::{BuildOutput, BuildSystem, DepSource, ResolvedDep, TestReport}; -pub use cache::BuildCache; -pub use error::{BuildError, BuildResult}; -pub use plugin::{CompilerPlugin, PluginError, PluginRegistry}; diff --git a/_archive/rust-bootstrap/engrams/el-build/src/plugin.rs b/_archive/rust-bootstrap/engrams/el-build/src/plugin.rs deleted file mode 100644 index 3cbad84..0000000 --- a/_archive/rust-bootstrap/engrams/el-build/src/plugin.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! Compiler plugin system. -//! -//! Plugins are Rust dynamic libraries (`.dylib` / `.so`) that implement the -//! [`CompilerPlugin`] trait. They are loaded at compile time and receive hooks -//! at each stage of the compilation pipeline. -//! -//! # Lifecycle hooks -//! 1. `on_ast` — called after parsing, before type checking -//! 2. `on_typed_ast` — called after type checking, before codegen -//! 3. `on_bytecode` — called after codegen, before sealing -//! -//! # Writing a plugin -//! ```rust,ignore -//! use el_build::CompilerPlugin; -//! use el_parser::Program; -//! use el_types::TypeEnv; -//! -//! pub struct MyPlugin; -//! -//! impl CompilerPlugin for MyPlugin { -//! fn name(&self) -> &str { "my-plugin" } -//! fn version(&self) -> &str { "0.1.0" } -//! fn on_ast(&self, _program: &mut Program) -> Result<(), el_build::PluginError> { Ok(()) } -//! fn on_typed_ast(&self, _program: &Program, _types: &TypeEnv) -> Result<(), el_build::PluginError> { Ok(()) } -//! fn on_bytecode(&self, _bytecode: &mut Vec) -> Result<(), el_build::PluginError> { Ok(()) } -//! } -//! ``` - -use std::path::Path; - -use thiserror::Error; - -use el_manifest::Manifest; - -// ── Error ───────────────────────────────────────────────────────────────────── - -#[derive(Debug, Error)] -pub enum PluginError { - #[error("plugin '{name}' ast hook failed: {reason}")] - AstHookFailed { name: String, reason: String }, - - #[error("plugin '{name}' typed-ast hook failed: {reason}")] - TypedAstHookFailed { name: String, reason: String }, - - #[error("plugin '{name}' bytecode hook failed: {reason}")] - BytecodeHookFailed { name: String, reason: String }, - - #[error("plugin '{name}' not found in {dir}")] - NotFound { name: String, dir: String }, - - #[error("plugin loading is not supported on this platform")] - PlatformUnsupported, -} - -// ── Plugin trait ────────────────────────────────────────────────────────────── - -/// The interface that all compiler plugins must implement. -/// -/// Plugins receive three optional hooks during compilation. Each hook may -/// mutate the data it receives (AST, bytecode) or read it for analysis. -pub trait CompilerPlugin: Send + Sync { - /// The plugin's canonical name (matches its key in `manifest.el [plugins]`). - fn name(&self) -> &str; - - /// The plugin's version string. - fn version(&self) -> &str; - - /// Called after parsing, before type checking. - /// - /// Implementations may add synthetic AST nodes, remove nodes, or - /// record observations. Mutation is allowed. - fn on_ast(&self, program: &mut el_parser::Program) -> Result<(), PluginError>; - - /// Called after type checking, before code generation. - /// - /// The AST is immutable at this stage. Implementations may inspect the - /// resolved types for documentation generation, linting, etc. - fn on_typed_ast( - &self, - program: &el_parser::Program, - types: &el_types::TypeEnv, - ) -> Result<(), PluginError>; - - /// Called after code generation, before sealing. - /// - /// Implementations may inspect or transform the raw bytecode bytes. - fn on_bytecode(&self, bytecode: &mut Vec) -> Result<(), PluginError>; -} - -// ── Registry ────────────────────────────────────────────────────────────────── - -/// A registry of loaded compiler plugins. -pub struct PluginRegistry { - plugins: Vec>, -} - -impl PluginRegistry { - /// Create an empty registry. - pub fn new() -> Self { - Self { - plugins: Vec::new(), - } - } - - /// Register a plugin directly (used in tests and for built-in plugins). - pub fn register(&mut self, plugin: Box) { - self.plugins.push(plugin); - } - - /// Load all plugins listed in the manifest's `[plugins]` section. - /// - /// Plugins are expected to be `.dylib` (macOS) / `.so` (Linux) files - /// in `plugin_dir`. Dynamic loading is marked as a TODO — for now, this - /// is a no-op stub that validates the plugin manifest entries. - pub fn load_from_manifest( - &mut self, - manifest: &Manifest, - plugin_dir: &Path, - ) -> Result<(), PluginError> { - for name in manifest.plugins.keys() { - // TODO(LLVM backend): use `libloading` crate to dlopen the .dylib/.so, - // look up the `engram_plugin_init` symbol, call it, and register the - // returned Box. - // - // Extension point: - // let lib = unsafe { libloading::Library::new(dylib_path) }?; - // let init: Symbol Box> = - // unsafe { lib.get(b"engram_plugin_init") }?; - // self.plugins.push(init()); - - let dylib_name = if cfg!(target_os = "macos") { - format!("lib{name}.dylib") - } else if cfg!(target_os = "windows") { - format!("{name}.dll") - } else { - format!("lib{name}.so") - }; - - let dylib_path = plugin_dir.join(&dylib_name); - if !dylib_path.exists() { - // Not treating missing plugins as fatal during the stub phase. - // In production, this would be an error. - eprintln!( - "warning: plugin '{name}' not found at {} (dynamic loading is a TODO)", - dylib_path.display() - ); - } - } - Ok(()) - } - - /// Run the `on_ast` hook for all registered plugins. - pub fn run_ast_hooks(&self, program: &mut el_parser::Program) -> Result<(), PluginError> { - for plugin in &self.plugins { - plugin.on_ast(program)?; - } - Ok(()) - } - - /// Run the `on_typed_ast` hook for all registered plugins. - pub fn run_typed_hooks( - &self, - program: &el_parser::Program, - types: &el_types::TypeEnv, - ) -> Result<(), PluginError> { - for plugin in &self.plugins { - plugin.on_typed_ast(program, types)?; - } - Ok(()) - } - - /// Run the `on_bytecode` hook for all registered plugins. - pub fn run_bytecode_hooks(&self, bytecode: &mut Vec) -> Result<(), PluginError> { - for plugin in &self.plugins { - plugin.on_bytecode(bytecode)?; - } - Ok(()) - } - - /// Number of plugins currently registered. - pub fn len(&self) -> usize { - self.plugins.len() - } - - /// Whether no plugins are registered. - pub fn is_empty(&self) -> bool { - self.plugins.is_empty() - } -} - -impl Default for PluginRegistry { - fn default() -> Self { - Self::new() - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - /// A no-op test plugin. - struct NopPlugin; - - impl CompilerPlugin for NopPlugin { - fn name(&self) -> &str { "nop-plugin" } - fn version(&self) -> &str { "0.1.0" } - fn on_ast(&self, _program: &mut el_parser::Program) -> Result<(), PluginError> { Ok(()) } - fn on_typed_ast(&self, _p: &el_parser::Program, _t: &el_types::TypeEnv) -> Result<(), PluginError> { Ok(()) } - fn on_bytecode(&self, _b: &mut Vec) -> Result<(), PluginError> { Ok(()) } - } - - /// A plugin that appends a byte to the bytecode (to verify mutation). - struct MutatingPlugin; - - impl CompilerPlugin for MutatingPlugin { - fn name(&self) -> &str { "mutating-plugin" } - fn version(&self) -> &str { "1.0.0" } - fn on_ast(&self, _program: &mut el_parser::Program) -> Result<(), PluginError> { Ok(()) } - fn on_typed_ast(&self, _p: &el_parser::Program, _t: &el_types::TypeEnv) -> Result<(), PluginError> { Ok(()) } - fn on_bytecode(&self, bytecode: &mut Vec) -> Result<(), PluginError> { - bytecode.push(0xFF); // marker byte - Ok(()) - } - } - - #[test] - fn test_empty_registry() { - let reg = PluginRegistry::new(); - assert!(reg.is_empty()); - assert_eq!(reg.len(), 0); - } - - #[test] - fn test_register_plugin() { - let mut reg = PluginRegistry::new(); - reg.register(Box::new(NopPlugin)); - assert_eq!(reg.len(), 1); - assert!(!reg.is_empty()); - } - - #[test] - fn test_bytecode_hook_mutates() { - let mut reg = PluginRegistry::new(); - reg.register(Box::new(MutatingPlugin)); - - let mut bytecode = vec![0x01, 0x02, 0x03]; - reg.run_bytecode_hooks(&mut bytecode).unwrap(); - assert_eq!(bytecode.last(), Some(&0xFF)); - assert_eq!(bytecode.len(), 4); - } - - #[test] - fn test_multiple_plugins_run_in_order() { - let mut reg = PluginRegistry::new(); - reg.register(Box::new(MutatingPlugin)); - reg.register(Box::new(MutatingPlugin)); - - let mut bytecode = vec![0x01]; - reg.run_bytecode_hooks(&mut bytecode).unwrap(); - // Two MutatingPlugins → two 0xFF bytes appended - assert_eq!(bytecode, vec![0x01, 0xFF, 0xFF]); - } - - #[test] - fn test_nop_plugin_hooks_succeed() { - let mut reg = PluginRegistry::new(); - reg.register(Box::new(NopPlugin)); - - let mut bytecode = vec![0x00]; - assert!(reg.run_bytecode_hooks(&mut bytecode).is_ok()); - assert_eq!(bytecode.len(), 1); // NopPlugin does not mutate - } -} diff --git a/_archive/rust-bootstrap/engrams/el-build/src/test_runner.rs b/_archive/rust-bootstrap/engrams/el-build/src/test_runner.rs deleted file mode 100644 index 689d920..0000000 --- a/_archive/rust-bootstrap/engrams/el-build/src/test_runner.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! Test runner — compiles and runs `*.test.el` / `*_test.el` files. - -use std::path::Path; - -use crate::build::TestReport; -use crate::error::BuildResult; - -/// Discover and run all test files under `src_root`. -/// -/// Test files must end in `.test.el` or `_test.el`. -/// This is a stub implementation — in the full toolchain the test files -/// are compiled to debug bytecode and run against the interpreter with -/// assertions captured. -pub async fn run_tests(src_root: &Path, workspace_root: &Path) -> BuildResult { - let test_files = discover_test_files(src_root); - let total = test_files.len(); - let mut passed = 0usize; - let mut failed = 0usize; - let mut failures = Vec::new(); - - for file in &test_files { - let rel = file - .strip_prefix(workspace_root) - .unwrap_or(file) - .display() - .to_string(); - - let source = match std::fs::read_to_string(file) { - Ok(s) => s, - Err(e) => { - failures.push(format!("{rel}: io error: {e}")); - failed += 1; - continue; - } - }; - - // Compile to debug bytecode — if compilation fails, test fails. - let opts = el_compiler::CompilerOptions { - target: el_compiler::Target::Debug, - source_path: file.clone(), - ..Default::default() - }; - - match el_compiler::Compiler::compile(&source, opts) { - Ok(output) => { - if output.diagnostics.iter().any(|d| d.contains("error")) { - failures.push(format!("{rel}: compile error")); - failed += 1; - } else { - passed += 1; - } - } - Err(e) => { - failures.push(format!("{rel}: {e}")); - failed += 1; - } - } - } - - Ok(TestReport { - total, - passed, - failed, - failures, - }) -} - -fn discover_test_files(root: &Path) -> Vec { - let mut files = Vec::new(); - collect_test_files(root, &mut files); - files.sort(); - files -} - -fn collect_test_files(dir: &Path, out: &mut Vec) { - let Ok(entries) = std::fs::read_dir(dir) else { - return; - }; - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - let name = path.file_name().unwrap_or_default().to_string_lossy(); - if !name.starts_with('.') && name != "dist" && name != "target" { - collect_test_files(&path, out); - } - } else { - let name = path.file_name().unwrap_or_default().to_string_lossy(); - if (name.ends_with(".test.el") || name.ends_with("_test.el")) - && path.extension().map(|e| e == "el").unwrap_or(false) - { - out.push(path); - } - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-compiler/Cargo.toml b/_archive/rust-bootstrap/engrams/el-compiler/Cargo.toml deleted file mode 100644 index 64440ae..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "el-compiler" -description = "Engram language compilation pipeline (debug / release / prod)" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-lexer = { workspace = true } -el-parser = { workspace = true } -el-types = { workspace = true } -el-seal = { workspace = true } -thiserror = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-compiler/src/bytecode.rs b/_archive/rust-bootstrap/engrams/el-compiler/src/bytecode.rs deleted file mode 100644 index 6eeb81f..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/src/bytecode.rs +++ /dev/null @@ -1,302 +0,0 @@ -//! Bytecode instruction set for the Engram virtual machine. -//! -//! The VM is a simple stack machine. Every instruction pops its operands -//! from the stack and pushes its result. Control flow uses relative signed -//! offsets from the instruction *after* the jump. - -use serde::{Deserialize, Serialize}; - -/// A runtime value on the VM stack. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Value { - Int(i64), - Float(f64), - Str(String), - Bool(bool), - Nil, - /// A list of values (used for `activate` results and array literals). - List(Vec), - /// A key-value map — used for Map literals. - /// Stored as a Vec of pairs to keep ordering and remain Serialize-friendly. - Map(Vec<(String, Value)>), - /// A Result value — Ok variant. - ResultOk(Box), - /// A Result value — Err variant. - ResultErr(Box), - /// A struct instance: type name + ordered field name-value pairs. - Struct { type_name: String, fields: Vec<(String, Value)> }, -} - -impl std::fmt::Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Int(n) => write!(f, "{n}"), - Value::Float(n) => write!(f, "{n}"), - Value::Str(s) => write!(f, "{s}"), - Value::Bool(b) => write!(f, "{b}"), - Value::Nil => write!(f, "nil"), - Value::List(vs) => { - let items: Vec<_> = vs.iter().map(|v| v.to_string()).collect(); - write!(f, "[{}]", items.join(", ")) - } - Value::Map(pairs) => { - let items: Vec<_> = pairs.iter().map(|(k, v)| format!("{k}: {v}")).collect(); - write!(f, "{{{}}}", items.join(", ")) - } - Value::ResultOk(v) => write!(f, "Ok({v})"), - Value::ResultErr(e) => write!(f, "Err({e})"), - Value::Struct { type_name, fields } => { - let fs: Vec<_> = fields.iter().map(|(k, v)| format!("{k}: {v}")).collect(); - write!(f, "{type_name} {{ {} }}", fs.join(", ")) - } - } - } -} - -/// A single VM instruction. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Bytecode { - // ── Stack ───────────────────────────────────────────────────────────────── - /// Push a constant value onto the stack. - Push(Value), - /// Discard the top of stack. - Pop, - /// Duplicate the top of stack. - Dup, - - // ── Arithmetic ──────────────────────────────────────────────────────────── - Add, - Sub, - Mul, - Div, - Mod, - BitAnd, - BitOr, - BitXor, - BitNot, - Shl, - Shr, - - // ── Comparison ──────────────────────────────────────────────────────────── - Eq, - NotEq, - Lt, - Gt, - LtEq, - GtEq, - - // ── Logical ─────────────────────────────────────────────────────────────── - And, - Or, - Not, - - // ── Locals ─────────────────────────────────────────────────────────────── - /// Load a local variable by name. - LoadLocal(String), - /// Store the top of stack into a local variable. - StoreLocal(String), - - // ── Functions ───────────────────────────────────────────────────────────── - /// Call a function by name with `arity` arguments. - Call { name: String, arity: u32 }, - /// Return from the current function (leaves return value on stack). - Return, - - // ── Control flow ────────────────────────────────────────────────────────── - /// Unconditional jump: `ip += offset` (offset is from the *next* instruction). - Jump(i32), - /// Jump if the top of stack is truthy; pops the value. - JumpIf(i32), - /// Jump if the top of stack is falsy; pops the value. - JumpIfNot(i32), - - // ── Fields & Indexing ───────────────────────────────────────────────────── - /// Load a named field from the struct on top of stack. - GetField(String), - /// Index into an array: pops index then array. - GetIndex, - /// Build a Map from the top N key-value pairs on the stack - /// (keys are strings pushed as Str, values follow each key). - BuildMap(u32), - /// Build a list from the top N items on the stack. - BuildList(u32), - /// Build a struct instance: pop N field values (named by fields in order), push Map. - BuildStruct { type_name: String, fields: Vec }, - /// Set a field on the Map on top of stack. - SetField(String), - - // ── Special ─────────────────────────────────────────────────────────────── - /// `activate TypeName "query"` — emit a semantic query stub. - /// In a full implementation this would call into the Engram runtime. - Activate { type_name: String, query: String }, - /// Mark the start of a sealed section (the runtime enforces protection). - SealedBegin, - /// Mark the end of a sealed section. - SealedEnd, - /// No-op — used as a placeholder for forward jumps. - Nop, - /// Halt the VM. - Halt, - /// `reason "query"` — call soma AI inference endpoint. - Reason { query: String }, - /// `parallel { name: expr, ... }` — spawn entries concurrently. - /// Each entry is a (name, entry_ip) pair where entry_ip is the bytecode offset. - Parallel { entries: Vec<(String, usize)> }, - /// Begin a trace region (debug mode: record start time). - TraceBegin { label: String }, - /// End a trace region (debug mode: print elapsed). - TraceEnd { label: String }, - /// Contract check: if top of stack is falsy, panic with message. - ContractCheck { message: String }, - /// Deploy: POST to soma deployment API. - DeployFn { fn_name: String, route: String, target: String }, -} - -impl std::fmt::Display for Bytecode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Bytecode::Push(v) => write!(f, "PUSH {v}"), - Bytecode::Pop => write!(f, "POP"), - Bytecode::Dup => write!(f, "DUP"), - Bytecode::Add => write!(f, "ADD"), - Bytecode::Sub => write!(f, "SUB"), - Bytecode::Mul => write!(f, "MUL"), - Bytecode::Div => write!(f, "DIV"), - Bytecode::Mod => write!(f, "MOD"), - Bytecode::BitAnd => write!(f, "BITAND"), - Bytecode::BitOr => write!(f, "BITOR"), - Bytecode::BitXor => write!(f, "BITXOR"), - Bytecode::BitNot => write!(f, "BITNOT"), - Bytecode::Shl => write!(f, "SHL"), - Bytecode::Shr => write!(f, "SHR"), - Bytecode::Eq => write!(f, "EQ"), - Bytecode::NotEq => write!(f, "NEQ"), - Bytecode::Lt => write!(f, "LT"), - Bytecode::Gt => write!(f, "GT"), - Bytecode::LtEq => write!(f, "LTE"), - Bytecode::GtEq => write!(f, "GTE"), - Bytecode::And => write!(f, "AND"), - Bytecode::Or => write!(f, "OR"), - Bytecode::Not => write!(f, "NOT"), - Bytecode::LoadLocal(n) => write!(f, "LOAD {n}"), - Bytecode::StoreLocal(n) => write!(f, "STORE {n}"), - Bytecode::Call { name, arity } => write!(f, "CALL {name}/{arity}"), - Bytecode::Return => write!(f, "RETURN"), - Bytecode::Jump(off) => write!(f, "JUMP {off:+}"), - Bytecode::JumpIf(off) => write!(f, "JUMPIF {off:+}"), - Bytecode::JumpIfNot(off) => write!(f, "JUMPIFNOT {off:+}"), - Bytecode::GetField(n) => write!(f, "GETFIELD {n}"), - Bytecode::GetIndex => write!(f, "GETINDEX"), - Bytecode::BuildMap(n) => write!(f, "BUILDMAP {n}"), - Bytecode::BuildList(n) => write!(f, "BUILDLIST {n}"), - Bytecode::BuildStruct { type_name, fields } => { - write!(f, "BUILDSTRUCT {type_name} [{}]", fields.join(", ")) - } - Bytecode::SetField(n) => write!(f, "SETFIELD {n}"), - Bytecode::Activate { type_name, query } => { - write!(f, "ACTIVATE {type_name} \"{query}\"") - } - Bytecode::SealedBegin => write!(f, "SEALED_BEGIN"), - Bytecode::SealedEnd => write!(f, "SEALED_END"), - Bytecode::Nop => write!(f, "NOP"), - Bytecode::Halt => write!(f, "HALT"), - Bytecode::Reason { query } => write!(f, "REASON \"{query}\""), - Bytecode::Parallel { entries } => { - let names: Vec<_> = entries.iter().map(|(n, ip)| format!("{n}@{ip}")).collect(); - write!(f, "PARALLEL [{}]", names.join(", ")) - } - Bytecode::TraceBegin { label } => write!(f, "TRACE_BEGIN \"{label}\""), - Bytecode::TraceEnd { label } => write!(f, "TRACE_END \"{label}\""), - Bytecode::ContractCheck { message } => write!(f, "CONTRACT_CHECK \"{message}\""), - Bytecode::DeployFn { fn_name, route, target } => { - write!(f, "DEPLOY {fn_name} -> {route} via {target}") - } - } - } -} - -/// Serialize bytecode instructions to bytes for storage/sealing. -pub fn serialize_bytecode(instructions: &[Bytecode]) -> Result, String> { - serde_json::to_vec(instructions).map_err(|e| e.to_string()) -} - -/// Deserialize bytecode instructions from bytes. -pub fn deserialize_bytecode(bytes: &[u8]) -> Result, String> { - serde_json::from_slice(bytes).map_err(|e| e.to_string()) -} - -// ── ELVM binary container format ────────────────────────────────────────────── -// -// Header: -// magic: [u8; 4] = b"ELVM" -// version: [u8; 4] (little-endian u32, currently 1) -// length: [u8; 8] (little-endian u64, byte length of the payload that follows) -// Payload: -// JSON-serialized Vec (same encoding as serialize_bytecode) -// -// The JSON payload is intentionally kept so that existing tooling can inspect -// .elc files by stripping the 16-byte header. Future versions may switch to a -// denser binary encoding while bumping the version field. - -/// Magic bytes that identify an El bytecode container. -pub const ELVM_MAGIC: &[u8; 4] = b"ELVM"; -/// Current container format version. -pub const ELVM_VERSION: u32 = 1; -/// Total header size in bytes (magic + version + length). -pub const ELVM_HEADER_SIZE: usize = 16; - -/// Wrap serialized bytecode in the ELVM binary container. -/// -/// Returns the full `.elc` file bytes: 16-byte header + JSON payload. -pub fn wrap_elvm(instructions: &[Bytecode]) -> Result, String> { - let payload = serialize_bytecode(instructions)?; - let length = payload.len() as u64; - - let mut out = Vec::with_capacity(ELVM_HEADER_SIZE + payload.len()); - out.extend_from_slice(ELVM_MAGIC); - out.extend_from_slice(&ELVM_VERSION.to_le_bytes()); - out.extend_from_slice(&length.to_le_bytes()); - out.extend_from_slice(&payload); - Ok(out) -} - -/// Unwrap and deserialize an ELVM binary container. -/// -/// Accepts either: -/// - A full ELVM container (starts with `ELVM` magic), or -/// - Raw JSON bytes (legacy `.elc` format — no header). -pub fn unwrap_elvm(bytes: &[u8]) -> Result, String> { - if bytes.starts_with(ELVM_MAGIC) { - // Binary container: skip header, deserialize payload. - if bytes.len() < ELVM_HEADER_SIZE { - return Err("truncated ELVM header".to_string()); - } - let version = u32::from_le_bytes( - bytes[4..8].try_into().map_err(|_| "bad version field".to_string())? - ); - if version != ELVM_VERSION { - return Err(format!("unsupported ELVM version {version} (expected {ELVM_VERSION})")); - } - let length = u64::from_le_bytes( - bytes[8..16].try_into().map_err(|_| "bad length field".to_string())? - ) as usize; - let payload_end = ELVM_HEADER_SIZE + length; - if bytes.len() < payload_end { - return Err(format!( - "truncated ELVM payload: expected {length} bytes, got {}", - bytes.len().saturating_sub(ELVM_HEADER_SIZE) - )); - } - deserialize_bytecode(&bytes[ELVM_HEADER_SIZE..payload_end]) - } else { - // Legacy JSON-only format (no header). - deserialize_bytecode(bytes) - } -} - -impl Bytecode { - /// Deserialize a bytecode slice — handles both ELVM container and raw JSON. - pub fn deserialize_all(bytes: &[u8]) -> Result, String> { - unwrap_elvm(bytes) - } -} diff --git a/_archive/rust-bootstrap/engrams/el-compiler/src/codegen.rs b/_archive/rust-bootstrap/engrams/el-compiler/src/codegen.rs deleted file mode 100644 index 90445ea..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/src/codegen.rs +++ /dev/null @@ -1,736 +0,0 @@ -//! Code generator: walks the AST and emits bytecode instructions. - -use el_parser::{BinOp, Expr, JsxAttrValue, Literal, Program, Stmt}; - -use crate::bytecode::{Bytecode, Value}; -use crate::error::CompileResult; -use crate::source_map::SourceMap; - -/// Generates bytecode from a parsed program. -pub struct Codegen { - instructions: Vec, - source_map: SourceMap, - #[allow(dead_code)] - emit_source_map: bool, -} - -impl Codegen { - pub fn new(emit_source_map: bool) -> Self { - Self { - instructions: Vec::new(), - source_map: SourceMap::new(), - emit_source_map, - } - } - - /// Generate bytecode for a complete program. - pub fn generate(mut self, program: &Program) -> CompileResult<(Vec, SourceMap)> { - for stmt in &program.stmts { - self.gen_stmt(stmt)?; - } - self.emit(Bytecode::Halt); - Ok((self.instructions, self.source_map)) - } - - // ── Emission helpers ────────────────────────────────────────────────────── - - fn emit(&mut self, instr: Bytecode) -> usize { - let idx = self.instructions.len(); - self.instructions.push(instr); - idx - } - - #[allow(dead_code)] - fn emit_at_span(&mut self, instr: Bytecode, span: el_lexer::Span) -> usize { - let idx = self.instructions.len(); - if self.emit_source_map { - self.source_map.record(idx, span); - } - self.instructions.push(instr); - idx - } - - fn patch_jump(&mut self, idx: usize, target: usize) { - // offset = target - (idx + 1) (jump is relative to the next instruction) - let offset = target as i32 - (idx as i32 + 1); - match &mut self.instructions[idx] { - Bytecode::Jump(o) | Bytecode::JumpIf(o) | Bytecode::JumpIfNot(o) => *o = offset, - _ => {} - } - } - - fn current_idx(&self) -> usize { - self.instructions.len() - } - - // ── 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)?; - // If-without-else leaves nothing on stack (it pops internally); - // push Nil so we always have exactly one return value. - if matches!(expr, Expr::If { else_: None, .. }) { - self.emit(Bytecode::Push(Value::Nil)); - } - } - 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<()> { - // Record the source span for this statement in the source map - if self.emit_source_map { - let span = stmt_span(stmt); - let idx = self.instructions.len(); - self.source_map.record(idx, span); - } - match stmt { - Stmt::Let { name, value, .. } => { - self.gen_expr(value)?; - self.emit(Bytecode::StoreLocal(name.clone())); - } - Stmt::Return(expr, _) => { - self.gen_expr(expr)?; - self.emit(Bytecode::Return); - } - Stmt::Expr(expr, _) => { - self.gen_expr(expr)?; - // Always discard the expression result in statement position. - // Expr::Block is NOT special-cased: even a block expression used - // as a statement should have its result discarded. - // Note: Expr::If { else_: Some(_) } leaves a value on stack from - // both branches (via gen_stmt_tail on the last block statement), - // so it must be popped here too. - // If-without-else already pops internally (see gen_expr for If), - // so we only skip the extra Pop for that case. - let needs_pop = match expr { - Expr::If { else_: None, .. } => false, // already handled internally - _ => true, - }; - if needs_pop { - self.emit(Bytecode::Pop); - } - } - Stmt::FnDef { name, params, body, requires, .. } => { - // In this simple bytecode model, function defs emit a Jump to skip - // the function body, then a label for the function start. - // A full implementation would use a call frame table; for now we - // emit the body inline and register the entry point offset. - let skip_jump = self.emit(Bytecode::Jump(0)); // patched below - - // Function body - // Bind parameters in order (caller pushes args left-to-right) - for param in params.iter().rev() { - self.emit(Bytecode::StoreLocal(param.name.clone())); - } - // Emit contract check if `requires` is present - if let Some(req_expr) = requires { - self.gen_expr(req_expr)?; - self.emit(Bytecode::ContractCheck { - message: format!("contract violation in fn '{name}': requires clause failed"), - }); - } - // All statements except the last use gen_stmt (which pops expr results). - // The last statement uses gen_stmt_tail so that the final expression - // value stays on the stack as the function's implicit return value. - let body_len = body.len(); - for (i, s) in body.iter().enumerate() { - if i + 1 == body_len { - self.gen_stmt_tail(s)?; - } else { - self.gen_stmt(s)?; - } - } - // If the body is empty, return Nil. - if body_len == 0 { - self.emit(Bytecode::Push(Value::Nil)); - } - self.emit(Bytecode::Return); - - // Patch the skip jump - let after = self.current_idx(); - self.patch_jump(skip_jump, after); - - // Register the function name → bytecode offset mapping - // (stored as a load of the entry point index as an Int constant, - // then store as a local — real implementations use a function table) - let entry_point = skip_jump + 1; // first instruction of body - self.emit(Bytecode::Push(Value::Int(entry_point as i64))); - self.emit(Bytecode::StoreLocal(format!("__fn_{name}"))); - } - Stmt::While { condition, body, .. } => { - // Codegen for `while { }`: - // loop_start: - // [condition] - // JumpIfNot(done) - // [body] - // Jump(loop_start) - // done: - let loop_start = self.current_idx(); - self.gen_expr(condition)?; - let to_done = self.emit(Bytecode::JumpIfNot(0)); // patched to done - for s in body { - self.gen_stmt(s)?; - } - let back_jump = self.emit(Bytecode::Jump(0)); // patched to loop_start - let done = self.current_idx(); - self.patch_jump(to_done, done); - self.patch_jump(back_jump, loop_start); - } - Stmt::Retry { count, body, fallback, .. } => { - // Codegen for retry N times: - // counter = N - // loop_start: - // if counter <= 0 goto fallback - // decrement counter - // [body] - // goto done - // fallback: - // [fallback_body] - // done: - let counter_name = format!("__retry_counter_{}__", self.current_idx()); - - // Initialize counter - self.gen_expr(count)?; - self.emit(Bytecode::StoreLocal(counter_name.clone())); - - // Loop start: check counter > 0 - let loop_start = self.current_idx(); - self.emit(Bytecode::LoadLocal(counter_name.clone())); - self.emit(Bytecode::Push(Value::Int(0))); - self.emit(Bytecode::Gt); - let to_fallback = self.emit(Bytecode::JumpIfNot(0)); // patched to fallback - - // Decrement counter - self.emit(Bytecode::LoadLocal(counter_name.clone())); - self.emit(Bytecode::Push(Value::Int(1))); - self.emit(Bytecode::Sub); - self.emit(Bytecode::StoreLocal(counter_name.clone())); - - // Execute body - for s in body { - self.gen_stmt(s)?; - } - // Body succeeded — jump to done - let to_done = self.emit(Bytecode::Jump(0)); - - // Fallback - let fallback_start = self.current_idx(); - self.patch_jump(to_fallback, fallback_start); - if let Some(fb_body) = fallback { - for s in fb_body { - self.gen_stmt(s)?; - } - } - - let done = self.current_idx(); - self.patch_jump(to_done, done); - - // Note: in this simple model the body always "succeeds". - // A real retry would need exception-like control flow. - // For the retry-loop semantic, also add a back-jump that - // jumps back to loop_start after each body execution would - // require adding another jump before `to_done`. This design - // runs the body once then exits — which is correct for - // "success on first try" semantics in a pure-fn language. - } - Stmt::Deploy { fn_name, route, target, .. } => { - self.emit(Bytecode::DeployFn { - fn_name: fn_name.clone(), - route: route.clone(), - target: target.clone(), - }); - } - Stmt::TypeDef { .. } | Stmt::EnumDef { .. } => { - // Type and enum definitions are compile-time only; no runtime code. - } - // Test-related statements — skipped during normal compilation. - // The el-test crate walks the AST directly rather than running compiled bytecode. - Stmt::TestDef { .. } | Stmt::Seed(..) | Stmt::Assert(..) => {} - // Component definition: generate a function that renders the component. - // The function is registered under "__component_" and the template - // is emitted as a nested function body. - Stmt::ComponentDef { name, state, methods, template, .. } => { - let fn_name = format!("__component_{name}"); - let skip_jump = self.emit(Bytecode::Jump(0)); - - // Emit component methods (each has its own Jump/body/Register pattern) - for m in methods { - self.gen_stmt(m)?; - } - - // Record where the template starts — AFTER all method bodies. - // This is the real entry point for calling this component. - let template_start = self.current_idx(); - - // Initialise state fields as locals (so template expressions can load them) - for field in state { - if let Some(default) = &field.default { - self.gen_expr(default)?; - } else { - self.emit(Bytecode::Push(Value::Nil)); - } - self.emit(Bytecode::StoreLocal(field.name.clone())); - } - - // Emit template as the return value - self.gen_expr(template)?; - self.emit(Bytecode::Return); - - let after = self.current_idx(); - self.patch_jump(skip_jump, after); - - // entry_point is the first instruction of the template body. - let entry_point = template_start; - self.emit(Bytecode::Push(Value::Int(entry_point as i64))); - self.emit(Bytecode::StoreLocal(format!("__fn_{fn_name}"))); - - // Also register the component name directly so `` works - self.emit(Bytecode::Push(Value::Int(entry_point as i64))); - self.emit(Bytecode::StoreLocal(format!("__fn_{name}"))); - } - // New statement kinds — no runtime code emitted. - _ => {} - } - Ok(()) - } - - // ── Expression code generation ──────────────────────────────────────────── - - fn gen_expr(&mut self, expr: &Expr) -> CompileResult<()> { - match expr { - Expr::Literal(lit) => { - let val = match lit { - Literal::Int(n) => Value::Int(*n), - Literal::Float(f) => Value::Float(*f), - Literal::Str(s) => Value::Str(s.clone()), - Literal::Bool(b) => Value::Bool(*b), - }; - self.emit(Bytecode::Push(val)); - } - Expr::Ident(name) => { - self.emit(Bytecode::LoadLocal(name.clone())); - } - Expr::BinOp { op, left, right } => { - // NullCoalesce (`a ?? b`) is short-circuit: if `a` is truthy, use it; else `b`. - // Bytecode: eval a, Dup, JumpIf(skip), Pop, eval b, skip: - if matches!(op, BinOp::NullCoalesce) { - self.gen_expr(left)?; - self.emit(Bytecode::Dup); - let skip = self.emit(Bytecode::JumpIf(0)); // patched below - self.emit(Bytecode::Pop); // discard the falsy `a` - self.gen_expr(right)?; - let after = self.current_idx(); - self.patch_jump(skip, after); - return Ok(()); - } - self.gen_expr(left)?; - self.gen_expr(right)?; - let instr = match op { - BinOp::Add => Bytecode::Add, - BinOp::Sub => Bytecode::Sub, - BinOp::Mul => Bytecode::Mul, - BinOp::Div => Bytecode::Div, - BinOp::Eq => Bytecode::Eq, - BinOp::NotEq => Bytecode::NotEq, - BinOp::Lt => Bytecode::Lt, - BinOp::Gt => Bytecode::Gt, - BinOp::LtEq => Bytecode::LtEq, - BinOp::GtEq => Bytecode::GtEq, - BinOp::And => Bytecode::And, - BinOp::Or => Bytecode::Or, - BinOp::Mod => Bytecode::Mod, - BinOp::BitAnd => Bytecode::BitAnd, - BinOp::BitOr => Bytecode::BitOr, - BinOp::BitXor => Bytecode::BitXor, - BinOp::Shl => Bytecode::Shl, - BinOp::Shr => Bytecode::Shr, - BinOp::NullCoalesce => unreachable!("handled above"), - }; - self.emit(instr); - } - Expr::UnaryNot(inner) => { - self.gen_expr(inner)?; - self.emit(Bytecode::Not); - } - Expr::UnaryBitNot(inner) => { - self.gen_expr(inner)?; - self.emit(Bytecode::BitNot); - } - Expr::Call { func, args } => { - // Push arguments left-to-right - for arg in args { - self.gen_expr(arg)?; - } - // Get the function name from the callee expression - let fn_name = match func.as_ref() { - Expr::Ident(n) => { - // Strip leading `@` from syscall/builtin names: - // `@http_get` → `http_get`, `@json_parse` → `json_parse` - n.strip_prefix('@').unwrap_or(n).to_string() - } - Expr::Field { object, field } => { - self.gen_expr(object)?; - field.clone() - } - _ => { - self.gen_expr(func)?; - "__dynamic__".to_string() - } - }; - self.emit(Bytecode::Call { name: fn_name, arity: args.len() as u32 }); - } - Expr::Block(stmts) => { - if stmts.is_empty() { - 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_ } => { - self.gen_expr(cond)?; - let jump_false = self.emit(Bytecode::JumpIfNot(0)); // patched - self.gen_expr(then)?; - if let Some(else_expr) = else_ { - let jump_end = self.emit(Bytecode::Jump(0)); // skip else - let else_start = self.current_idx(); - self.patch_jump(jump_false, else_start); - self.gen_expr(else_expr)?; - let after_else = self.current_idx(); - self.patch_jump(jump_end, after_else); - } else { - // No else branch: if-without-else is a statement, not a value - // expression. Discard the then-block's pushed value so both - // paths leave the stack at the same height (net zero change). - self.emit(Bytecode::Pop); - let after_pop = self.current_idx(); - self.patch_jump(jump_false, after_pop); // false path skips Pop too - } - } - Expr::Match { subject, arms } => { - self.gen_expr(subject)?; - // Simplified match: for each arm, dup subject, push pattern, - // compare, branch. A full implementation would use a jump table. - let mut end_jumps = Vec::new(); - for arm in arms { - match &arm.pattern { - el_parser::Pattern::Wildcard => { - // Wildcard always matches — pop subject and run body directly. - self.emit(Bytecode::Pop); - self.gen_expr(&arm.body)?; - end_jumps.push(self.emit(Bytecode::Jump(0))); - // No jump_no_match needed — wildcard always matches. - // But we still need to patch the end jumps at the end. - // Nothing else to do; break out of the loop since wildcard - // is a catch-all and subsequent arms are unreachable. - break; - } - el_parser::Pattern::Binding(name) => { - // Bind and always match. - self.emit(Bytecode::Dup); - self.emit(Bytecode::StoreLocal(name.clone())); - // 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); - } - _ => { - self.emit(Bytecode::Dup); - // 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); - } - } - } - // Default fallthrough: pop subject, push nil - self.emit(Bytecode::Pop); - self.emit(Bytecode::Push(Value::Nil)); - let end = self.current_idx(); - for j in end_jumps { - self.patch_jump(j, end); - } - } - Expr::Activate { type_name, query } => { - self.emit(Bytecode::Activate { - type_name: type_name.clone(), - query: query.clone(), - }); - } - Expr::Sealed(stmts) => { - self.emit(Bytecode::SealedBegin); - for s in stmts { - self.gen_stmt(s)?; - } - self.emit(Bytecode::SealedEnd); - self.emit(Bytecode::Push(Value::Nil)); - } - Expr::Field { object, field } => { - self.gen_expr(object)?; - self.emit(Bytecode::GetField(field.clone())); - } - Expr::Array(elems) => { - // Push each element onto the stack, then collect with BuildList. - for e in elems { - self.gen_expr(e)?; - } - self.emit(Bytecode::BuildList(elems.len() as u32)); - } - Expr::Path { segments } => { - // Emit the last segment as a string value (enum variant reference) - let variant = segments.last().cloned().unwrap_or_default(); - self.emit(Bytecode::Push(Value::Str(variant))); - } - Expr::Index { object, index } => { - self.gen_expr(object)?; - self.gen_expr(index)?; - self.emit(Bytecode::GetIndex); - } - Expr::StructLit { type_name, fields, .. } => { - // Push each field value in declaration order - for (_, field_expr) in fields { - self.gen_expr(field_expr)?; - } - let field_names: Vec = fields.iter().map(|(n, _)| n.clone()).collect(); - self.emit(Bytecode::BuildStruct { - type_name: type_name.clone(), - fields: field_names, - }); - } - Expr::MapLiteral(pairs) => { - // Push each key-value pair, then collect with BuildMap. - let n = pairs.len() as u32; - for (key_expr, val_expr) in pairs { - self.gen_expr(key_expr)?; - self.gen_expr(val_expr)?; - } - self.emit(Bytecode::BuildMap(n)); - } - Expr::With { base, updates } => { - // Generate base struct clone then apply updates - self.gen_expr(base)?; - for (field, val_expr) in updates { - self.gen_expr(val_expr)?; - self.emit(Bytecode::SetField(field.clone())); - } - } - Expr::Reason { query } => { - self.emit(Bytecode::Reason { query: query.clone() }); - } - Expr::Parallel { entries } => { - // For parallel, emit each expression sequentially and collect into a Map - // A full implementation would use threads; here we collect results into a Map - let n = entries.len() as u32; - for (name, expr) in entries { - self.emit(Bytecode::Push(Value::Str(name.clone()))); - self.gen_expr(expr)?; - } - self.emit(Bytecode::BuildMap(n)); - } - Expr::Trace { label, body } => { - self.emit(Bytecode::TraceBegin { label: label.clone() }); - for s in body { - self.gen_stmt(s)?; - } - self.emit(Bytecode::TraceEnd { label: label.clone() }); - self.emit(Bytecode::Push(Value::Nil)); - } - // JSX element: push tag name, attrs (as a map), and children list, then call __jsx__ - Expr::JsxElement { tag, attrs, children, .. } => { - // If the tag starts with an uppercase letter it's a component reference. - // Call the component function directly (it takes no args, returns HTML string). - let is_component = tag.chars().next().map_or(false, |c| c.is_uppercase()); - - if is_component { - // Call the component as a function: Call { name: tag, arity: 0 } - // The component will render itself and return an HTML string. - self.emit(Bytecode::Call { name: tag.clone(), arity: 0 }); - } else { - // Push tag name - self.emit(Bytecode::Push(Value::Str(tag.clone()))); - // Build attrs map: push key-value pairs - let n_attrs = attrs.len() as u32; - for (attr_name, attr_val) in attrs { - self.emit(Bytecode::Push(Value::Str(attr_name.clone()))); - match attr_val { - JsxAttrValue::Str(s) => { - self.emit(Bytecode::Push(Value::Str(s.clone()))); - } - JsxAttrValue::Expr(expr) => { - self.gen_expr(expr)?; - } - } - } - self.emit(Bytecode::BuildMap(n_attrs)); - // Build children list - for child in children { - self.gen_expr(child)?; - } - self.emit(Bytecode::BuildList(children.len() as u32)); - // Call __jsx__(tag, attrs, children) - self.emit(Bytecode::Call { name: "__jsx__".to_string(), arity: 3 }); - } - } - // JSX expression interpolation: just evaluate the inner expression - Expr::JsxExpr(inner) => { - self.gen_expr(inner)?; - } - // JSX text: push as string - Expr::JsxText(text) => { - self.emit(Bytecode::Push(Value::Str(text.clone()))); - } - // Closure: compile as an inline function and push a reference to it. - // The closure body is emitted as a skip-over block. - Expr::Closure { params, body, .. } => { - let closure_id = self.current_idx(); - let fn_name = format!("__closure_{closure_id}__"); - let skip_jump = self.emit(Bytecode::Jump(0)); - - // Bind params in reverse order (stack has args pushed left-to-right) - for param in params.iter().rev() { - self.emit(Bytecode::StoreLocal(param.name.clone())); - } - self.gen_expr(body)?; - self.emit(Bytecode::Return); - - let after = self.current_idx(); - self.patch_jump(skip_jump, after); - - let entry_point = skip_jump + 1; - // Register the closure function - self.emit(Bytecode::Push(Value::Int(entry_point as i64))); - self.emit(Bytecode::StoreLocal(format!("__fn_{fn_name}"))); - - // Push a reference to this closure (as its function name) - self.emit(Bytecode::Push(Value::Str(fn_name))); - } - // New expression kinds — push Nil as placeholder - _ => { - self.emit(Bytecode::Push(Value::Nil)); - } - } - Ok(()) - } -} - -// ── Helper: extract a representative span from a statement ──────────────────── - -fn stmt_span(stmt: &Stmt) -> el_lexer::Span { - match stmt { - Stmt::Let { span, .. } - | Stmt::Return(_, span) - | Stmt::Expr(_, span) - | Stmt::FnDef { span, .. } - | Stmt::TypeDef { span, .. } - | Stmt::EnumDef { span, .. } - | Stmt::TestDef { span, .. } - | Stmt::Seed(_, span) - | Stmt::Assert(_, span) => *span, - _ => el_lexer::Span::new(0, 0, 0, 0), - } -} - -#[cfg(test)] -mod tests { - use el_lexer::tokenize; - use el_parser::parse; - use super::*; - - fn gen(src: &str) -> Vec { - let tokens = tokenize(src).unwrap(); - let prog = parse(tokens, src.to_string()).unwrap(); - let cg = Codegen::new(false); - let (bc, _) = cg.generate(&prog).unwrap(); - bc - } - - #[test] - fn test_push_int() { - let bc = gen("42"); - assert!(matches!(&bc[0], Bytecode::Push(Value::Int(42)))); - } - - #[test] - fn test_let_store() { - let bc = gen("let x = 1"); - assert!(matches!(&bc[1], Bytecode::StoreLocal(n) if n == "x")); - } - - #[test] - fn test_add() { - let bc = gen("1 + 2"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Add))); - } - - #[test] - fn test_halt_at_end() { - let bc = gen("42"); - assert!(matches!(bc.last(), Some(Bytecode::Halt))); - } - - #[test] - fn test_activate_emitted() { - let bc = gen(r#"activate User where "query""#); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Activate { .. }))); - } - - #[test] - fn test_sealed_markers() { - let bc = gen("sealed { let x = 1 }"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedBegin))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedEnd))); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-compiler/src/compiler.rs b/_archive/rust-bootstrap/engrams/el-compiler/src/compiler.rs deleted file mode 100644 index 10602ae..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/src/compiler.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! Top-level compiler struct — orchestrates the full pipeline. - -use std::path::PathBuf; - -use el_lexer::tokenize; -use el_parser::parse; -use el_seal::{seal, SealConfig, SealedArtifact}; -use el_types::TypeChecker; - -use crate::bytecode::{deserialize_bytecode, serialize_bytecode}; -use crate::codegen::Codegen; -use crate::error::{CompileError, CompileResult}; - -/// Which compilation target to produce. -#[derive(Debug, Clone, PartialEq)] -pub enum Target { - /// Full debug info: source maps, stack traces, no optimization. - Debug, - /// Optimized, stripped, no debug info. - Release, - /// Quantum-sealed: encrypted bytecode, cannot be decompiled. - Prod, -} - -/// Compiler configuration. -#[derive(Debug, Clone)] -pub struct CompilerOptions { - pub target: Target, - pub output_path: PathBuf, - pub source_path: PathBuf, - /// Path to an Engram database for `activate` type resolution. - /// `None` disables semantic type compatibility (falls back to structural). - pub engram_db_path: Option, - /// Seal configuration for the `prod` target. - pub seal_config: SealConfig, -} - -impl Default for CompilerOptions { - fn default() -> Self { - Self { - target: Target::Debug, - output_path: PathBuf::from("out.elc"), - source_path: PathBuf::from("main.el"), - engram_db_path: None, - seal_config: SealConfig::default(), - } - } -} - -/// The output of a compilation. -#[derive(Debug)] -pub struct CompileOutput { - /// The compiled artifact bytes. Format depends on target: - /// - Debug/Release: JSON-serialized `Vec` - /// - Prod: `SealedArtifact` wire format (`ENGRAM01` + JSON body) - pub artifact: Vec, - pub target: Target, - /// Whether the artifact is quantum-sealed. - pub sealed: bool, - /// JSON source map (debug target only). - pub source_map: Option, - /// Type-check and compilation diagnostics. - pub diagnostics: Vec, -} - -/// The Engram language compiler. -pub struct Compiler; - -impl Compiler { - /// Compile `source` with the given options. - pub fn compile(source: &str, opts: CompilerOptions) -> CompileResult { - // ── Step 1: Lex ─────────────────────────────────────────────────────── - let tokens = tokenize(source)?; - - // ── Step 2: Parse ───────────────────────────────────────────────────── - let program = parse(tokens, source.to_string())?; - - // ── Step 3: Type-check ──────────────────────────────────────────────── - let mut checker = TypeChecker::with_builtins(); - let diags = checker.check(&program); - let diagnostics: Vec = diags.iter().map(|d| d.message.clone()).collect(); - - // We continue compiling even with type errors in debug/release mode. - // In prod mode, type errors are fatal. - if opts.target == Target::Prod && !checker.ok() { - return Err(CompileError::Type( - diagnostics.join("; ") - )); - } - - // ── Step 4: Code generation ─────────────────────────────────────────── - let emit_sm = matches!(opts.target, Target::Debug); - let cg = Codegen::new(emit_sm); - let (bytecode, source_map) = cg.generate(&program) - .map_err(|e| CompileError::Codegen(e.to_string()))?; - - let bytecode_bytes = serialize_bytecode(&bytecode) - .map_err(|e| CompileError::Codegen(e.to_string()))?; - - // ── Step 5: Target-specific post-processing ─────────────────────────── - match opts.target { - Target::Debug => { - let sm_json = source_map.to_json() - .map_err(|e| CompileError::Serialization(e.to_string()))?; - Ok(CompileOutput { - artifact: bytecode_bytes, - target: Target::Debug, - sealed: false, - source_map: Some(sm_json), - diagnostics, - }) - } - Target::Release => { - Ok(CompileOutput { - artifact: bytecode_bytes, - target: Target::Release, - sealed: false, - source_map: None, - diagnostics, - }) - } - Target::Prod => { - let artifact = seal(&bytecode_bytes, &opts.seal_config)?; - let artifact_bytes = artifact.to_bytes() - .map_err(|e| CompileError::Serialization(e.to_string()))?; - Ok(CompileOutput { - artifact: artifact_bytes, - target: Target::Prod, - sealed: true, - source_map: None, - diagnostics, - }) - } - } - } - - /// Convenience: compile and unseal, returning the bytecode instructions. - pub fn compile_and_unseal( - source: &str, - opts: CompilerOptions, - binding_key: &[u8], - ) -> CompileResult> { - let output = Self::compile(source, opts)?; - let sealed_artifact = SealedArtifact::from_bytes(&output.artifact) - .map_err(CompileError::Seal)?; - let bytecode_bytes = el_seal::unseal(&sealed_artifact, binding_key) - .map_err(CompileError::Seal)?; - let instructions = deserialize_bytecode(&bytecode_bytes) - .map_err(|e| CompileError::Codegen(e.to_string()))?; - Ok(instructions) - } -} - -#[cfg(test)] -mod tests { - use el_seal::{DeploymentBinding, SealAlgorithm}; - use super::*; - - fn debug_opts() -> CompilerOptions { - CompilerOptions { - target: Target::Debug, - ..Default::default() - } - } - - fn release_opts() -> CompilerOptions { - CompilerOptions { - target: Target::Release, - ..Default::default() - } - } - - fn prod_opts() -> CompilerOptions { - CompilerOptions { - target: Target::Prod, - seal_config: SealConfig { - algorithm: SealAlgorithm::Aes256Gcm, - deployment_binding: DeploymentBinding::None, - }, - ..Default::default() - } - } - - #[test] - fn test_compile_hello_world_debug() { - let src = r#"let msg: String = "Hello, World!""#; - let out = Compiler::compile(src, debug_opts()).unwrap(); - assert!(!out.artifact.is_empty()); - assert!(!out.sealed); - assert!(out.source_map.is_some()); - } - - #[test] - fn test_compile_release_no_source_map() { - let src = "let x: Int = 42"; - let out = Compiler::compile(src, release_opts()).unwrap(); - assert!(out.source_map.is_none()); - assert!(!out.sealed); - } - - #[test] - fn test_compile_prod_is_sealed() { - let src = "let x: Int = 1"; - let out = Compiler::compile(src, prod_opts()).unwrap(); - assert!(out.sealed); - // Artifact must start with ENGRAM01 magic - assert_eq!(&out.artifact[..8], b"ENGRAM01"); - } - - #[test] - fn test_prod_roundtrip() { - let src = "let answer: Int = 42"; - let opts = prod_opts(); - let out = Compiler::compile(src, opts).unwrap(); - let sealed = SealedArtifact::from_bytes(&out.artifact).unwrap(); - let bytecode_bytes = el_seal::unseal(&sealed, &[]).unwrap(); - let instructions = deserialize_bytecode(&bytecode_bytes).unwrap(); - // Should have a PUSH 42, STORE answer, and HALT at minimum - assert!(instructions.iter().any(|b| matches!(b, crate::bytecode::Bytecode::Push(crate::bytecode::Value::Int(42))))); - } - - #[test] - fn test_compile_fn_def() { - let src = r#" -fn add(a: Int, b: Int) -> Int { - return a + b -} -"#; - let out = Compiler::compile(src, debug_opts()).unwrap(); - assert!(!out.artifact.is_empty()); - } - - #[test] - fn test_compile_type_mismatch_warning_debug() { - // In debug mode, type errors are warnings (not fatal) - let src = r#"let x: Int = "not an int""#; - let out = Compiler::compile(src, debug_opts()).unwrap(); - assert!(!out.diagnostics.is_empty()); - } - - #[test] - fn test_source_map_has_entries() { - let src = "let x = 1\nlet y = 2"; - let out = Compiler::compile(src, debug_opts()).unwrap(); - let sm_json = out.source_map.unwrap(); - let sm: crate::source_map::SourceMap = serde_json::from_str(&sm_json).unwrap(); - assert!(!sm.entries.is_empty()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-compiler/src/debugger.rs b/_archive/rust-bootstrap/engrams/el-compiler/src/debugger.rs deleted file mode 100644 index 5253513..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/src/debugger.rs +++ /dev/null @@ -1,279 +0,0 @@ -//! Step-debugger infrastructure for the Engram VM. -//! -//! The [`Debugger`] is attached to the bytecode interpreter and emits -//! [`DebugEvent`]s as execution proceeds. IDEs and `el debug` consume these -//! events to show variable state, call stack, and current source line. - -use std::collections::{HashMap, HashSet}; - -use crate::bytecode::Value; - -/// A single frame on the call stack. -#[derive(Debug, Clone)] -pub struct StackFrame { - pub function_name: String, - pub source_file: String, - pub line: u32, - pub col: u32, -} - -/// Controls how the debugger advances through bytecode. -#[derive(Debug, Clone, PartialEq)] -pub enum StepMode { - /// Run freely until the next breakpoint. - Run, - /// Execute exactly one statement, then pause. - StepOver, - /// Step into function calls (pause on first instruction of callee). - StepInto, - /// Run until the current frame returns, then pause. - StepOut, -} - -/// An event emitted by the VM when in debug mode. -#[derive(Debug, Clone)] -pub enum DebugEvent { - /// Execution paused at a breakpoint. - Breakpoint { - offset: usize, - frame: StackFrame, - }, - /// Execution paused after a single step. - Step { - frame: StackFrame, - locals: HashMap, - }, - /// A function returned a value. - Return { - value: Value, - }, - /// The VM encountered a runtime error. - Error { - message: String, - frame: StackFrame, - }, -} - -/// The debugger attached to a running VM instance. -/// -/// In debug mode the interpreter queries [`should_pause`] before each -/// instruction. If it returns `true`, execution stops and a [`DebugEvent`] -/// is emitted to the registered handler. -pub struct Debugger { - /// Bytecode offsets at which to pause execution. - pub breakpoints: HashSet, - /// Current stepping mode. - pub step_mode: StepMode, - /// Simulated call stack (maintained by the interpreter). - pub call_stack: Vec, - /// Snapshot of local variables at the last pause. - pub locals: HashMap, - /// Events emitted since the last [`drain_events`] call. - events: Vec, -} - -impl Debugger { - /// Create a new debugger that will break on the very first instruction. - pub fn new() -> Self { - Self { - breakpoints: HashSet::new(), - step_mode: StepMode::StepOver, - call_stack: vec![StackFrame { - function_name: "".into(), - source_file: "".into(), - line: 1, - col: 1, - }], - locals: HashMap::new(), - events: Vec::new(), - } - } - - /// Add a breakpoint at a bytecode offset. - pub fn add_breakpoint(&mut self, offset: usize) { - self.breakpoints.insert(offset); - } - - /// Remove a breakpoint. - pub fn remove_breakpoint(&mut self, offset: usize) { - self.breakpoints.remove(&offset); - } - - /// Returns `true` if the debugger should pause at `offset`. - pub fn should_pause(&self, offset: usize) -> bool { - if self.breakpoints.contains(&offset) { - return true; - } - matches!(self.step_mode, StepMode::StepOver | StepMode::StepInto) - } - - /// Called by the interpreter when it pauses at `offset`. - pub fn on_pause(&mut self, offset: usize, locals: HashMap) { - self.locals = locals.clone(); - let frame = self.current_frame_cloned(); - if self.breakpoints.contains(&offset) { - self.events.push(DebugEvent::Breakpoint { offset, frame }); - } else { - self.events.push(DebugEvent::Step { frame, locals }); - } - } - - /// Called when a function is entered. - pub fn push_frame(&mut self, function_name: String, source_file: String) { - self.call_stack.push(StackFrame { - function_name, - source_file, - line: 1, - col: 1, - }); - } - - /// Called when a function returns. - pub fn pop_frame(&mut self, value: Value) { - self.call_stack.pop(); - self.events.push(DebugEvent::Return { value }); - } - - /// Record a runtime error. - pub fn on_error(&mut self, message: String) { - let frame = self.current_frame_cloned(); - self.events.push(DebugEvent::Error { message, frame }); - } - - /// Update the source position of the top frame. - pub fn update_position(&mut self, line: u32, col: u32) { - if let Some(frame) = self.call_stack.last_mut() { - frame.line = line; - frame.col = col; - } - } - - /// Drain and return all queued events. - pub fn drain_events(&mut self) -> Vec { - std::mem::take(&mut self.events) - } - - /// Current frame clone, or a sentinel if the stack is empty. - fn current_frame_cloned(&self) -> StackFrame { - self.call_stack.last().cloned().unwrap_or_else(|| StackFrame { - function_name: String::new(), - source_file: String::new(), - line: 0, - col: 0, - }) - } -} - -impl Default for Debugger { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_breakpoint_triggers_pause() { - let mut dbg = Debugger::new(); - dbg.step_mode = StepMode::Run; // not stepping — only breakpoints - dbg.add_breakpoint(5); - assert!(!dbg.should_pause(0)); - assert!(!dbg.should_pause(4)); - assert!(dbg.should_pause(5)); - assert!(!dbg.should_pause(6)); - } - - #[test] - fn test_step_over_always_pauses() { - let mut dbg = Debugger::new(); - dbg.step_mode = StepMode::StepOver; - assert!(dbg.should_pause(0)); - assert!(dbg.should_pause(99)); - } - - #[test] - fn test_step_into_always_pauses() { - let mut dbg = Debugger::new(); - dbg.step_mode = StepMode::StepInto; - assert!(dbg.should_pause(0)); - } - - #[test] - fn test_run_mode_no_pause_without_breakpoint() { - let mut dbg = Debugger::new(); - dbg.step_mode = StepMode::Run; - assert!(!dbg.should_pause(42)); - } - - #[test] - fn test_remove_breakpoint() { - let mut dbg = Debugger::new(); - dbg.step_mode = StepMode::Run; - dbg.add_breakpoint(10); - assert!(dbg.should_pause(10)); - dbg.remove_breakpoint(10); - assert!(!dbg.should_pause(10)); - } - - #[test] - fn test_on_pause_emits_step_event() { - let mut dbg = Debugger::new(); - dbg.step_mode = StepMode::StepOver; - let mut locals = HashMap::new(); - locals.insert("x".into(), Value::Int(42)); - dbg.on_pause(0, locals.clone()); - let events = dbg.drain_events(); - assert_eq!(events.len(), 1); - assert!(matches!(events[0], DebugEvent::Step { .. })); - } - - #[test] - fn test_on_pause_at_breakpoint_emits_breakpoint_event() { - let mut dbg = Debugger::new(); - dbg.step_mode = StepMode::Run; - dbg.add_breakpoint(7); - dbg.on_pause(7, HashMap::new()); - let events = dbg.drain_events(); - assert_eq!(events.len(), 1); - assert!(matches!(events[0], DebugEvent::Breakpoint { offset: 7, .. })); - } - - #[test] - fn test_push_pop_frame() { - let mut dbg = Debugger::new(); - dbg.push_frame("my_fn".into(), "test.el".into()); - assert_eq!(dbg.call_stack.len(), 2); - assert_eq!(dbg.call_stack[1].function_name, "my_fn"); - dbg.pop_frame(Value::Int(0)); - assert_eq!(dbg.call_stack.len(), 1); - let events = dbg.drain_events(); - assert!(matches!(events[0], DebugEvent::Return { .. })); - } - - #[test] - fn test_on_error_emits_error_event() { - let mut dbg = Debugger::new(); - dbg.on_error("division by zero".into()); - let events = dbg.drain_events(); - assert!(matches!(&events[0], DebugEvent::Error { message, .. } if message == "division by zero")); - } - - #[test] - fn test_drain_clears_events() { - let mut dbg = Debugger::new(); - dbg.on_error("oops".into()); - let _ = dbg.drain_events(); - let events2 = dbg.drain_events(); - assert!(events2.is_empty()); - } - - #[test] - fn test_update_position() { - let mut dbg = Debugger::new(); - dbg.update_position(10, 5); - assert_eq!(dbg.call_stack[0].line, 10); - assert_eq!(dbg.call_stack[0].col, 5); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-compiler/src/error.rs b/_archive/rust-bootstrap/engrams/el-compiler/src/error.rs deleted file mode 100644 index d0872e7..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/src/error.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Compiler error type. - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum CompileError { - #[error("lex error: {0}")] - Lex(#[from] el_lexer::LexError), - - #[error("parse error: {0}")] - Parse(#[from] el_parser::ParseError), - - #[error("type error: {0}")] - Type(String), - - #[error("codegen error: {0}")] - Codegen(String), - - #[error("seal error: {0}")] - Seal(#[from] el_seal::SealError), - - #[error("serialization error: {0}")] - Serialization(String), - - #[error("io error: {0}")] - Io(String), -} - -pub type CompileResult = Result; diff --git a/_archive/rust-bootstrap/engrams/el-compiler/src/lib.rs b/_archive/rust-bootstrap/engrams/el-compiler/src/lib.rs deleted file mode 100644 index 12c269d..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! el-compiler — Engram language compilation pipeline. -//! -//! Takes a source string and produces a compiled artifact for one of three -//! targets: [`Target::Debug`], [`Target::Release`], or [`Target::Prod`]. -//! -//! # Pipeline -//! -//! ```text -//! Source ──lex──► Tokens ──parse──► AST ──typecheck──► Typed AST -//! ──codegen──► Bytecode ──[seal]──► Artifact -//! ``` -//! -//! # Debug target -//! Emits bytecode + a JSON source map (bytecode offset → source span). -//! -//! # Release target -//! Emits bytecode only; no debug info; minor dead-code pruning. -//! -//! # Prod target -//! Emits bytecode, then passes it through [`el_seal`] with the deployment -//! key from `ENGRAM_SEAL_KEY`. The result is a [`SealedArtifact`] that -//! cannot be decompiled without the key. - -mod bytecode; -mod codegen; -mod compiler; -mod debugger; -mod error; -mod source_map; - -pub use bytecode::{Bytecode, Value, serialize_bytecode, deserialize_bytecode, wrap_elvm, unwrap_elvm, ELVM_MAGIC, ELVM_VERSION}; -pub use compiler::{CompileOutput, Compiler, CompilerOptions, Target}; -pub use debugger::{DebugEvent, Debugger, StackFrame, StepMode}; -pub use error::{CompileError, CompileResult}; -pub use source_map::SourceMap; diff --git a/_archive/rust-bootstrap/engrams/el-compiler/src/source_map.rs b/_archive/rust-bootstrap/engrams/el-compiler/src/source_map.rs deleted file mode 100644 index 87e3007..0000000 --- a/_archive/rust-bootstrap/engrams/el-compiler/src/source_map.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Source map: maps bytecode instruction indices to source spans. -//! -//! Only emitted for the debug target. The JSON format is simple and can be -//! consumed by any debugger or IDE extension. - -use serde::{Deserialize, Serialize}; -use el_lexer::Span; - -/// A single mapping entry: bytecode index → source span. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MapEntry { - /// Index of the bytecode instruction (0-based). - pub instruction: usize, - pub start: usize, - pub end: usize, - pub line: u32, - pub col: u32, -} - -impl MapEntry { - pub fn new(instruction: usize, span: Span) -> Self { - Self { - instruction, - start: span.start, - end: span.end, - line: span.line, - col: span.col, - } - } -} - -/// The full source map for a compilation unit. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct SourceMap { - pub entries: Vec, -} - -impl SourceMap { - pub fn new() -> Self { - Self::default() - } - - /// Record that instruction at `index` was generated from `span`. - pub fn record(&mut self, index: usize, span: Span) { - self.entries.push(MapEntry::new(index, span)); - } - - /// Look up the source span for a given instruction index. - pub fn lookup(&self, index: usize) -> Option<&MapEntry> { - self.entries.iter().rfind(|e| e.instruction <= index) - } - - /// Serialize to JSON string. - pub fn to_json(&self) -> Result { - serde_json::to_string_pretty(self).map_err(|e| e.to_string()) - } -} diff --git a/_archive/rust-bootstrap/engrams/el-fmt/Cargo.toml b/_archive/rust-bootstrap/engrams/el-fmt/Cargo.toml deleted file mode 100644 index cfb4513..0000000 --- a/_archive/rust-bootstrap/engrams/el-fmt/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "el-fmt" -version = "0.1.0" -edition = "2021" - -[dependencies] -el-lexer = { path = "../el-lexer" } -el-parser = { path = "../el-parser" } -thiserror = "2" - -[dev-dependencies] -el-lexer = { path = "../el-lexer" } -el-parser = { path = "../el-parser" } diff --git a/_archive/rust-bootstrap/engrams/el-fmt/src/config.rs b/_archive/rust-bootstrap/engrams/el-fmt/src/config.rs deleted file mode 100644 index 430dcc3..0000000 --- a/_archive/rust-bootstrap/engrams/el-fmt/src/config.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Formatter configuration. - -#[derive(Debug, Clone)] -pub struct FmtConfig { - pub indent: IndentStyle, - /// Number of spaces per indent level (default 4). - pub indent_width: usize, - /// Maximum line width before wrapping (default 100). - pub max_line_width: usize, - /// Whether to ensure the output ends with a newline (default true). - pub trailing_newline: bool, - /// Whether to emit a space before an opening brace (default true). - pub space_before_brace: bool, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum IndentStyle { - Spaces, - Tabs, -} - -impl Default for FmtConfig { - fn default() -> Self { - Self { - indent: IndentStyle::Spaces, - indent_width: 4, - max_line_width: 100, - trailing_newline: true, - space_before_brace: true, - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-fmt/src/error.rs b/_archive/rust-bootstrap/engrams/el-fmt/src/error.rs deleted file mode 100644 index 0c8d464..0000000 --- a/_archive/rust-bootstrap/engrams/el-fmt/src/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Error types for el-fmt. - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum FmtError { - #[error("lex error: {0}")] - Lex(String), - #[error("parse error: {0}")] - Parse(String), -} diff --git a/_archive/rust-bootstrap/engrams/el-fmt/src/formatter.rs b/_archive/rust-bootstrap/engrams/el-fmt/src/formatter.rs deleted file mode 100644 index 14dd211..0000000 --- a/_archive/rust-bootstrap/engrams/el-fmt/src/formatter.rs +++ /dev/null @@ -1,542 +0,0 @@ -//! AST pretty-printer — the core of el-fmt. - -use el_parser::{BinOp, Expr, Literal, MatchArm, Pattern, Program, Stmt, TypeExpr}; - -use crate::{FmtConfig, FmtError}; -use crate::config::IndentStyle; - -pub struct Formatter { - config: FmtConfig, -} - -impl Formatter { - pub fn new(config: FmtConfig) -> Self { - Self { config } - } - - pub fn format(&self, program: &Program) -> Result { - let mut out = String::new(); - for (i, stmt) in program.stmts.iter().enumerate() { - if i > 0 { - out.push('\n'); - } - self.fmt_stmt(&mut out, stmt, 0); - } - if self.config.trailing_newline && !out.ends_with('\n') { - out.push('\n'); - } - Ok(out) - } - - fn indent(&self, depth: usize) -> String { - match self.config.indent { - IndentStyle::Spaces => " ".repeat(depth * self.config.indent_width), - IndentStyle::Tabs => "\t".repeat(depth), - } - } - - fn fmt_stmt(&self, out: &mut String, stmt: &Stmt, depth: usize) { - let ind = self.indent(depth); - match stmt { - Stmt::Let { name, type_ann, value, .. } => { - out.push_str(&ind); - out.push_str("let "); - out.push_str(name); - if let Some(ty) = type_ann { - out.push_str(": "); - out.push_str(&self.fmt_type(ty)); - } - out.push_str(" = "); - self.fmt_expr(out, value, depth); - out.push('\n'); - } - - Stmt::Return(expr, _) => { - out.push_str(&format!("{ind}return ")); - self.fmt_expr(out, expr, depth); - out.push('\n'); - } - - Stmt::Expr(expr, _) => { - out.push_str(&ind); - self.fmt_expr(out, expr, depth); - out.push('\n'); - } - - Stmt::FnDef { name, params, body, decorators, return_type, .. } => { - // Decorators - for dec in decorators { - out.push_str(&format!("{ind}@{}\n", dec.name)); - } - // Parameters - let params_str: Vec = params - .iter() - .map(|p| format!("{}: {}", p.name, self.fmt_type(&p.type_ann))) - .collect(); - // Always emit return type — the parser requires `->`. - let ret = format!(" -> {}", self.fmt_type(return_type)); - let brace_space = if self.config.space_before_brace { " " } else { "" }; - out.push_str(&format!( - "{ind}fn {name}({}){}{brace_space}{{\n", - params_str.join(", "), - ret, - )); - for s in body { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{ind}}}\n")); - } - - Stmt::TypeDef { name, fields, .. } => { - out.push_str(&format!("{ind}type {name} {{\n")); - for f in fields { - out.push_str(&format!( - "{} {}: {}\n", - ind, - f.name, - self.fmt_type(&f.type_ann) - )); - } - out.push_str(&format!("{ind}}}\n")); - } - - Stmt::EnumDef { name, variants, .. } => { - out.push_str(&format!("{ind}enum {name} {{\n")); - for v in variants { - if let Some(payload) = &v.payload { - out.push_str(&format!( - "{} {}({})\n", - ind, - v.name, - self.fmt_type(payload) - )); - } else { - out.push_str(&format!("{} {}\n", ind, v.name)); - } - } - out.push_str(&format!("{ind}}}\n")); - } - - Stmt::TestDef { name, body, .. } => { - out.push_str(&format!("{ind}test {:?} {{\n", name)); - for s in body { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{ind}}}\n")); - } - - Stmt::Assert(expr, _) => { - out.push_str(&format!("{ind}assert ")); - self.fmt_expr(out, expr, depth); - out.push('\n'); - } - - Stmt::Import { path, names, alias, .. } => { - if names.is_empty() { - let joined = path.join("::"); - if let Some(a) = alias { - out.push_str(&format!("{ind}import {joined} as {a}\n")); - } else { - out.push_str(&format!("{ind}import {joined}\n")); - } - } else { - let joined = path.join("::"); - let items = names.join(", "); - out.push_str(&format!("{ind}from {joined} import {{ {items} }}\n")); - } - } - - - Stmt::ProtocolDef { name, methods, .. } => { - out.push_str(&format!("{ind}protocol {name} {{\n")); - for m in methods { - let params_str: Vec = m - .params - .iter() - .map(|p| format!("{}: {}", p.name, self.fmt_type(&p.type_ann))) - .collect(); - out.push_str(&format!( - "{} fn {}({}) -> {}\n", - ind, - m.name, - params_str.join(", "), - self.fmt_type(&m.return_type) - )); - } - out.push_str(&format!("{ind}}}\n")); - } - - Stmt::ImplDef { protocol_name, type_name, methods, .. } => { - out.push_str(&format!("{ind}impl {protocol_name} for {type_name} {{\n")); - for m in methods { - self.fmt_stmt(out, m, depth + 1); - } - out.push_str(&format!("{ind}}}\n")); - } - - Stmt::Seed(seed, _) => { - use el_parser::SeedStmt; - match seed { - SeedStmt::Node { node_type, content, importance, tier } => { - let tier_str = tier - .as_deref() - .map(|t| format!(", tier: {t}")) - .unwrap_or_default(); - out.push_str(&format!( - "{ind}seed {node_type} {{ content: {:?}, importance: {importance}{tier_str} }}\n", - content - )); - } - SeedStmt::Edge { from, to, relation, weight } => { - out.push_str(&format!( - "{ind}seed Edge {{ from: {from}, to: {to}, relation: {relation:?}, weight: {weight} }}\n" - )); - } - } - } - - Stmt::Retry { count, body, fallback, .. } => { - out.push_str(&format!("{ind}retry ")); - self.fmt_expr(out, count, depth); - out.push_str(" times {\n"); - for s in body { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{ind}}}")); - if let Some(fb) = fallback { - out.push_str(" fallback {\n"); - for s in fb { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{ind}}}")); - } - out.push('\n'); - } - - Stmt::Deploy { fn_name, route, target, .. } => { - out.push_str(&format!("{ind}deploy {fn_name} to \"{route}\" via {target}\n")); - } - - Stmt::While { condition, body, .. } => { - out.push_str(&format!("{ind}while ")); - self.fmt_expr(out, condition, depth); - out.push_str(" {\n"); - for s in body { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{ind}}}\n")); - } - - // Component definition (UI/reactive components) — emit as-is placeholder. - Stmt::ComponentDef { name, methods, .. } => { - out.push_str(&format!("{ind}component {name} {{\n")); - for s in methods { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{ind}}}\n")); - } - - // Catch-all: unknown/future statement kinds are emitted as a comment. - #[allow(unreachable_patterns)] - _ => { - out.push_str(&format!("{ind}// [unformatted statement]\n")); - } - } - } - - fn fmt_expr(&self, out: &mut String, expr: &Expr, depth: usize) { - match expr { - Expr::Literal(lit) => self.fmt_literal(out, lit), - - Expr::Ident(name) => out.push_str(name), - - Expr::Path { segments } => out.push_str(&segments.join("::")), - - Expr::BinOp { op, left, right } => { - self.fmt_expr(out, left, depth); - out.push_str(&format!(" {} ", self.fmt_binop(op))); - self.fmt_expr(out, right, depth); - } - - Expr::UnaryNot(inner) => { - out.push('!'); - self.fmt_expr(out, inner, depth); - } - - Expr::UnaryBitNot(inner) => { - out.push('~'); - self.fmt_expr(out, inner, depth); - } - - Expr::Try(inner) => { - self.fmt_expr(out, inner, depth); - out.push('?'); - } - - Expr::Call { func, args } => { - self.fmt_expr(out, func, depth); - out.push('('); - for (i, arg) in args.iter().enumerate() { - if i > 0 { - out.push_str(", "); - } - self.fmt_expr(out, arg, depth); - } - out.push(')'); - } - - Expr::Block(stmts) => { - out.push_str("{\n"); - for s in stmts { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{}}}", self.indent(depth))); - } - - Expr::If { cond, then, else_ } => { - out.push_str("if "); - self.fmt_expr(out, cond, depth); - out.push(' '); - self.fmt_expr(out, then, depth); - if let Some(else_expr) = else_ { - out.push_str(" else "); - self.fmt_expr(out, else_expr, depth); - } - } - - Expr::Activate { type_name, query } => { - out.push_str(&format!("activate {type_name} where {:?}", query)); - } - - Expr::Field { object, field } => { - self.fmt_expr(out, object, depth); - out.push('.'); - out.push_str(field); - } - - Expr::Index { object, index } => { - self.fmt_expr(out, object, depth); - out.push('['); - self.fmt_expr(out, index, depth); - out.push(']'); - } - - Expr::Array(elems) => { - out.push('['); - for (i, e) in elems.iter().enumerate() { - if i > 0 { - out.push_str(", "); - } - self.fmt_expr(out, e, depth); - } - out.push(']'); - } - - Expr::MapLiteral(pairs) => { - out.push('{'); - for (i, (k, v)) in pairs.iter().enumerate() { - if i > 0 { - out.push_str(", "); - } - self.fmt_expr(out, k, depth); - out.push_str(": "); - self.fmt_expr(out, v, depth); - } - out.push('}'); - } - - Expr::Sealed(stmts) => { - out.push_str("sealed {\n"); - for s in stmts { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{}}}", self.indent(depth))); - } - - Expr::Match { subject, arms } => { - out.push_str("match "); - self.fmt_expr(out, subject, depth); - out.push_str(" {\n"); - for arm in arms { - self.fmt_match_arm(out, arm, depth); - } - out.push_str(&format!("{}}}", self.indent(depth))); - } - - Expr::Closure { params, return_type, body, .. } => { - out.push('|'); - let params_str: Vec = params - .iter() - .map(|p| format!("{}: {}", p.name, self.fmt_type(&p.type_ann))) - .collect(); - out.push_str(¶ms_str.join(", ")); - out.push('|'); - if let Some(rt) = return_type { - out.push_str(&format!(" -> {}", self.fmt_type(rt))); - } - out.push(' '); - self.fmt_expr(out, body, depth); - } - Expr::StructLit { type_name, fields, .. } => { - out.push_str(type_name); - out.push_str(" { "); - let fields_str: Vec = fields - .iter() - .map(|(name, val)| { - let mut s = format!("{name}: "); - self.fmt_expr(&mut s, val, depth); - s - }) - .collect(); - out.push_str(&fields_str.join(", ")); - out.push_str(" }"); - } - - Expr::With { base, updates } => { - self.fmt_expr(out, base, depth); - out.push_str(" with { "); - for (k, v) in updates { - out.push_str(&format!("{k}: ")); - self.fmt_expr(out, v, depth); - out.push_str(", "); - } - out.push('}'); - } - Expr::Reason { query } => { - out.push_str(&format!("reason {:?}", query)); - } - Expr::Parallel { entries } => { - out.push_str("parallel { "); - for (name, e) in entries { - out.push_str(&format!("{name}: ")); - self.fmt_expr(out, e, depth); - out.push_str(", "); - } - out.push('}'); - } - Expr::Trace { label, body } => { - out.push_str(&format!("trace {:?} {{\n", label)); - for s in body { - self.fmt_stmt(out, s, depth + 1); - } - out.push_str(&format!("{}}}", self.indent(depth))); - } - - // JSX expressions — emit minimal JSX syntax. - Expr::JsxElement { tag, attrs, children, self_closing } => { - out.push('<'); - out.push_str(tag); - for (key, val) in attrs { - out.push(' '); - out.push_str(key); - match val { - el_parser::JsxAttrValue::Str(s) => out.push_str(&format!("=\"{s}\"")), - el_parser::JsxAttrValue::Expr(e) => { - out.push_str("={"); - self.fmt_expr(out, e, depth); - out.push('}'); - } - } - } - if *self_closing { - out.push_str(" />"); - } else { - out.push('>'); - for child in children { - self.fmt_expr(out, child, depth); - } - out.push_str(&format!("")); - } - } - Expr::JsxExpr(inner) => { - out.push('{'); - self.fmt_expr(out, inner, depth); - out.push('}'); - } - Expr::JsxText(text) => { - out.push_str(text); - } - - // Catch-all: unknown/future expression kinds. - #[allow(unreachable_patterns)] - _ => { - out.push_str("/* [unformatted expr] */"); - } - } - } - - fn fmt_literal(&self, out: &mut String, lit: &Literal) { - match lit { - Literal::Int(n) => out.push_str(&n.to_string()), - Literal::Float(f) => out.push_str(&f.to_string()), - Literal::Str(s) => out.push_str(&format!("{s:?}")), - Literal::Bool(b) => out.push_str(&b.to_string()), - } - } - - fn fmt_binop(&self, op: &BinOp) -> &'static str { - match op { - BinOp::Add => "+", - BinOp::Sub => "-", - BinOp::Mul => "*", - BinOp::Div => "/", - BinOp::Eq => "==", - BinOp::NotEq => "!=", - BinOp::Lt => "<", - BinOp::Gt => ">", - BinOp::LtEq => "<=", - BinOp::GtEq => ">=", - BinOp::And => "&&", - BinOp::Or => "||", - BinOp::Mod => "%", - BinOp::BitAnd => "&", - BinOp::BitOr => "|", - BinOp::BitXor => "^", - BinOp::Shl => "<<", - BinOp::Shr => ">>", - BinOp::NullCoalesce => "??", - } - } - - fn fmt_match_arm(&self, out: &mut String, arm: &MatchArm, depth: usize) { - out.push_str(&format!("{} ", self.indent(depth))); - self.fmt_pattern(out, &arm.pattern); - out.push_str(" => "); - self.fmt_expr(out, &arm.body, depth + 1); - out.push('\n'); - } - - fn fmt_pattern(&self, out: &mut String, pat: &Pattern) { - match pat { - Pattern::Wildcard => out.push('_'), - Pattern::Binding(name) => out.push_str(name), - Pattern::Literal(lit) => self.fmt_literal(out, lit), - Pattern::EnumVariant { enum_name, variant, payload } => { - out.push_str(&format!("{enum_name}::")); - out.push_str(variant); - if let Some(bind) = payload { - out.push_str(&format!("({bind})")); - } - } - } - } - - pub fn fmt_type(&self, ty: &TypeExpr) -> String { - match ty { - TypeExpr::Named(n) => n.clone(), - TypeExpr::Array(inner) => format!("[{}]", self.fmt_type(inner)), - TypeExpr::Optional(inner) => format!("{}?", self.fmt_type(inner)), - TypeExpr::Result { ok, err } => { - format!("Result<{}, {}>", self.fmt_type(ok), self.fmt_type(err)) - } - TypeExpr::Map { key, value } => { - format!("Map<{}, {}>", self.fmt_type(key), self.fmt_type(value)) - } - TypeExpr::Fn { params, return_type } => { - let ps: Vec<_> = params.iter().map(|p| self.fmt_type(p)).collect(); - format!("fn({}) -> {}", ps.join(", "), self.fmt_type(return_type)) - } - TypeExpr::TypeParam(n) => n.clone(), - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-fmt/src/lib.rs b/_archive/rust-bootstrap/engrams/el-fmt/src/lib.rs deleted file mode 100644 index 5231c8a..0000000 --- a/_archive/rust-bootstrap/engrams/el-fmt/src/lib.rs +++ /dev/null @@ -1,327 +0,0 @@ -//! el-fmt — canonical source formatter for el. -//! -//! Formats a `.el` source file into its canonical representation. -//! Parsing an already-formatted file and re-formatting it produces identical output. - -pub mod config; -pub mod error; -pub mod formatter; - -pub use config::FmtConfig; -pub use error::FmtError; -pub use formatter::Formatter; - -/// Format el source code. Returns the canonical formatted version. -pub fn format(source: &str) -> Result { - format_with_config(source, &FmtConfig::default()) -} - -/// Format with an explicit configuration. -pub fn format_with_config(source: &str, config: &FmtConfig) -> Result { - let tokens = - el_lexer::tokenize(source).map_err(|e| FmtError::Lex(e.to_string()))?; - let program = - el_parser::parse(tokens, source.to_string()).map_err(|e| FmtError::Parse(e.to_string()))?; - Formatter::new(config.clone()).format(&program) -} - -/// Check whether `source` is already in canonical form. -/// Returns `true` if formatting would produce no changes. -pub fn is_canonical(source: &str) -> Result { - let formatted = format(source)?; - Ok(formatted == source) -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - fn fmt(src: &str) -> String { - format(src).unwrap() - } - - fn idempotent(src: &str) { - let once = fmt(src); - let twice = fmt(&once); - assert_eq!(once, twice, "format not idempotent for:\n{src}"); - } - - // 1. Integer literal - #[test] - fn test_integer_literal() { - assert_eq!(fmt("42"), "42\n"); - } - - // 2. Let binding (no type annotation in source → formatter emits inferred type) - // We parse "let x = 1" which gives type_ann from the parser. - // Since el-parser always injects a type_ann, we just check the output is stable. - #[test] - fn test_let_binding_idempotent() { - // Round-trip: parse what we emit and re-emit - let source = "let x: Int = 1\n"; - assert_eq!(fmt(source), source); - idempotent(source); - } - - // 3. Binary operator spacing - #[test] - fn test_binary_op_spacing() { - let out = fmt("1 + 2\n"); - assert!(out.contains("1 + 2"), "expected '1 + 2' in: {out}"); - } - - // 4. Function definition canonical form - #[test] - fn test_fn_def() { - let src = "fn add(a: Int, b: Int) -> Int {\n return a + b\n}\n"; - let out = fmt(src); - assert!(out.contains("fn add("), "missing fn signature: {out}"); - assert!(out.contains("return a + b"), "missing return: {out}"); - idempotent(src); - } - - // 5. Nested function has 4-space indent - #[test] - fn test_nested_indent() { - let src = "fn outer() -> Void {\n fn inner() -> Void {\n }\n}\n"; - let out = fmt(src); - assert!(out.contains(" fn inner("), "inner fn not indented: {out}"); - idempotent(src); - } - - // 6. If expression spacing - #[test] - fn test_if_expr() { - let src = "fn f() -> Void {\n if true {\n }\n}\n"; - let out = fmt(src); - assert!(out.contains("if true"), "missing if: {out}"); - idempotent(src); - } - - // 7. If-else expression - #[test] - fn test_if_else() { - let src = "fn f(x: Int) -> Void {\n if x {\n } else {\n }\n}\n"; - let out = fmt(src); - assert!(out.contains("else"), "missing else: {out}"); - idempotent(src); - } - - // 8. Match expression arms on own lines - #[test] - fn test_match_expr() { - let src = "fn f(x: Status) -> Void {\n match x {\n Status::Active(v) => 1\n _ => 0\n }\n}\n"; - let out = fmt(src); - assert!(out.contains("match x"), "missing match: {out}"); - assert!(out.contains("=>"), "missing arm: {out}"); - idempotent(src); - } - - // 9. Activate expression - #[test] - fn test_activate() { - let src = "fn f() -> Void {\n activate User where \"active users\"\n}\n"; - let out = fmt(src); - assert!(out.contains("activate User where"), "missing activate: {out}"); - idempotent(src); - } - - // 10. Sealed block - #[test] - fn test_sealed_block() { - let src = "fn f() -> Void {\n sealed {\n let x: Int = 1\n }\n}\n"; - let out = fmt(src); - assert!(out.contains("sealed {"), "missing sealed: {out}"); - idempotent(src); - } - - // 11. Array literal - #[test] - fn test_array_literal() { - let src = "[1, 2, 3]\n"; - let out = fmt(src); - assert!(out.contains("[1, 2, 3]"), "missing array: {out}"); - idempotent(src); - } - - // 12. Field access - #[test] - fn test_field_access() { - let src = "fn f(u: User) -> Void {\n u.name\n}\n"; - let out = fmt(src); - assert!(out.contains("u.name"), "missing field access: {out}"); - idempotent(src); - } - - // 13. Function call with args - #[test] - fn test_fn_call() { - let src = "foo(1, 2)\n"; - let out = fmt(src); - assert!(out.contains("foo(1, 2)"), "missing call: {out}"); - idempotent(src); - } - - // 14. Type definition - #[test] - fn test_type_def() { - let src = "type User {\n name: String\n age: Int\n}\n"; - let out = fmt(src); - assert!(out.contains("type User {"), "missing type def: {out}"); - assert!(out.contains("name: String"), "missing field: {out}"); - idempotent(src); - } - - // 15. Enum definition - #[test] - fn test_enum_def() { - let src = "enum Status {\n Active\n Inactive\n}\n"; - let out = fmt(src); - assert!(out.contains("enum Status {"), "missing enum def: {out}"); - assert!(out.contains("Active"), "missing variant: {out}"); - idempotent(src); - } - - // 16. Decorator on fn - #[test] - fn test_decorator() { - let src = "@experience\nfn handle() -> Void {\n}\n"; - let out = fmt(src); - assert!(out.contains("@experience"), "missing decorator: {out}"); - idempotent(src); - } - - // 17. Multiple decorators in order - #[test] - fn test_multiple_decorators() { - let src = "@public\n@experience\nfn handle() -> Void {\n}\n"; - let out = fmt(src); - let pub_pos = out.find("@public").unwrap(); - let exp_pos = out.find("@experience").unwrap(); - assert!(pub_pos < exp_pos, "decorators out of order: {out}"); - idempotent(src); - } - - // 18. Return type annotation - #[test] - fn test_return_type() { - let src = "fn add(a: Int, b: Int) -> Int {\n return a + b\n}\n"; - let out = fmt(src); - assert!(out.contains("-> Int"), "missing return type: {out}"); - idempotent(src); - } - - // 19. Result type - #[test] - fn test_result_type() { - let src = "fn load() -> Result {\n return \"ok\"\n}\n"; - let out = fmt(src); - assert!(out.contains("Result"), "missing result type: {out}"); - idempotent(src); - } - - // 20. Optional type - #[test] - fn test_optional_type() { - let src = "fn find() -> String? {\n return \"ok\"\n}\n"; - let out = fmt(src); - assert!(out.contains("String?"), "missing optional type: {out}"); - idempotent(src); - } - - // 21. Trailing newline always present - #[test] - fn test_trailing_newline() { - let out = fmt("42"); - assert!(out.ends_with('\n'), "missing trailing newline"); - } - - // 22. is_canonical returns true for already-canonical source - #[test] - fn test_is_canonical_true() { - let src = "42\n"; - assert!(is_canonical(src).unwrap(), "expected canonical"); - } - - // 23. is_canonical returns false for non-canonical source - #[test] - fn test_is_canonical_false() { - // No trailing newline - let result = is_canonical("42"); - // Either it returns false OR the formatter fixes it - // Either way it should not error - assert!(result.is_ok()); - } - - // 24. Empty program produces just a newline - #[test] - fn test_empty_program() { - // An empty string has no stmts so produces nothing; trailing newline adds one - let out = fmt(""); - assert_eq!(out, "\n"); - } - - // 25. Idempotence for multiple constructs - #[test] - fn test_idempotent_fn_def() { - idempotent("fn add(a: Int, b: Int) -> Int {\n return a + b\n}\n"); - idempotent("fn noop() -> Void {\n}\n"); - } - - #[test] - fn test_idempotent_type_def() { - idempotent("type User {\n name: String\n age: Int\n}\n"); - } - - #[test] - fn test_idempotent_enum_def() { - idempotent("enum Status {\n Active\n Inactive\n}\n"); - } - - // 26. Test block - #[test] - fn test_test_block() { - let src = "test \"my test\" {\n assert 1 == 1\n}\n"; - let out = fmt(src); - assert!(out.contains("test \"my test\""), "missing test block: {out}"); - idempotent(src); - } - - // 27. Wildcard pattern in match - #[test] - fn test_wildcard_pattern() { - let src = "fn f(x: Status) -> Void {\n match x {\n _ => 0\n }\n}\n"; - let out = fmt(src); - assert!(out.contains("_ =>"), "missing wildcard: {out}"); - idempotent(src); - } - - // 28. Binding pattern in match - #[test] - fn test_binding_pattern() { - let src = "fn f(x: Int) -> Void {\n match x {\n v => v\n }\n}\n"; - let out = fmt(src); - assert!(out.contains("v =>"), "missing binding: {out}"); - idempotent(src); - } - - // 29. Enum variant with payload - #[test] - fn test_enum_variant_payload() { - let src = "enum Msg {\n Value(Int)\n Empty\n}\n"; - let out = fmt(src); - assert!(out.contains("Value(Int)"), "missing payload variant: {out}"); - idempotent(src); - } - - // 30. for loop - #[test] - fn test_for_loop() { - let src = "fn f(items: [Int]) -> Void {\n for x in items {\n x\n }\n}\n"; - let out = fmt(src); - assert!(out.contains("for x in"), "missing for loop: {out}"); - idempotent(src); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/Cargo.toml b/_archive/rust-bootstrap/engrams/el-integration/Cargo.toml deleted file mode 100644 index aa1d0d0..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "el-integration" -description = "Engram language integration tests — full pipeline end-to-end" -version.workspace = true -edition.workspace = true -license.workspace = true - -[lib] -name = "el_integration" -path = "src/lib.rs" - -[dependencies] -el-lexer = { workspace = true } -el-parser = { workspace = true } -el-types = { workspace = true } -el-compiler = { workspace = true } -el-stdlib = { workspace = true } -serde_json = { workspace = true } -el-seal = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/lib.rs b/_archive/rust-bootstrap/engrams/el-integration/src/lib.rs deleted file mode 100644 index 2784fd7..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/lib.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Integration tests for the Engram language compiler pipeline. -//! -//! Each test module exercises the full pipeline: -//! `source → lex → parse → type-check → compile` - -#[cfg(test)] -pub mod tests; - -use el_compiler::{Compiler, CompilerOptions}; -use el_lexer::tokenize; -use el_parser::parse; -use el_types::{TypeChecker, TypeEnv}; - -/// Build a TypeEnv that includes both the core builtins and the full stdlib. -fn stdlib_env() -> TypeEnv { - let mut env = TypeEnv::with_builtins(); - el_stdlib::register_builtins(&mut env); - env -} - -/// Convenience: run the full pipeline on a source string. -/// Returns `Ok(())` if parsing and type-checking succeed with no errors. -/// The type environment includes all stdlib builtins. -pub fn pipeline_ok(src: &str) -> Result<(), String> { - let tokens = tokenize(src).map_err(|e| format!("lex error: {e}"))?; - let prog = parse(tokens, src.to_string()).map_err(|e| format!("parse error: {e}"))?; - let mut checker = TypeChecker::new(stdlib_env()); - checker.check(&prog); - if checker.ok() { - Ok(()) - } else { - let msgs: Vec<_> = checker.diagnostics.iter().map(|d| d.message.clone()).collect(); - Err(format!("type errors: {}", msgs.join(", "))) - } -} - -/// Run through lexer + parser only, returning the parsed program. -pub fn parse_ok(src: &str) -> Result { - let tokens = tokenize(src).map_err(|e| format!("lex error: {e}"))?; - parse(tokens, src.to_string()).map_err(|e| format!("parse error: {e}")) -} - -/// Run the compiler and return the bytecode artifact bytes. -pub fn compile_ok(src: &str) -> Result, String> { - let out = Compiler::compile(src, CompilerOptions::default()) - .map_err(|e| format!("compile error: {e}"))?; - Ok(out.artifact) -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/activate_typing.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/activate_typing.rs deleted file mode 100644 index 3e98720..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/activate_typing.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Tests that `activate` expressions parse and type-check correctly. -//! -//! `activate TypeName where "semantic query"` is the Engram graph query primitive. -//! It returns `[TypeName]` — an array of matching nodes. The type checker -//! requires `TypeName` to be a defined type in the type environment. - -use crate::{parse_ok, pipeline_ok}; - -// ── Parse tests ─────────────────────────────────────────────────────────────── - -#[test] -fn test_parse_activate_basic() { - let src = r#"let results = activate User where "recent users""#; - assert!(parse_ok(src).is_ok(), "basic activate expression should parse"); -} - -#[test] -fn test_parse_activate_in_let_binding() { - let src = r#"let users = activate User where "all active users""#; - assert!(parse_ok(src).is_ok(), "activate in let binding should parse"); -} - -#[test] -fn test_parse_activate_in_fn_body() { - let src = r#" -fn find_users(query: String) -> [User] { - let results = activate User where "active users" - return results -} -"#; - assert!(parse_ok(src).is_ok(), "activate in function body should parse"); -} - -#[test] -fn test_parse_activate_multiple_in_program() { - let src = r#" -let users = activate User where "all users" -let docs = activate Document where "recent documents" -"#; - assert!(parse_ok(src).is_ok(), "multiple activates should parse"); -} - -#[test] -fn test_parse_activate_with_complex_query() { - let src = r#"let items = activate Product where "top-selling electronics under $500""#; - assert!(parse_ok(src).is_ok(), "activate with complex query string should parse"); -} - -#[test] -fn test_parse_activate_used_in_test_block() { - let src = r#" -test "activate in test" target: unit { - let results = activate User where "test users" - assert true -} -"#; - assert!(parse_ok(src).is_ok(), "activate inside test block should parse"); -} - -// ── Activate AST structure tests ────────────────────────────────────────────── - -#[test] -fn test_activate_parses_to_correct_ast_node() { - use el_parser::{Expr, Stmt}; - - let src = r#"let u = activate User where "query""#; - let prog = parse_ok(src).unwrap(); - assert_eq!(prog.stmts.len(), 1); - - if let Stmt::Let { value, .. } = &prog.stmts[0] { - assert!( - matches!(value, Expr::Activate { type_name, query } - if type_name == "User" && query == "query"), - "expected Activate node" - ); - } else { - panic!("expected Let statement"); - } -} - -#[test] -fn test_activate_type_name_is_preserved() { - use el_parser::{Expr, Stmt}; - - let src = r#"let n = activate NeuralPattern where "dense clusters""#; - let prog = parse_ok(src).unwrap(); - if let Stmt::Let { value, .. } = &prog.stmts[0] { - if let Expr::Activate { type_name, .. } = value { - assert_eq!(type_name, "NeuralPattern"); - } else { - panic!("expected Activate expr"); - } - } else { - panic!("expected Let stmt"); - } -} - -// ── Pipeline tests ──────────────────────────────────────────────────────────── -// The type checker requires the activated type to be defined in the type env. -// These tests define the type before activating it. - -#[test] -fn test_pipeline_activate_defined_type_typechecks() { - let src = r#" -type User { - id: Int - name: String -} -let results = activate User where "all users" -"#; - assert!(pipeline_ok(src).is_ok(), "activate on defined type should pass type checking"); -} - -#[test] -fn test_pipeline_activate_result_used_in_stdlib_call() { - let src = r#" -type User { - id: Int - name: String -} -let results = activate User where "active users" -let count: Int = array_length(results) -"#; - assert!(pipeline_ok(src).is_ok(), "activate result used in stdlib call should type-check"); -} - -#[test] -fn test_pipeline_engram_search_typechecks() { - let src = r#"let items = engram_search("neural patterns", 10)"#; - assert!(pipeline_ok(src).is_ok(), "engram_search should type-check"); -} - -#[test] -fn test_pipeline_engram_node_count_typechecks() { - let src = r#"let n: Int = engram_node_count()"#; - assert!(pipeline_ok(src).is_ok(), "engram_node_count should type-check"); -} - -#[test] -fn test_pipeline_activate_in_fn_with_defined_type() { - let src = r#" -type Document { - id: Int - title: String - content: String -} -fn find_docs(query: String) -> [Document] { - let results = activate Document where "recent documents" - return results -} -"#; - assert!(pipeline_ok(src).is_ok(), "activate in fn with defined type should type-check"); -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/bytecode_structure.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/bytecode_structure.rs deleted file mode 100644 index 31af126..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/bytecode_structure.rs +++ /dev/null @@ -1,1079 +0,0 @@ -//! Bytecode-structure integration tests. -//! -//! These tests compile Engram source all the way through the full pipeline and -//! then inspect the deserialized bytecode to verify that the correct instructions -//! are emitted. This exercises the entire stack: -//! source → lex → parse → type-check → codegen → bytecode serialization → deserialization -//! -//! Strategy: check instruction *presence/absence* (`.iter().any(...)`) rather than -//! exact position — positional tests are fragile to codegen tweaks. - -use el_compiler::{Bytecode, Compiler, CompilerOptions, Target, Value}; -use el_lexer::tokenize; -use el_parser::parse; -use el_seal::{DeploymentBinding, SealAlgorithm, SealConfig, SealedArtifact}; -use el_types::TypeChecker; - -fn deserialize_bytecode(bytes: &[u8]) -> Result, String> { - Bytecode::deserialize_all(bytes) -} - -// ── Shared helpers ──────────────────────────────────────────────────────────── - -fn debug_opts() -> CompilerOptions { - CompilerOptions { target: Target::Debug, ..Default::default() } -} - -fn prod_opts() -> CompilerOptions { - CompilerOptions { - target: Target::Prod, - seal_config: SealConfig { - algorithm: SealAlgorithm::Aes256Gcm, - deployment_binding: DeploymentBinding::None, - }, - ..Default::default() - } -} - -fn compile_debug(src: &str) -> el_compiler::CompileOutput { - Compiler::compile(src, debug_opts()).expect("compile failed") -} - -fn bytecode_of(src: &str) -> Vec { - let out = compile_debug(src); - deserialize_bytecode(&out.artifact).expect("deserialization failed") -} - -fn parse_program(src: &str) -> el_parser::Program { - let tokens = tokenize(src).expect("lex failed"); - parse(tokens, src.to_string()).expect("parse failed") -} - -// ========================================================================= -// Group 1: Basic compilation -// ========================================================================= - -#[test] -fn test_compile_hello_world() { - let src = r#"let msg: String = "Hello""#; - let out = compile_debug(src); - assert!(!out.artifact.is_empty(), "artifact must not be empty"); - assert!(!out.sealed); -} - -#[test] -fn test_compile_arithmetic() { - let bc = bytecode_of("let x = 1 + 2 * 3"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Add)), "must contain ADD"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Mul)), "must contain MUL"); -} - -#[test] -fn test_compile_let_with_type() { - let out = compile_debug("let n: Int = 42"); - assert!(out.diagnostics.is_empty(), "no type errors expected"); - assert!(!out.artifact.is_empty()); -} - -#[test] -fn test_compile_boolean() { - let bc = bytecode_of("let b: Bool = true"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Bool(true))))); -} - -#[test] -fn test_compile_float() { - let bc = bytecode_of("let pi: Float = 3.14"); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Push(Value::Float(f)) if (*f - 3.14).abs() < 1e-9))); -} - -#[test] -fn test_compile_string_literal() { - let bc = bytecode_of(r#"let s = "hello world""#); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Push(Value::Str(s)) if s == "hello world"))); -} - -#[test] -fn test_compile_empty_program() { - let bc = bytecode_of(""); - assert!( - matches!(bc.last(), Some(Bytecode::Halt)), - "empty program must end with HALT" - ); -} - -#[test] -fn test_compile_multiple_lets() { - let src = "let a = 1\nlet b = 2\nlet c = 3"; - let bc = bytecode_of(src); - let stores: Vec<_> = bc - .iter() - .filter(|b| matches!(b, Bytecode::StoreLocal(_))) - .collect(); - assert!(stores.len() >= 3, "expected at least 3 StoreLocal instructions"); -} - -#[test] -fn test_compile_debug_has_source_map() { - let out = compile_debug("let x = 1"); - assert!(out.source_map.is_some(), "debug target must produce a source map"); -} - -#[test] -fn test_compile_release_no_source_map() { - let out = Compiler::compile("let x = 42", CompilerOptions { target: Target::Release, ..Default::default() }) - .expect("compile failed"); - assert!(out.source_map.is_none()); - assert!(!out.sealed); - assert!(!out.artifact.is_empty()); -} - -#[test] -fn test_artifact_is_valid_json_debug() { - let out = compile_debug("let x = 1"); - let parsed: serde_json::Value = - serde_json::from_slice(&out.artifact).expect("debug artifact must be valid JSON"); - assert!(parsed.is_array(), "debug artifact JSON must be an array"); -} - -#[test] -fn test_artifact_is_valid_json_release() { - let out = Compiler::compile("let x = 1", CompilerOptions { target: Target::Release, ..Default::default() }) - .expect("compile failed"); - let parsed: serde_json::Value = - serde_json::from_slice(&out.artifact).expect("release artifact must be valid JSON"); - assert!(parsed.is_array(), "release artifact JSON must be an array"); -} - -// ========================================================================= -// Group 2: Functions -// ========================================================================= - -#[test] -fn test_fn_def_compiles() { - let src = r#" -fn greet() -> String { - return "hello" -} -"#; - let out = compile_debug(src); - assert!(!out.artifact.is_empty()); -} - -#[test] -fn test_fn_with_params() { - let src = r#" -fn add(a: Int, b: Int) -> Int { - return a + b -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Return))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Add))); -} - -#[test] -fn test_fn_call_emits_call_instruction() { - let src = r#" -fn double(x: Int) -> Int { - return x + x -} -let result = double(21) -"#; - let bc = bytecode_of(src); - assert!( - bc.iter() - .any(|b| matches!(b, Bytecode::Call { name, .. } if name == "double")), - "expected Call {{ name: \"double\", .. }}" - ); -} - -#[test] -fn test_fn_return_type() { - let src = r#" -fn get_answer() -> Int { - return 42 -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(42))))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Return))); -} - -#[test] -fn test_fn_recursive() { - let src = r#" -fn fib(n: Int) -> Int { - if n <= 1 { - return n - } else { - return fib(n - 1) + fib(n - 2) - } -} -"#; - let bc = bytecode_of(src); - assert!( - bc.iter() - .any(|b| matches!(b, Bytecode::Call { name, .. } if name == "fib")), - "expected recursive Call to fib" - ); -} - -#[test] -fn test_fn_entry_point_stored() { - let src = r#"fn add(a: Int, b: Int) -> Int { return a + b }"#; - let bc = bytecode_of(src); - // The codegen stores the function entry-point offset as __fn_ - assert!( - bc.iter() - .any(|b| matches!(b, Bytecode::StoreLocal(n) if n == "__fn_add")), - "expected StoreLocal(__fn_add) as entry point marker" - ); -} - -#[test] -fn test_fn_multiple_defs() { - let src = r#" -fn foo() -> Int { return 1 } -fn bar() -> Int { return 2 } -"#; - let bc = bytecode_of(src); - let returns: Vec<_> = bc.iter().filter(|b| matches!(b, Bytecode::Return)).collect(); - assert!(returns.len() >= 2, "expected at least 2 Return instructions"); -} - -// ========================================================================= -// Group 3: Control flow -// ========================================================================= - -#[test] -fn test_if_expr() { - let src = "let y = if true { 1 } else { 2 }"; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::JumpIfNot(_)))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Jump(_)))); -} - -#[test] -fn test_if_no_else() { - let src = "if true { let x = 1 }"; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::JumpIfNot(_)))); -} - -#[test] -fn test_match_literal() { - let src = r#" -let n = 1 -let result = match n { - 1 => "one" - 2 => "two" - _ => "other" -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Eq))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::JumpIfNot(_)))); -} - -#[test] -fn test_match_wildcard() { - let src = r#" -let x = 5 -let r = match x { - _ => "anything" -} -"#; - let bc = bytecode_of(src); - // Wildcard pops the subject before running the body - assert!(bc.iter().any(|b| matches!(b, Bytecode::Pop))); -} - -#[test] -fn test_nested_if() { - let src = r#" -let a = true -let b = false -let r = if a { if b { 1 } else { 2 } } else { 3 } -"#; - let bc = bytecode_of(src); - let jumps: Vec<_> = bc - .iter() - .filter(|b| matches!(b, Bytecode::JumpIfNot(_))) - .collect(); - assert!(jumps.len() >= 2, "expected at least 2 JumpIfNot for nested if"); -} - -#[test] -fn test_if_condition_uses_eq() { - let src = "let v = if 1 == 1 { 10 } else { 20 }"; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Eq))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::JumpIfNot(_)))); -} - -// ========================================================================= -// Group 4: Types — type-checker API -// ========================================================================= - -#[test] -fn test_struct_def_no_errors() { - let src = r#" -type User { - name: String - age: Int -} -"#; - let out = compile_debug(src); - assert!(out.diagnostics.is_empty(), "struct def should have no type errors"); -} - -#[test] -fn test_enum_def_no_errors() { - let src = r#" -enum Status { - Active - Inactive - Pending(String) -} -"#; - let out = compile_debug(src); - assert!(out.diagnostics.is_empty(), "enum def should have no type errors"); -} - -#[test] -fn test_type_mismatch_diagnostic() { - let src = r#"let x: Int = "not an int""#; - let out = compile_debug(src); - assert!( - !out.diagnostics.is_empty(), - "type mismatch should produce at least one diagnostic" - ); -} - -#[test] -fn test_named_type_in_fn_param() { - let src = r#" -type Point { - x: Int - y: Int -} -fn get_x(p: Point) -> Int { - return p.x -} -"#; - let out = compile_debug(src); - assert!(!out.artifact.is_empty()); -} - -#[test] -fn test_array_type() { - let src = "let xs: [Int] = [1, 2, 3]"; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(1))))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(2))))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(3))))); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Call { name, .. } if name == "__build_list__"))); -} - -#[test] -fn test_optional_type_annotation_accepted() { - let src = "let maybe: Int? = 0"; - let out = compile_debug(src); - assert!(!out.artifact.is_empty()); -} - -#[test] -fn test_type_checker_ok_on_valid_program() { - let src = "let x: Int = 10"; - let prog = parse_program(src); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - assert!(checker.ok(), "valid program should type-check without errors"); -} - -#[test] -fn test_type_checker_error_on_mismatch() { - let src = r#"let x: Int = "oops""#; - let prog = parse_program(src); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - assert!(!checker.ok(), "type mismatch should be an error"); - assert!(!checker.diagnostics.is_empty()); -} - -#[test] -fn test_typecheck_struct_fields_resolved() { - let src = r#" -type Person { - name: String - age: Int -} -"#; - let prog = parse_program(src); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - assert!( - !checker.diagnostics.iter().any(|d| d.is_error), - "struct with known field types should have no errors" - ); -} - -#[test] -fn test_typecheck_enum_variants_resolved() { - let src = r#" -enum Mood { - Happy - Sad - Neutral -} -"#; - let prog = parse_program(src); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - assert!( - !checker.diagnostics.iter().any(|d| d.is_error), - "simple enum def should have no errors" - ); -} - -// ========================================================================= -// Group 5: Expressions -// ========================================================================= - -#[test] -fn test_binary_ops_all() { - let cases: &[(&str, fn(&Bytecode) -> bool)] = &[ - ("1 + 2", |b| matches!(b, Bytecode::Add)), - ("3 - 1", |b| matches!(b, Bytecode::Sub)), - ("2 * 3", |b| matches!(b, Bytecode::Mul)), - ("6 / 2", |b| matches!(b, Bytecode::Div)), - ]; - for (src, pred) in cases { - let bc = bytecode_of(src); - assert!(bc.iter().any(pred), "bytecode for `{src}` missing expected op"); - } -} - -#[test] -fn test_comparison_ops() { - let cases: &[(&str, fn(&Bytecode) -> bool)] = &[ - ("1 == 1", |b| matches!(b, Bytecode::Eq)), - ("1 != 2", |b| matches!(b, Bytecode::NotEq)), - ("1 < 2", |b| matches!(b, Bytecode::Lt)), - ("2 > 1", |b| matches!(b, Bytecode::Gt)), - ("1 <= 1", |b| matches!(b, Bytecode::LtEq)), - ("1 >= 1", |b| matches!(b, Bytecode::GtEq)), - ]; - for (src, pred) in cases { - let bc = bytecode_of(src); - assert!(bc.iter().any(pred), "bytecode for `{src}` missing comparison op"); - } -} - -#[test] -fn test_logical_ops() { - let bc_and = bytecode_of("true && false"); - assert!(bc_and.iter().any(|b| matches!(b, Bytecode::And))); - - let bc_or = bytecode_of("true || false"); - assert!(bc_or.iter().any(|b| matches!(b, Bytecode::Or))); -} - -#[test] -fn test_unary_not() { - let bc = bytecode_of("!true"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Not))); -} - -#[test] -fn test_array_literal_arity() { - let bc = bytecode_of("[1, 2, 3]"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(1))))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(2))))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(3))))); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Call { name, arity } if name == "__build_list__" && *arity == 3))); -} - -#[test] -fn test_empty_array_literal() { - let bc = bytecode_of("let empty = []"); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Call { name, arity } if name == "__build_list__" && *arity == 0))); -} - -#[test] -fn test_block_expr() { - let src = r#" -let result = { - let a = 1 - let b = 2 - a + b -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Add))); -} - -#[test] -fn test_field_access() { - let src = r#" -type Point { x: Int y: Int } -fn get_x(p: Point) -> Int { - return p.x -} -"#; - let bc = bytecode_of(src); - assert!( - bc.iter() - .any(|b| matches!(b, Bytecode::GetField(f) if f == "x")), - "expected GetField(\"x\")" - ); -} - -#[test] -fn test_index_expr() { - let src = r#" -fn first(xs: [Int]) -> Int { - return xs[0] -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::GetIndex))); -} - -#[test] -fn test_path_expr_emits_str_push() { - let src = r#" -enum Color { Red Green Blue } -let c = Color::Red -"#; - let bc = bytecode_of(src); - assert!( - bc.iter() - .any(|b| matches!(b, Bytecode::Push(Value::Str(s)) if s == "Red")), - "expected Push(Str(\"Red\")) for path expr" - ); -} - -#[test] -fn test_chained_arithmetic() { - let bc = bytecode_of("1 + 2 + 3 + 4"); - let add_count = bc.iter().filter(|b| matches!(b, Bytecode::Add)).count(); - assert!(add_count >= 3, "expected at least 3 ADD instructions for 1+2+3+4"); -} - -#[test] -fn test_subtraction_emits_sub() { - let bc = bytecode_of("10 - 3"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Sub))); -} - -#[test] -fn test_division_emits_div() { - let bc = bytecode_of("100 / 4"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Div))); -} - -// ========================================================================= -// Group 6: Activate construct -// ========================================================================= - -#[test] -fn test_activate_emits_activate_instruction() { - let src = r#"activate User where "find admins""#; - let bc = bytecode_of(src); - assert!( - bc.iter().any(|b| matches!( - b, - Bytecode::Activate { type_name, query } - if type_name == "User" && query == "find admins" - )), - "expected Activate {{ type_name: \"User\", query: \"find admins\" }}" - ); -} - -#[test] -fn test_activate_type_name_preserved() { - let src = r#"activate Document where "recent docs""#; - let bc = bytecode_of(src); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Activate { type_name, .. } if type_name == "Document"))); -} - -#[test] -fn test_activate_query_preserved() { - let src = r#"activate Node where "high importance nodes""#; - let bc = bytecode_of(src); - assert!(bc.iter().any( - |b| matches!(b, Bytecode::Activate { query, .. } if query == "high importance nodes") - )); -} - -#[test] -fn test_activate_in_fn_body() { - let src = r#" -fn fetch_users() -> String { - let result = activate User where "all users" - return "done" -} -"#; - let bc = bytecode_of(src); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Activate { type_name, .. } if type_name == "User"))); -} - -#[test] -fn test_activate_assigned_to_let() { - let src = r#"let result = activate Node where "query""#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Activate { .. }))); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::StoreLocal(n) if n == "result"))); -} - -// ========================================================================= -// Group 7: Sealed blocks -// ========================================================================= - -#[test] -fn test_sealed_emits_markers() { - let src = "sealed { let x = 1 }"; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedBegin))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedEnd))); -} - -#[test] -fn test_sealed_begin_before_end() { - let src = "sealed { let secret = 42 }"; - let bc = bytecode_of(src); - let begin_pos = bc - .iter() - .position(|b| matches!(b, Bytecode::SealedBegin)) - .expect("SealedBegin not found"); - let end_pos = bc - .iter() - .position(|b| matches!(b, Bytecode::SealedEnd)) - .expect("SealedEnd not found"); - assert!(begin_pos < end_pos, "SealedBegin must appear before SealedEnd"); -} - -#[test] -fn test_sealed_in_fn() { - let src = r#" -fn secure() -> String { - sealed { let key = "secret" } - return "done" -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedBegin))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedEnd))); -} - -#[test] -fn test_prod_sealed_encryption() { - let src = "let x = 1"; - let out = Compiler::compile(src, prod_opts()).expect("prod compile failed"); - assert!(out.sealed); - assert_eq!(&out.artifact[..8], b"ENGRAM01", "artifact must start with ENGRAM01 magic"); -} - -#[test] -fn test_sealed_body_content_compiled() { - // The sealed block body should still be compiled — the int 10 is pushed inside the sealed region - let src = "sealed { let a = 10 }"; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Int(10))))); -} - -// ========================================================================= -// Group 8: Full pipeline type checking -// ========================================================================= - -#[test] -fn test_typecheck_fn_return_mismatch_debug_compiles() { - // In debug mode, type errors are non-fatal — we get diagnostics but still compile - let src = r#" -fn bad() -> Int { - return "not an int" -} -"#; - let out = compile_debug(src); - assert!(!out.artifact.is_empty(), "should still produce artifact in debug mode"); -} - -#[test] -fn test_typecheck_undefined_type_produces_diagnostic() { - let src = r#"let x: NonExistentType = 0"#; - let prog = parse_program(src); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - assert!( - !checker.diagnostics.is_empty() || !checker.ok(), - "undefined type should produce diagnostics" - ); -} - -#[test] -fn test_typecheck_array_homogeneous_accepted() { - let src = "let xs: [Int] = [1, 2, 3]"; - let prog = parse_program(src); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - assert!(!prog.stmts.is_empty()); -} - -#[test] -fn test_typecheck_fn_signature_registered() { - let src = r#" -fn square(n: Int) -> Int { - return n * n -} -let r = square(5) -"#; - let prog = parse_program(src); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - assert!(!prog.stmts.is_empty()); -} - -// ========================================================================= -// Group 9: Real programs — multi-statement programs -// ========================================================================= - -#[test] -fn test_program_fibonacci() { - let src = r#" -fn fib(n: Int) -> Int { - if n <= 1 { - return n - } else { - return fib(n - 1) + fib(n - 2) - } -} -let answer = fib(10) -"#; - let out = compile_debug(src); - assert!(out.diagnostics.is_empty(), "fibonacci should have no type errors"); - let bc = deserialize_bytecode(&out.artifact).unwrap(); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Call { name, .. } if name == "fib"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::LtEq))); -} - -#[test] -fn test_program_http_handler() { - let src = r#" -fn handle_request(path: String) -> String { - if path == "/" { - return "index" - } else { - return "not found" - } -} -fn http_serve() -> String { - return handle_request("/") -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Call { name, .. } if name == "handle_request"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Eq))); -} - -#[test] -fn test_program_types_and_activate() { - let src = r#" -type User { - name: String - age: Int -} -let users = activate User where "all active users" -"#; - let bc = bytecode_of(src); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Activate { type_name, .. } if type_name == "User"))); -} - -#[test] -fn test_program_enum_match() { - let src = r#" -enum Direction { - North - South - East - West -} -let d = Direction::North -let label = match d { - "North" => "up" - "South" => "down" - _ => "side" -} -"#; - let bc = bytecode_of(src); - // Path expr pushes the variant name as a Str - assert!(bc.iter().any(|b| matches!(b, Bytecode::Push(Value::Str(s)) if s == "North"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Eq))); -} - -#[test] -fn test_program_multi_function_call_chain() { - let src = r#" -fn add_one(n: Int) -> Int { return n + 1 } -fn double(n: Int) -> Int { return n + n } -fn transform(n: Int) -> Int { return double(add_one(n)) } -let result = transform(5) -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Call { name, .. } if name == "transform"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Call { name, .. } if name == "double"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Call { name, .. } if name == "add_one"))); -} - -#[test] -fn test_program_sealed_with_activate() { - let src = r#" -sealed { - let secret_query = activate SecretNode where "classified" -} -"#; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedBegin))); - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Activate { type_name, .. } if type_name == "SecretNode"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::SealedEnd))); -} - -// ========================================================================= -// Group 10: Prod target and sealing -// ========================================================================= - -#[test] -fn test_prod_magic_bytes() { - let out = Compiler::compile("let x = 1", prod_opts()).expect("prod compile failed"); - assert!(out.artifact.len() >= 8, "prod artifact must be at least 8 bytes"); - assert_eq!(&out.artifact[..8], b"ENGRAM01", "prod artifact must start with ENGRAM01"); -} - -#[test] -fn test_prod_roundtrip() { - let src = "let answer: Int = 42"; - let out = Compiler::compile(src, prod_opts()).expect("prod compile failed"); - let sealed = SealedArtifact::from_bytes(&out.artifact).expect("from_bytes failed"); - let bytecode_bytes = el_seal::unseal(&sealed, &[]).expect("unseal failed"); - let instructions = deserialize_bytecode(&bytecode_bytes).expect("deserialization failed"); - assert!( - instructions - .iter() - .any(|b| matches!(b, Bytecode::Push(Value::Int(42)))), - "roundtripped bytecode must contain Push(42)" - ); -} - -#[test] -fn test_prod_type_error_is_fatal() { - let src = r#"let x: Int = "not an int""#; - let result = Compiler::compile(src, prod_opts()); - assert!(result.is_err(), "prod mode must fail when there are type errors"); -} - -#[test] -fn test_prod_sealed_flag() { - let out = Compiler::compile("let x = 0", prod_opts()).expect("prod compile failed"); - assert!(out.sealed, "prod output must be marked as sealed"); -} - -#[test] -fn test_prod_no_source_map() { - let out = Compiler::compile("let x = 1", prod_opts()).expect("prod compile failed"); - assert!(out.source_map.is_none(), "prod output must not contain a source map"); -} - -#[test] -fn test_prod_roundtrip_fn_def() { - let src = r#" -fn square(n: Int) -> Int { - return n * n -} -"#; - let out = Compiler::compile(src, prod_opts()).expect("prod compile failed"); - let sealed = SealedArtifact::from_bytes(&out.artifact).expect("from_bytes failed"); - let bc_bytes = el_seal::unseal(&sealed, &[]).expect("unseal failed"); - let instructions = deserialize_bytecode(&bc_bytes).expect("deserialization failed"); - assert!(instructions.iter().any(|b| matches!(b, Bytecode::Mul))); - assert!(instructions.iter().any(|b| matches!(b, Bytecode::Return))); -} - -#[test] -fn test_prod_roundtrip_sealed_block() { - let src = "sealed { let key = 99 }"; - let out = Compiler::compile(src, prod_opts()).expect("prod compile failed"); - let sealed = SealedArtifact::from_bytes(&out.artifact).expect("from_bytes failed"); - let bc_bytes = el_seal::unseal(&sealed, &[]).expect("unseal failed"); - let instructions = deserialize_bytecode(&bc_bytes).expect("deserialization failed"); - assert!(instructions.iter().any(|b| matches!(b, Bytecode::SealedBegin))); - assert!(instructions - .iter() - .any(|b| matches!(b, Bytecode::Push(Value::Int(99))))); - assert!(instructions.iter().any(|b| matches!(b, Bytecode::SealedEnd))); -} - -// ========================================================================= -// Group 11: Bytecode structure invariants -// ========================================================================= - -#[test] -fn test_halt_always_last() { - let cases = [ - "let x = 1", - "42", - "fn f() -> Int { return 1 }", - r#"let s = "hi""#, - ]; - for src in &cases { - let bc = bytecode_of(src); - assert!( - matches!(bc.last(), Some(Bytecode::Halt)), - "bytecode for `{src}` must end with HALT" - ); - } -} - -#[test] -fn test_store_load_roundtrip_in_bytecode() { - let src = "let x = 5\nlet y = x"; - let bc = bytecode_of(src); - assert!(bc.iter().any(|b| matches!(b, Bytecode::StoreLocal(n) if n == "x"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::LoadLocal(n) if n == "x"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::StoreLocal(n) if n == "y"))); -} - -#[test] -fn test_not_eq_emits_noteq() { - let bc = bytecode_of("1 != 2"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::NotEq))); -} - -#[test] -fn test_lt_emits_lt() { - let bc = bytecode_of("1 < 2"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Lt))); -} - -#[test] -fn test_gt_emits_gt() { - let bc = bytecode_of("2 > 1"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Gt))); -} - -#[test] -fn test_lteq_emits_lteq() { - let bc = bytecode_of("1 <= 1"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::LtEq))); -} - -#[test] -fn test_gteq_emits_gteq() { - let bc = bytecode_of("2 >= 1"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::GtEq))); -} - -#[test] -fn test_and_emits_and() { - let bc = bytecode_of("true && false"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::And))); -} - -#[test] -fn test_or_emits_or() { - let bc = bytecode_of("true || false"); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Or))); -} - -#[test] -fn test_diagnostics_are_non_empty_strings() { - let src = r#"let x: Int = "bad""#; - let out = compile_debug(src); - for d in &out.diagnostics { - assert!(!d.is_empty(), "diagnostic messages must not be empty strings"); - } -} - -#[test] -fn test_parse_produces_correct_stmt_count() { - let src = "let x = 1"; - let prog = parse_program(src); - assert_eq!(prog.stmts.len(), 1); - assert_eq!(prog.source, src); -} - -#[test] -fn test_parse_fn_stmt_is_fn_def() { - let src = r#"fn f() -> Int { return 1 }"#; - let prog = parse_program(src); - assert_eq!(prog.stmts.len(), 1); - assert!(matches!(&prog.stmts[0], el_parser::Stmt::FnDef { name, .. } if name == "f")); -} - -#[test] -fn test_parse_multiple_stmts_count() { - let src = "let a = 1\nlet b = 2\nlet c = 3"; - let prog = parse_program(src); - assert_eq!(prog.stmts.len(), 3); -} - -#[test] -fn test_compile_multiple_lets_is_ok() { - let src = r#" -let a: Int = 1 -let b: Int = 2 -let c: Int = a + b -"#; - let out = compile_debug(src); - assert!(!out.artifact.is_empty()); - assert!(out.diagnostics.is_empty()); -} - -#[test] -fn test_jump_if_not_offset_is_nonzero_for_if() { - // A real if expression must jump past at least one instruction - let src = "if true { 1 } else { 2 }"; - let bc = bytecode_of(src); - let has_nonzero_jump = bc.iter().any(|b| match b { - Bytecode::JumpIfNot(off) => *off != 0, - _ => false, - }); - assert!(has_nonzero_jump, "JumpIfNot offset must be non-zero for real if"); -} - -#[test] -fn test_match_string_pattern_emits_str_push() { - let src = r#" -let x = "hello" -let r = match x { - "hello" => 1 - _ => 0 -} -"#; - let bc = bytecode_of(src); - // Pattern literal "hello" is pushed for comparison - assert!(bc - .iter() - .any(|b| matches!(b, Bytecode::Push(Value::Str(s)) if s == "hello"))); - assert!(bc.iter().any(|b| matches!(b, Bytecode::Eq))); -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/compiler_pipeline.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/compiler_pipeline.rs deleted file mode 100644 index 2f8086e..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/compiler_pipeline.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Full pipeline tests: source → lex → parse → type-check → compile. - -use crate::{compile_ok, parse_ok, pipeline_ok}; - -// ── Parse-only smoke tests ──────────────────────────────────────────────────── - -#[test] -fn test_parse_let_int() { - assert!(parse_ok("let x: Int = 42").is_ok()); -} - -#[test] -fn test_parse_let_string() { - assert!(parse_ok(r#"let name: String = "hello""#).is_ok()); -} - -#[test] -fn test_parse_let_bool() { - assert!(parse_ok("let flag: Bool = true").is_ok()); -} - -#[test] -fn test_parse_fn_def() { - let src = r#" -fn add(a: Int, b: Int) -> Int { - return a + b -} -"#; - assert!(parse_ok(src).is_ok()); -} - -#[test] -fn test_parse_if_else() { - let src = r#" -fn abs(x: Int) -> Int { - if x < 0 { - return 0 - x - } else { - return x - } -} -"#; - assert!(parse_ok(src).is_ok()); -} - -#[test] -fn test_parse_type_def() { - let src = r#" -type User { - id: Int - name: String - email: String -} -"#; - assert!(parse_ok(src).is_ok()); -} - -#[test] -fn test_parse_enum_def() { - let src = r#" -enum Status { - Active - Inactive - Pending -} -"#; - assert!(parse_ok(src).is_ok()); -} - -#[test] -fn test_parse_match_expr() { - let src = r#" -fn describe(s: Status) -> String { - match s { - Status::Active => "active" - Status::Inactive => "inactive" - _ => "unknown" - } -} -"#; - assert!(parse_ok(src).is_ok()); -} - -#[test] -fn test_parse_array_literal() { - assert!(parse_ok("let xs: [Int] = [1, 2, 3]").is_ok()); -} - -#[test] -fn test_parse_nested_fn_calls() { - let src = r#" -fn double(x: Int) -> Int { - return x * 2 -} -fn quad(x: Int) -> Int { - return double(double(x)) -} -"#; - assert!(parse_ok(src).is_ok()); -} - -// ── Pipeline (lex + parse + type-check) tests ───────────────────────────────── - -#[test] -fn test_pipeline_hello_world() { - assert!(pipeline_ok(r#"let msg: String = "Hello, World!""#).is_ok()); -} - -#[test] -fn test_pipeline_arithmetic() { - assert!(pipeline_ok("let result: Int = 3 + 4 * 2").is_ok()); -} - -#[test] -fn test_pipeline_fn_def_and_call() { - let src = r#" -fn square(n: Int) -> Int { - return n * n -} -let s: Int = square(5) -"#; - assert!(pipeline_ok(src).is_ok()); -} - -#[test] -fn test_pipeline_bool_logic() { - assert!(pipeline_ok("let ok: Bool = true && false").is_ok()); - assert!(pipeline_ok("let ok: Bool = true || false").is_ok()); -} - -#[test] -fn test_pipeline_float_literal() { - assert!(pipeline_ok("let pi: Float = 3.14").is_ok()); -} - -#[test] -fn test_pipeline_string_trim_via_call() { - // string_trim is registered as a stdlib builtin - let src = r#"let s: String = string_trim(" hello ")"#; - assert!(pipeline_ok(src).is_ok()); -} - -// ── Compile tests (full artifact generation) ────────────────────────────────── - -#[test] -fn test_compile_integer_literal() { - let artifact = compile_ok("let x: Int = 99").unwrap(); - assert!(!artifact.is_empty()); -} - -#[test] -fn test_compile_fn_def() { - let src = r#" -fn greet(name: String) -> String { - return name -} -"#; - let artifact = compile_ok(src).unwrap(); - assert!(!artifact.is_empty()); -} - -#[test] -fn test_compile_if_else() { - let src = r#" -fn max_val(a: Int, b: Int) -> Int { - if a > b { - return a - } else { - return b - } -} -"#; - let artifact = compile_ok(src).unwrap(); - assert!(!artifact.is_empty()); -} - -#[test] -fn test_compile_produces_valid_json_artifact() { - let artifact = compile_ok("let x: Int = 1").unwrap(); - // Debug target produces JSON-serialized bytecode - let parsed: serde_json::Value = serde_json::from_slice(&artifact).unwrap(); - assert!(parsed.is_array()); -} - -#[test] -fn test_compile_multiple_stmts() { - let src = r#" -let a: Int = 1 -let b: Int = 2 -let c: Int = a + b -"#; - let artifact = compile_ok(src).unwrap(); - assert!(!artifact.is_empty()); -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/decorator_codegen.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/decorator_codegen.rs deleted file mode 100644 index d8820b5..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/decorator_codegen.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! Tests that decorator-annotated functions parse and compile correctly. -//! -//! Decorators are metadata — they do not change compilation semantics in -//! the current implementation. The compiler emits identical bytecode whether -//! or not a function is decorated. - -use crate::{compile_ok, parse_ok, pipeline_ok}; - -// ── Parse tests ─────────────────────────────────────────────────────────────── - -#[test] -fn test_parse_authenticate_decorator() { - let src = r#" -@authenticate -fn get_user(id: Int) -> String { - return "user" -} -"#; - assert!(parse_ok(src).is_ok(), "@authenticate decorator should parse"); -} - -#[test] -fn test_parse_public_decorator() { - let src = r#" -@public -fn health_check() -> Bool { - return true -} -"#; - assert!(parse_ok(src).is_ok(), "@public decorator should parse"); -} - -#[test] -fn test_parse_cache_decorator_with_args() { - let src = r#" -@cache(300) -fn get_config(key: String) -> String { - return key -} -"#; - assert!(parse_ok(src).is_ok(), "@cache(ttl) decorator should parse"); -} - -#[test] -fn test_parse_multiple_decorators() { - let src = r#" -@authenticate -@public -fn list_items() -> [String] { - return ["a", "b"] -} -"#; - assert!(parse_ok(src).is_ok(), "multiple decorators should parse"); -} - -#[test] -fn test_parse_decorator_with_string_arg() { - let src = r#" -@route("/api/users") -fn list_users() -> [String] { - return ["alice", "bob"] -} -"#; - assert!(parse_ok(src).is_ok(), "@route decorator with string arg should parse"); -} - -#[test] -fn test_parse_decorator_preserves_fn_body() { - let src = r#" -@authenticate -fn add(a: Int, b: Int) -> Int { - return a + b -} -"#; - let prog = parse_ok(src).unwrap(); - // There is exactly one top-level statement (the fn def) - assert_eq!(prog.stmts.len(), 1); -} - -// ── Pipeline tests ──────────────────────────────────────────────────────────── - -#[test] -fn test_pipeline_decorated_fn_typechecks() { - let src = r#" -@authenticate -fn secure_op(id: Int) -> Bool { - return true -} -"#; - assert!(pipeline_ok(src).is_ok(), "decorated fn should type-check"); -} - -#[test] -fn test_pipeline_multiple_decorators_typechecks() { - let src = r#" -@authenticate -@cache(60) -fn get_profile(id: Int) -> String { - return "profile" -} -"#; - assert!(pipeline_ok(src).is_ok(), "multiply-decorated fn should type-check"); -} - -// ── Compile tests ───────────────────────────────────────────────────────────── - -#[test] -fn test_compile_decorated_fn_produces_artifact() { - let src = r#" -@authenticate -fn whoami() -> String { - return "me" -} -"#; - let artifact = compile_ok(src).unwrap(); - assert!(!artifact.is_empty(), "decorated fn should produce bytecode artifact"); -} - -#[test] -fn test_compile_decorator_does_not_change_bytecode_semantics() { - // A decorated function and an identical undecorated function should both - // compile without errors and produce non-empty artifacts. - let decorated = r#" -@public -fn value() -> Int { - return 42 -} -"#; - let plain = r#" -fn value() -> Int { - return 42 -} -"#; - let art_dec = compile_ok(decorated).unwrap(); - let art_plain = compile_ok(plain).unwrap(); - // Both should compile to non-empty artifacts - assert!(!art_dec.is_empty()); - assert!(!art_plain.is_empty()); -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/error_propagation.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/error_propagation.rs deleted file mode 100644 index 2279a1d..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/error_propagation.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! Tests for Result type annotation, the `?` try operator, and closures. - -use crate::{parse_ok, pipeline_ok}; - -// ── Result type annotation parsing ───────────────────────────────────── - -#[test] -fn test_parse_result_return_type() { - let src = r#" -fn divide(a: Int, b: Int) -> Result { - return a -} -"#; - assert!(parse_ok(src).is_ok(), "Result return type should parse"); -} - -#[test] -fn test_parse_result_in_let_binding() { - let src = r#" -fn parse_int(s: String) -> Result { - return 0 -} -"#; - assert!(parse_ok(src).is_ok(), "Result in function signature should parse"); -} - -#[test] -fn test_parse_result_with_complex_types() { - let src = r#" -fn fetch_user(id: Int) -> Result { - return "user" -} -"#; - assert!(parse_ok(src).is_ok(), "Result should parse"); -} - -#[test] -fn test_parse_nested_result() { - let src = r#" -fn complex_op() -> Result, String> { - return 0 -} -"#; - assert!(parse_ok(src).is_ok(), "nested Result types should parse"); -} - -// ── Try operator (`?`) parsing ──────────────────────────────────────────────── - -#[test] -fn test_parse_try_operator_on_call() { - let src = r#" -fn safe_div(a: Int, b: Int) -> Result { - return a -} -fn compute() -> Result { - let x: Int = safe_div(10, 2)? - return x -} -"#; - assert!(parse_ok(src).is_ok(), "? operator on function call should parse"); -} - -#[test] -fn test_parse_try_operator_on_variable() { - let src = r#" -fn process(result: Result) -> Result { - let value: Int = result? - return value -} -"#; - assert!(parse_ok(src).is_ok(), "? operator on variable should parse"); -} - -#[test] -fn test_parse_chained_try_operators() { - let src = r#" -fn step1() -> Result { return 1 } -fn step2(n: Int) -> Result { return "ok" } -fn pipeline() -> Result { - let n: Int = step1()? - let s: String = step2(n)? - return s -} -"#; - assert!(parse_ok(src).is_ok(), "chained ? operators should parse"); -} - -// ── Optional type (`T?`) parsing ────────────────────────────────────────────── - -#[test] -fn test_parse_optional_return_type() { - let src = r#" -fn find(id: Int) -> String? { - return "user" -} -"#; - assert!(parse_ok(src).is_ok(), "Optional return type T? should parse"); -} - -#[test] -fn test_parse_optional_parameter() { - let src = r#" -fn greet(name: String?) -> String { - return "hello" -} -"#; - assert!(parse_ok(src).is_ok(), "Optional parameter type T? should parse"); -} - -#[test] -fn test_parse_optional_in_let_binding() { - let src = r#" -fn maybe_val() -> Int? { - return 42 -} -"#; - assert!(parse_ok(src).is_ok(), "Optional in let binding should parse"); -} - -// ── Closure parsing ─────────────────────────────────────────────────────────── - -#[test] -fn test_parse_simple_closure() { - let src = r#"let double = |x: Int| x * 2"#; - assert!(parse_ok(src).is_ok(), "simple closure should parse"); -} - -#[test] -fn test_parse_closure_with_return_type() { - let src = r#"let double = |x: Int| -> Int { return x * 2 }"#; - assert!(parse_ok(src).is_ok(), "closure with explicit return type should parse"); -} - -#[test] -fn test_parse_closure_with_multiple_params() { - let src = r#"let add = |a: Int, b: Int| a + b"#; - assert!(parse_ok(src).is_ok(), "multi-param closure should parse"); -} - -#[test] -fn test_parse_closure_single_param_no_body_type() { - // Closure with one param and inferred return type - let src = r#"let inc = |n: Int| n + 1"#; - assert!(parse_ok(src).is_ok(), "single-param closure with inferred return should parse"); -} - -#[test] -fn test_parse_closure_with_block_body() { - let src = r#" -let compute = |x: Int| -> Int { - let y: Int = x * 2 - return y + 1 -} -"#; - assert!(parse_ok(src).is_ok(), "closure with block body should parse"); -} - -// ── Pipeline tests ──────────────────────────────────────────────────────────── - -#[test] -fn test_pipeline_result_return_type_typechecks() { - let src = r#" -fn safe_op(x: Int) -> Result { - return x -} -"#; - assert!(pipeline_ok(src).is_ok(), "Result return type should type-check"); -} - -#[test] -fn test_pipeline_optional_return_type_typechecks() { - let src = r#" -fn maybe(x: Int) -> Int? { - return x -} -"#; - assert!(pipeline_ok(src).is_ok(), "Optional return type should type-check"); -} - -#[test] -fn test_pipeline_closure_typechecks() { - let src = r#"let inc = |n: Int| n + 1"#; - assert!(pipeline_ok(src).is_ok(), "closure expression should type-check"); -} - -#[test] -fn test_pipeline_try_operator_typechecks() { - let src = r#" -fn maybe_int() -> Result { - return 1 -} -fn compute() -> Result { - let x: Int = maybe_int()? - return x -} -"#; - assert!(pipeline_ok(src).is_ok(), "? operator should type-check"); -} - -#[test] -fn test_pipeline_result_stdlib_unwrap_or() { - let src = r#" -fn safe_op(x: Int) -> Result { - return x -} -let val: Int = result_unwrap_or(safe_op(5), 0) -"#; - assert!(pipeline_ok(src).is_ok(), "result_unwrap_or should type-check"); -} - -#[test] -fn test_pipeline_optional_stdlib_is_some() { - let src = r#" -fn maybe(x: Int) -> Int? { - return x -} -let val: Bool = optional_is_some(maybe(3)) -"#; - assert!(pipeline_ok(src).is_ok(), "optional_is_some should type-check"); -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/mod.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/mod.rs deleted file mode 100644 index 6f39dca..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Integration test modules — full pipeline end-to-end. - -mod compiler_pipeline; -mod stdlib_usage; -mod protocol_conformance; -mod decorator_codegen; -mod test_framework; -mod activate_typing; -mod error_propagation; diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/protocol_conformance.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/protocol_conformance.rs deleted file mode 100644 index 30304f3..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/protocol_conformance.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Tests that verify protocol definitions and impl blocks parse correctly -//! and pass through the type-checking pipeline. - -use crate::{parse_ok, pipeline_ok}; - -// ── Protocol definition parsing ─────────────────────────────────────────────── - -#[test] -fn test_parse_protocol_definition() { - let src = r#" -protocol Serializable { - fn serialize(self: Serializable) -> String - fn deserialize(data: String) -> Serializable -} -"#; - assert!(parse_ok(src).is_ok(), "protocol definition should parse"); -} - -#[test] -fn test_parse_protocol_with_multiple_methods() { - let src = r#" -protocol Comparable { - fn compare(a: Comparable, b: Comparable) -> Int - fn equals(a: Comparable, b: Comparable) -> Bool - fn less_than(a: Comparable, b: Comparable) -> Bool -} -"#; - assert!(parse_ok(src).is_ok(), "multi-method protocol should parse"); -} - -#[test] -fn test_parse_impl_for_type() { - let src = r#" -protocol Printable { - fn print(self: Printable) -> String -} -type Point { - x: Float - y: Float -} -impl Printable for Point { - fn print(self: Point) -> String { - return "point" - } -} -"#; - assert!(parse_ok(src).is_ok(), "impl block should parse"); -} - -#[test] -fn test_parse_impl_with_multiple_methods() { - let src = r#" -protocol Codec { - fn encode(data: String) -> String - fn decode(data: String) -> String -} -type Base64Codec { - padding: Bool -} -impl Codec for Base64Codec { - fn encode(data: String) -> String { - return data - } - fn decode(data: String) -> String { - return data - } -} -"#; - assert!(parse_ok(src).is_ok(), "impl with multiple methods should parse"); -} - -// ── Protocol pipeline tests ─────────────────────────────────────────────────── - -#[test] -fn test_pipeline_protocol_definition_ok() { - let src = r#" -protocol Runnable { - fn run(self: Runnable) -> Int -} -"#; - assert!(pipeline_ok(src).is_ok(), "protocol definition should type-check"); -} - -#[test] -fn test_pipeline_impl_for_builtin_type() { - let src = r#" -protocol Describable { - fn describe(self: Describable) -> String -} -type Tag { - label: String - value: Int -} -impl Describable for Tag { - fn describe(self: Tag) -> String { - return self.label - } -} -"#; - assert!(pipeline_ok(src).is_ok(), "impl block should type-check"); -} - -#[test] -fn test_pipeline_multiple_impls_for_same_protocol() { - let src = r#" -protocol Shape { - fn area(self: Shape) -> Float -} -type Circle { - radius: Float -} -type Square { - side: Float -} -impl Shape for Circle { - fn area(self: Circle) -> Float { - return self.radius * self.radius - } -} -impl Shape for Square { - fn area(self: Square) -> Float { - return self.side * self.side - } -} -"#; - assert!(pipeline_ok(src).is_ok(), "multiple impls for same protocol should type-check"); -} - -#[test] -fn test_pipeline_protocol_with_result_return() { - let src = r#" -protocol Validatable { - fn validate(self: Validatable) -> Result -} -"#; - assert!(pipeline_ok(src).is_ok(), "protocol with Result return type should type-check"); -} - -#[test] -fn test_pipeline_protocol_with_optional_return() { - let src = r#" -protocol Repository { - fn find_by_id(id: Int) -> String? -} -"#; - assert!(pipeline_ok(src).is_ok(), "protocol with optional return type should type-check"); -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/stdlib_usage.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/stdlib_usage.rs deleted file mode 100644 index ae9ac08..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/stdlib_usage.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! Integration tests for programs that use stdlib functions. -//! -//! The stdlib functions are registered as builtins, so el programs can call -//! them without an import statement. - -use crate::pipeline_ok; - -// ── Array stdlib ────────────────────────────────────────────────────────────── - -#[test] -fn test_array_length_call_typechecks() { - let src = r#" -let xs: [Int] = [1, 2, 3] -let n: Int = array_length(xs) -"#; - assert!(pipeline_ok(src).is_ok(), "array_length should be in scope"); -} - -#[test] -fn test_array_push_call_typechecks() { - let src = r#" -let xs: [Int] = [1, 2, 3] -let ys: [Int] = array_push(xs, 4) -"#; - assert!(pipeline_ok(src).is_ok(), "array_push should be in scope"); -} - -#[test] -fn test_array_pop_call_typechecks() { - // array_pop returns T? (Optional), not [T] - let src = r#" -let xs: [Int] = [1, 2, 3] -let head = array_pop(xs) -"#; - assert!(pipeline_ok(src).is_ok(), "array_pop should be in scope"); -} - -#[test] -fn test_array_reverse_call_typechecks() { - let src = r#" -let xs: [Int] = [3, 2, 1] -let ys: [Int] = array_reverse(xs) -"#; - assert!(pipeline_ok(src).is_ok(), "array_reverse should be in scope"); -} - -#[test] -fn test_array_contains_returns_bool() { - // array_contains takes [String] and String - let src = r#" -let xs: [String] = ["a", "b", "c"] -let found: Bool = array_contains(xs, "b") -"#; - assert!(pipeline_ok(src).is_ok(), "array_contains should be in scope"); -} - -// ── String stdlib ───────────────────────────────────────────────────────────── - -#[test] -fn test_string_len_call_typechecks() { - let src = r#"let n: Int = string_len("hello")"#; - assert!(pipeline_ok(src).is_ok(), "string_len should be in scope"); -} - -#[test] -fn test_string_trim_call_typechecks() { - let src = r#"let s: String = string_trim(" hi ")"#; - assert!(pipeline_ok(src).is_ok(), "string_trim should be in scope"); -} - -#[test] -fn test_string_to_upper_call_typechecks() { - let src = r#"let s: String = string_to_upper("hello")"#; - assert!(pipeline_ok(src).is_ok(), "string_to_upper should be in scope"); -} - -#[test] -fn test_string_to_lower_call_typechecks() { - let src = r#"let s: String = string_to_lower("HELLO")"#; - assert!(pipeline_ok(src).is_ok(), "string_to_lower should be in scope"); -} - -#[test] -fn test_string_contains_returns_bool() { - let src = r#"let ok: Bool = string_contains("hello world", "world")"#; - assert!(pipeline_ok(src).is_ok(), "string_contains should be in scope"); -} - -#[test] -fn test_string_concat_call_typechecks() { - let src = r#"let s: String = string_concat("hello", " world")"#; - assert!(pipeline_ok(src).is_ok(), "string_concat should be in scope"); -} - -// ── Math stdlib ─────────────────────────────────────────────────────────────── - -#[test] -fn test_math_abs_call_typechecks() { - // math_abs takes Float -> Float - let src = r#"let n: Float = math_abs(0.0 - 5.0)"#; - assert!(pipeline_ok(src).is_ok(), "math_abs should be in scope"); -} - -#[test] -fn test_math_max_call_typechecks() { - // math_max takes (Float, Float) -> Float - let src = r#"let n: Float = math_max(3.0, 7.0)"#; - assert!(pipeline_ok(src).is_ok(), "math_max should be in scope"); -} - -#[test] -fn test_math_min_call_typechecks() { - // math_min takes (Float, Float) -> Float - let src = r#"let n: Float = math_min(3.0, 7.0)"#; - assert!(pipeline_ok(src).is_ok(), "math_min should be in scope"); -} - -#[test] -fn test_math_pow_call_typechecks() { - let src = r#"let n: Float = math_pow(2.0, 10.0)"#; - assert!(pipeline_ok(src).is_ok(), "math_pow should be in scope"); -} - -#[test] -fn test_math_abs_int_call_typechecks() { - // math_abs_int takes Int -> Int (integer variant) - let src = r#"let n: Int = math_abs_int(0 - 5)"#; - assert!(pipeline_ok(src).is_ok(), "math_abs_int should be in scope"); -} - -#[test] -fn test_math_max_int_call_typechecks() { - let src = r#"let n: Int = math_max_int(3, 7)"#; - assert!(pipeline_ok(src).is_ok(), "math_max_int should be in scope"); -} - -// ── Map stdlib ──────────────────────────────────────────────────────────────── - -#[test] -fn test_map_new_call_typechecks() { - let src = r#"let m: Map = map_new()"#; - assert!(pipeline_ok(src).is_ok(), "map_new should be in scope"); -} - -#[test] -fn test_map_size_call_typechecks() { - let src = r#" -let m: Map = map_new() -let n: Int = map_size(m) -"#; - assert!(pipeline_ok(src).is_ok(), "map_size should be in scope"); -} - -#[test] -fn test_map_is_empty_call_typechecks() { - let src = r#" -let m: Map = map_new() -let empty: Bool = map_is_empty(m) -"#; - assert!(pipeline_ok(src).is_ok(), "map_is_empty should be in scope"); -} - -// ── Engram graph stdlib ─────────────────────────────────────────────────────── - -#[test] -fn test_engram_node_count_call_typechecks() { - let src = r#"let n: Int = engram_node_count()"#; - assert!(pipeline_ok(src).is_ok(), "engram_node_count should be in scope"); -} - -#[test] -fn test_engram_search_call_typechecks() { - let src = r#"let results = engram_search("neural patterns", 10)"#; - assert!(pipeline_ok(src).is_ok(), "engram_search should be in scope"); -} - -#[test] -fn test_engram_edge_between_returns_bool() { - // engram_edge_between takes (Uuid, Uuid) -> Bool - // Uuid literals are just strings assigned to Uuid type - let src = r#" -fn check_edge(a: Uuid, b: Uuid) -> Bool { - return engram_edge_between(a, b) -} -"#; - assert!(pipeline_ok(src).is_ok(), "engram_edge_between should be in scope"); -} diff --git a/_archive/rust-bootstrap/engrams/el-integration/src/tests/test_framework.rs b/_archive/rust-bootstrap/engrams/el-integration/src/tests/test_framework.rs deleted file mode 100644 index 5495184..0000000 --- a/_archive/rust-bootstrap/engrams/el-integration/src/tests/test_framework.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Tests that el `test { ... }` blocks parse and type-check correctly. - -use crate::{parse_ok, pipeline_ok}; - -// ── Parse tests ─────────────────────────────────────────────────────────────── - -#[test] -fn test_parse_simple_test_block() { - let src = r#" -test "addition works" { - assert 1 + 1 == 2 -} -"#; - assert!(parse_ok(src).is_ok(), "simple test block should parse"); -} - -#[test] -fn test_parse_test_with_unit_target() { - let src = r#" -test "unit test" target: unit { - assert true -} -"#; - assert!(parse_ok(src).is_ok(), "test with unit target should parse"); -} - -#[test] -fn test_parse_test_with_e2e_target() { - let src = r#" -test "e2e test" target: e2e { - assert true -} -"#; - assert!(parse_ok(src).is_ok(), "test with e2e target should parse"); -} - -#[test] -fn test_parse_test_with_both_target() { - let src = r#" -test "both targets" target: both { - assert true -} -"#; - assert!(parse_ok(src).is_ok(), "test with both target should parse"); -} - -#[test] -fn test_parse_test_with_let_binding() { - let src = r#" -test "arithmetic" { - let x: Int = 3 + 4 - assert x == 7 -} -"#; - assert!(parse_ok(src).is_ok(), "test with let binding should parse"); -} - -#[test] -fn test_parse_test_with_fn_call() { - let src = r#" -fn double(n: Int) -> Int { - return n * 2 -} -test "double function" { - let result: Int = double(5) - assert result == 10 -} -"#; - assert!(parse_ok(src).is_ok(), "test calling fn should parse"); -} - -#[test] -fn test_parse_multiple_asserts() { - let src = r#" -test "multiple assertions" { - assert 1 < 2 - assert 2 < 3 - assert 3 > 0 -} -"#; - assert!(parse_ok(src).is_ok(), "test with multiple asserts should parse"); -} - -#[test] -fn test_parse_test_with_seed_node() { - let src = r#" -test "with seed data" target: unit { - seed Node { node_type: "User", content: "Alice", importance: 0.9 } - assert true -} -"#; - assert!(parse_ok(src).is_ok(), "test with seed node should parse"); -} - -// ── Pipeline tests ──────────────────────────────────────────────────────────── - -#[test] -fn test_pipeline_test_block_typechecks() { - let src = r#" -test "type-checks ok" { - let x: Int = 42 - assert x > 0 -} -"#; - assert!(pipeline_ok(src).is_ok(), "test block should type-check"); -} - -#[test] -fn test_pipeline_test_with_string_typechecks() { - let src = r#" -test "string test" { - let s: String = "hello" - let n: Int = string_len(s) - assert n > 0 -} -"#; - assert!(pipeline_ok(src).is_ok(), "test using stdlib should type-check"); -} - -#[test] -fn test_pipeline_multiple_test_blocks() { - let src = r#" -test "first" { - assert true -} -test "second" { - assert 1 == 1 -} -"#; - assert!(pipeline_ok(src).is_ok(), "multiple test blocks should type-check"); -} diff --git a/_archive/rust-bootstrap/engrams/el-lexer/Cargo.toml b/_archive/rust-bootstrap/engrams/el-lexer/Cargo.toml deleted file mode 100644 index 0fba306..0000000 --- a/_archive/rust-bootstrap/engrams/el-lexer/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "el-lexer" -description = "Engram language tokenizer" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -thiserror = { workspace = true } - -[dev-dependencies] diff --git a/_archive/rust-bootstrap/engrams/el-lexer/src/error.rs b/_archive/rust-bootstrap/engrams/el-lexer/src/error.rs deleted file mode 100644 index ed05c4f..0000000 --- a/_archive/rust-bootstrap/engrams/el-lexer/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Lexer error types. - -use thiserror::Error; -use crate::token::Span; - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -#[error("{kind} at {span}")] -pub struct LexError { - pub kind: LexErrorKind, - pub span: Span, -} - -impl LexError { - pub fn new(kind: LexErrorKind, span: Span) -> Self { - Self { kind, span } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum LexErrorKind { - #[error("unexpected character {0:?}")] - UnexpectedChar(char), - - #[error("unterminated string literal")] - UnterminatedString, - - #[error("invalid escape sequence \\{0}")] - InvalidEscape(char), - - #[error("invalid numeric literal")] - InvalidNumeric, - - #[error("integer literal out of range")] - IntegerOverflow, -} diff --git a/_archive/rust-bootstrap/engrams/el-lexer/src/lexer.rs b/_archive/rust-bootstrap/engrams/el-lexer/src/lexer.rs deleted file mode 100644 index 0ec90fb..0000000 --- a/_archive/rust-bootstrap/engrams/el-lexer/src/lexer.rs +++ /dev/null @@ -1,602 +0,0 @@ -//! Single-pass lexer implementation. - -use crate::error::{LexError, LexErrorKind}; -use crate::token::{Span, Spanned, Token}; - -/// Tokenize an Engram source string. -/// -/// Returns a `Vec` ending with a single [`Token::Eof`]. On the first -/// unrecognised character the function returns an error; partial output -/// is discarded. -pub fn tokenize(source: &str) -> Result>, LexError> { - let mut lex = Lexer::new(source); - lex.scan_all() -} - -// ── Lexer state ────────────────────────────────────────────────────────────── - -struct Lexer<'src> { - src: &'src str, - /// Current byte position into `src`. - pos: usize, - line: u32, - /// Byte position of the start of the current line (for computing columns). - line_start: usize, -} - -impl<'src> Lexer<'src> { - fn new(src: &'src str) -> Self { - Self { src, pos: 0, line: 1, line_start: 0 } - } - - // ── Utilities ───────────────────────────────────────────────────────────── - - fn col_at(&self, byte_pos: usize) -> u32 { - (byte_pos - self.line_start + 1) as u32 - } - - fn span_from(&self, start: usize) -> Span { - Span::new(start, self.pos, self.line, self.col_at(start)) - } - - fn peek(&self) -> Option { - self.src[self.pos..].chars().next() - } - - fn peek2(&self) -> Option { - let mut chars = self.src[self.pos..].chars(); - chars.next(); - chars.next() - } - - fn advance(&mut self) -> Option { - let ch = self.peek()?; - self.pos += ch.len_utf8(); - if ch == '\n' { - self.line += 1; - self.line_start = self.pos; - } - Some(ch) - } - - /// Consume the next character only if it matches `expected`. - fn eat(&mut self, expected: char) -> bool { - if self.peek() == Some(expected) { - self.advance(); - true - } else { - false - } - } - - fn at_end(&self) -> bool { - self.pos >= self.src.len() - } - - fn spanned(&self, tok: Token, start: usize) -> Spanned { - Spanned::new(tok, self.span_from(start)) - } - - // ── Main scan loop ──────────────────────────────────────────────────────── - - fn scan_all(&mut self) -> Result>, LexError> { - let mut tokens = Vec::new(); - loop { - self.skip_whitespace_and_comments(); - if self.at_end() { - let span = Span::point(self.pos, self.line, self.col_at(self.pos)); - tokens.push(Spanned::new(Token::Eof, span)); - break; - } - let tok = self.scan_token()?; - tokens.push(tok); - } - Ok(tokens) - } - - fn skip_whitespace_and_comments(&mut self) { - loop { - // Skip whitespace - while let Some(ch) = self.peek() { - if ch.is_whitespace() { - self.advance(); - } else { - break; - } - } - // Skip line comments `// ...` - if self.peek() == Some('/') && self.peek2() == Some('/') { - self.advance(); // first / - self.advance(); // second / - while let Some(ch) = self.peek() { - self.advance(); - if ch == '\n' { - break; - } - } - } else { - break; - } - } - } - - fn scan_token(&mut self) -> Result, LexError> { - let start = self.pos; - let ch = self.advance().unwrap(); - - let tok = match ch { - // ── Delimiters ────────────────────────────────────────────────── - '(' => Token::LParen, - ')' => Token::RParen, - '{' => Token::LBrace, - '}' => Token::RBrace, - '[' => Token::LBracket, - ']' => Token::RBracket, - ',' => Token::Comma, - '.' => Token::Dot, - ';' => Token::Semicolon, - - // ── Operators that may be multi-char ──────────────────────────── - '+' => Token::Plus, - '*' => Token::Star, - '%' => Token::Percent, - '^' => Token::Caret, - '~' => Token::Tilde, - '-' => { - if self.eat('>') { - Token::Arrow - } else { - Token::Minus - } - } - '/' => Token::Slash, - '=' => { - if self.eat('=') { - Token::EqEq - } else if self.eat('>') { - Token::FatArrow - } else { - Token::Eq - } - } - '!' => { - if self.eat('=') { - Token::NotEq - } else { - Token::Not - } - } - '<' => { - if self.eat('<') { - Token::Shl - } else if self.eat('=') { - Token::LtEq - } else { - Token::Lt - } - } - '>' => { - if self.eat('>') { - Token::Shr - } else if self.eat('=') { - Token::GtEq - } else { - Token::Gt - } - } - '&' => { - if self.eat('&') { - Token::And - } else { - Token::Ampersand - } - } - '|' => { - if self.eat('|') { - Token::Or - } else if self.eat('>') { - Token::PipeOp - } else { - Token::Pipe - } - } - '@' => Token::At, - '#' => Token::Hash, - '?' => { - if self.eat('?') { Token::NullCoalesce } else { Token::QuestionMark } - } - ':' => { - if self.eat(':') { - Token::ColonColon - } else { - Token::Colon - } - } - - // ── String literals ────────────────────────────────────────────── - '"' => self.scan_string(start)?, - - // ── Numeric literals ───────────────────────────────────────────── - c if c.is_ascii_digit() => self.scan_number(start, c)?, - - // ── Identifiers and keywords ───────────────────────────────────── - c if c.is_alphabetic() || c == '_' => self.scan_ident_or_keyword(start, c), - - other => { - // Emit Unknown token instead of erroring — allows JSX text with - // Unicode / special characters to pass through the lexer gracefully. - Token::Unknown(other) - } - }; - - Ok(self.spanned(tok, start)) - } - - // ── String scanning ─────────────────────────────────────────────────────── - - fn scan_string(&mut self, start: usize) -> Result { - let mut s = String::new(); - loop { - match self.peek() { - None => { - return Err(LexError::new( - LexErrorKind::UnterminatedString, - self.span_from(start), - )) - } - Some('"') => { - self.advance(); - return Ok(Token::StringLiteral(s)); - } - Some('\\') => { - self.advance(); // consume backslash - match self.peek() { - Some('n') => { self.advance(); s.push('\n'); } - Some('t') => { self.advance(); s.push('\t'); } - Some('r') => { self.advance(); s.push('\r'); } - Some('"') => { self.advance(); s.push('"'); } - Some('\\') => { self.advance(); s.push('\\'); } - Some('0') => { self.advance(); s.push('\0'); } - Some(c) => { - let esc = c; - self.advance(); - return Err(LexError::new( - LexErrorKind::InvalidEscape(esc), - self.span_from(start), - )); - } - None => { - return Err(LexError::new( - LexErrorKind::UnterminatedString, - self.span_from(start), - )) - } - } - } - Some(c) => { - s.push(c); - self.advance(); - } - } - } - } - - // ── Numeric scanning ────────────────────────────────────────────────────── - - fn scan_number(&mut self, start: usize, first: char) -> Result { - let mut raw = String::new(); - raw.push(first); - - let mut is_float = false; - - // Collect digits - while let Some(c) = self.peek() { - if c.is_ascii_digit() || c == '_' { - raw.push(c); - self.advance(); - } else if c == '.' && self.peek2().is_some_and(|d| d.is_ascii_digit()) { - is_float = true; - raw.push(c); - self.advance(); - } else { - break; - } - } - - if is_float { - let clean: String = raw.chars().filter(|&c| c != '_').collect(); - let v: f64 = clean.parse().map_err(|_| LexError::new( - LexErrorKind::InvalidNumeric, - self.span_from(start), - ))?; - Ok(Token::FloatLiteral(v)) - } else { - let clean: String = raw.chars().filter(|&c| c != '_').collect(); - let v: i64 = clean.parse().map_err(|_| LexError::new( - LexErrorKind::IntegerOverflow, - self.span_from(start), - ))?; - Ok(Token::IntLiteral(v)) - } - } - - // ── Identifier / keyword scanning ───────────────────────────────────────── - - fn scan_ident_or_keyword(&mut self, _start: usize, first: char) -> Token { - let mut s = String::new(); - s.push(first); - while let Some(c) = self.peek() { - if c.is_alphanumeric() || c == '_' { - s.push(c); - self.advance(); - } else { - break; - } - } - keyword_or_ident(s) - } -} - -// ── Keyword table ───────────────────────────────────────────────────────────── - -fn keyword_or_ident(s: String) -> Token { - match s.as_str() { - "let" => Token::Let, - "fn" => Token::Fn, - "type" => Token::Type, - "enum" => Token::Enum, - "match" => Token::Match, - "return" => Token::Return, - "activate" => Token::Activate, - "where" => Token::Where, - "sealed" => Token::Sealed, - "if" => Token::If, - "else" => Token::Else, - "for" => Token::For, - "in" => Token::In, - "while" => Token::While, - "test" => Token::Test, - "seed" => Token::Seed, - "assert" => Token::Assert, - "target" => Token::Target, - "protocol" => Token::Protocol, - "impl" => Token::Impl, - "import" => Token::Import, - "from" => Token::From, - "as" => Token::As, - "true" => Token::BoolLiteral(true), - "false" => Token::BoolLiteral(false), - "with" => Token::With, - "retry" => Token::Retry, - "times" => Token::Times, - "fallback" => Token::Fallback, - "reason" => Token::Reason, - "parallel" => Token::Parallel, - "trace" => Token::Trace, - "requires" => Token::Requires, - "deploy" => Token::Deploy, - "to" => Token::To, - "via" => Token::Via, - "component" => Token::Component, - "props" => Token::Props, - "state" => Token::State, - "template" => Token::Template, - _ => Token::Ident(s), - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use crate::token::Token; - - fn toks(src: &str) -> Vec { - tokenize(src).unwrap().into_iter().map(|s| s.node).collect() - } - - #[test] - fn test_empty_source() { - let result = tokenize("").unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(result[0].node, Token::Eof); - } - - #[test] - fn test_keywords() { - let src = "let fn type enum match return activate where sealed if else for in"; - let tokens = toks(src); - assert_eq!(tokens[0], Token::Let); - assert_eq!(tokens[1], Token::Fn); - assert_eq!(tokens[2], Token::Type); - assert_eq!(tokens[3], Token::Enum); - assert_eq!(tokens[4], Token::Match); - assert_eq!(tokens[5], Token::Return); - assert_eq!(tokens[6], Token::Activate); - assert_eq!(tokens[7], Token::Where); - assert_eq!(tokens[8], Token::Sealed); - assert_eq!(tokens[9], Token::If); - assert_eq!(tokens[10], Token::Else); - assert_eq!(tokens[11], Token::For); - assert_eq!(tokens[12], Token::In); - } - - #[test] - fn test_bool_literals() { - let tokens = toks("true false"); - assert_eq!(tokens[0], Token::BoolLiteral(true)); - assert_eq!(tokens[1], Token::BoolLiteral(false)); - } - - #[test] - fn test_int_literal() { - let tokens = toks("42 0 1_000_000"); - assert_eq!(tokens[0], Token::IntLiteral(42)); - assert_eq!(tokens[1], Token::IntLiteral(0)); - assert_eq!(tokens[2], Token::IntLiteral(1_000_000)); - } - - #[test] - fn test_float_literal() { - let tokens = toks("3.14 0.5"); - assert_eq!(tokens[0], Token::FloatLiteral(3.14)); - assert_eq!(tokens[1], Token::FloatLiteral(0.5)); - } - - #[test] - fn test_string_literal() { - let tokens = toks(r#""hello" "world\n""#); - assert_eq!(tokens[0], Token::StringLiteral("hello".into())); - assert_eq!(tokens[1], Token::StringLiteral("world\n".into())); - } - - #[test] - fn test_operators() { - let src = "+ - * / = == != < > <= >= && || ! -> =>"; - let tokens = toks(src); - assert_eq!(tokens[0], Token::Plus); - assert_eq!(tokens[1], Token::Minus); - assert_eq!(tokens[2], Token::Star); - assert_eq!(tokens[3], Token::Slash); - assert_eq!(tokens[4], Token::Eq); - assert_eq!(tokens[5], Token::EqEq); - assert_eq!(tokens[6], Token::NotEq); - assert_eq!(tokens[7], Token::Lt); - assert_eq!(tokens[8], Token::Gt); - assert_eq!(tokens[9], Token::LtEq); - assert_eq!(tokens[10], Token::GtEq); - assert_eq!(tokens[11], Token::And); - assert_eq!(tokens[12], Token::Or); - assert_eq!(tokens[13], Token::Not); - assert_eq!(tokens[14], Token::Arrow); - assert_eq!(tokens[15], Token::FatArrow); - } - - #[test] - fn test_delimiters() { - let src = "( ) { } [ ] , : :: . ;"; - let tokens = toks(src); - assert_eq!(tokens[0], Token::LParen); - assert_eq!(tokens[1], Token::RParen); - assert_eq!(tokens[2], Token::LBrace); - assert_eq!(tokens[3], Token::RBrace); - assert_eq!(tokens[4], Token::LBracket); - assert_eq!(tokens[5], Token::RBracket); - assert_eq!(tokens[6], Token::Comma); - assert_eq!(tokens[7], Token::Colon); - assert_eq!(tokens[8], Token::ColonColon); - assert_eq!(tokens[9], Token::Dot); - assert_eq!(tokens[10], Token::Semicolon); - } - - #[test] - fn test_line_comment_skipped() { - let tokens = toks("let // this is a comment\nfn"); - assert_eq!(tokens[0], Token::Let); - assert_eq!(tokens[1], Token::Fn); - } - - #[test] - fn test_span_line_col() { - let src = "let\nfn"; - let tokens = tokenize(src).unwrap(); - assert_eq!(tokens[0].span.line, 1); - assert_eq!(tokens[0].span.col, 1); - assert_eq!(tokens[1].span.line, 2); - assert_eq!(tokens[1].span.col, 1); - } - - #[test] - fn test_unterminated_string_error() { - let result = tokenize(r#""unterminated"#); - assert!(result.is_err()); - } - - #[test] - fn test_at_token() { - let tokens = toks("@public"); - assert_eq!(tokens[0], Token::At); - assert_eq!(tokens[1], Token::Ident("public".into())); - } - - #[test] - fn test_hello_world_program() { - let src = r#" -fn greet(name: String) -> String { - return "Hello, " + name -} - -let msg: String = greet("Will") -"#; - let tokens = tokenize(src).unwrap(); - // Verify it tokenizes without error and the last token is EOF - assert_eq!(tokens.last().unwrap().node, Token::Eof); - // Should have a reasonable number of tokens - assert!(tokens.len() > 10); - } - - #[test] - fn test_activate_syntax() { - let src = r#"activate User where "customer who purchased recently""#; - let tokens = toks(src); - assert_eq!(tokens[0], Token::Activate); - assert_eq!(tokens[1], Token::Ident("User".into())); - assert_eq!(tokens[2], Token::Where); - assert_eq!(tokens[3], Token::StringLiteral("customer who purchased recently".into())); - } - - #[test] - fn test_colon_colon_path() { - let tokens = toks("Status::Active"); - assert_eq!(tokens[0], Token::Ident("Status".into())); - assert_eq!(tokens[1], Token::ColonColon); - assert_eq!(tokens[2], Token::Ident("Active".into())); - } - - #[test] - fn test_new_keywords() { - let tokens = toks("protocol impl import from as"); - assert_eq!(tokens[0], Token::Protocol); - assert_eq!(tokens[1], Token::Impl); - assert_eq!(tokens[2], Token::Import); - assert_eq!(tokens[3], Token::From); - assert_eq!(tokens[4], Token::As); - } - - #[test] - fn test_pipe_token() { - let tokens = toks("|x: Int|"); - assert_eq!(tokens[0], Token::Pipe); - assert_eq!(tokens[1], Token::Ident("x".into())); - assert_eq!(tokens[4], Token::Pipe); - } - - #[test] - fn test_question_mark_token() { - let tokens = toks("x?"); - assert_eq!(tokens[0], Token::Ident("x".into())); - assert_eq!(tokens[1], Token::QuestionMark); - } - - #[test] - fn test_ident_with_underscore() { - let tokens = toks("my_var _private __double"); - assert_eq!(tokens[0], Token::Ident("my_var".into())); - assert_eq!(tokens[1], Token::Ident("_private".into())); - assert_eq!(tokens[2], Token::Ident("__double".into())); - } - - #[test] - fn test_sealed_block_tokens() { - // sealed { let x: String = "secret" } - // [0]=Sealed [1]={ [2]=let [3]=x [4]=: [5]=String [6]== [7]="secret" [8]=} - let src = "sealed { let x: String = \"secret\" }"; - let tokens = toks(src); - assert_eq!(tokens[0], Token::Sealed); - assert_eq!(tokens[1], Token::LBrace); - assert_eq!(tokens[2], Token::Let); - assert_eq!(tokens[7], Token::StringLiteral("secret".into())); - assert_eq!(tokens[8], Token::RBrace); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-lexer/src/lib.rs b/_archive/rust-bootstrap/engrams/el-lexer/src/lib.rs deleted file mode 100644 index 9f87c53..0000000 --- a/_archive/rust-bootstrap/engrams/el-lexer/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! el-lexer — Engram language tokenizer. -//! -//! Converts source text into a flat stream of [`Spanned`] values. -//! All spans carry byte offsets, line, and column so that the parser and -//! diagnostics engine can point back to exact source positions. -//! -//! # Design -//! - Single-pass; O(n) in source length. -//! - No heap allocation per character — only allocates when producing -//! string / identifier token payloads. -//! - All errors carry a [`Span`] so the caller can produce good diagnostics. - -mod error; -mod lexer; -mod token; - -pub use error::{LexError, LexErrorKind}; -pub use lexer::tokenize; -pub use token::{Span, Spanned, Token}; diff --git a/_archive/rust-bootstrap/engrams/el-lexer/src/token.rs b/_archive/rust-bootstrap/engrams/el-lexer/src/token.rs deleted file mode 100644 index 687c8ef..0000000 --- a/_archive/rust-bootstrap/engrams/el-lexer/src/token.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! Token definitions and span types. - -/// A span in the source file — byte offsets plus human-readable location. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Span { - /// Byte offset of the first character of this token. - pub start: usize, - /// Byte offset one past the last character of this token. - pub end: usize, - /// 1-based line number. - pub line: u32, - /// 1-based column number (byte column within the line). - pub col: u32, -} - -impl Span { - pub fn new(start: usize, end: usize, line: u32, col: u32) -> Self { - Self { start, end, line, col } - } - - /// A zero-width span at the given position (used for EOF). - pub fn point(pos: usize, line: u32, col: u32) -> Self { - Self { start: pos, end: pos, line, col } - } -} - -impl std::fmt::Display for Span { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.line, self.col) - } -} - -/// A value annotated with its source location. -#[derive(Debug, Clone, PartialEq)] -pub struct Spanned { - pub node: T, - pub span: Span, -} - -impl Spanned { - pub fn new(node: T, span: Span) -> Self { - Self { node, span } - } -} - -/// All tokens the Engram language lexer can produce. -#[derive(Debug, Clone, PartialEq)] -pub enum Token { - // ── Keywords ────────────────────────────────────────────────────────────── - /// `let` - Let, - /// `fn` - Fn, - /// `type` - Type, - /// `enum` - Enum, - /// `match` - Match, - /// `return` - Return, - /// `activate` — the spreading-activation query construct - Activate, - /// `where` — used in `activate T where "query"` - Where, - /// `sealed` — quantum-sealed block - Sealed, - /// `if` - If, - /// `else` - Else, - /// `for` - For, - /// `in` - In, - /// `while` - While, - /// `test` — test block definition - Test, - /// `seed` — graph seeding statement inside a test - Seed, - /// `assert` — assertion statement inside a test - Assert, - /// `target` — test target annotation (`target: e2e`) - Target, - /// `protocol` — protocol definition - Protocol, - /// `impl` — protocol implementation block - Impl, - /// `import` — import statement - Import, - /// `from` — `from package import { ... }` - From, - /// `as` — alias in import (`import X as Y`) - As, - /// `with` — record update syntax - With, - /// `retry` — retry block - Retry, - /// `times` — used in `retry N times` - Times, - /// `fallback` — fallback block in retry - Fallback, - /// `reason` — AI inference primitive - Reason, - /// `parallel` — concurrent execution block - Parallel, - /// `trace` — zero-cost observability block - Trace, - /// `requires` — precondition annotation on fn - Requires, - /// `deploy` — deployment primitive - Deploy, - /// `to` — used in `deploy fn to "/route"` - To, - /// `via` — used in `deploy fn to "/route" via soma` - Via, - /// `component` — component definition - Component, - /// `props` — props block inside a component - Props, - /// `state` — state block inside a component - State, - /// `template` — template block inside a component - Template, - /// `|>` — pipe operator - PipeOp, - /// `true` / `false` - BoolLiteral(bool), - - // ── Literals ────────────────────────────────────────────────────────────── - IntLiteral(i64), - FloatLiteral(f64), - /// String literal with escape sequences already resolved. - StringLiteral(String), - - // ── Identifiers ─────────────────────────────────────────────────────────── - Ident(String), - - // ── Operators ───────────────────────────────────────────────────────────── - /// `+` - Plus, - /// `-` - Minus, - /// `*` - Star, - /// `/` - Slash, - /// `=` - Eq, - /// `==` - EqEq, - /// `!=` - NotEq, - /// `<` - Lt, - /// `>` - Gt, - /// `<=` - LtEq, - /// `>=` - GtEq, - /// `&&` - And, - /// `||` - Or, - /// `!` - Not, - /// `->` (function return type arrow) - Arrow, - /// `=>` (match arm) - FatArrow, - - // ── Delimiters ──────────────────────────────────────────────────────────── - /// `(` - LParen, - /// `)` - RParen, - /// `{` - LBrace, - /// `}` - RBrace, - /// `[` - LBracket, - /// `]` - RBracket, - /// `,` - Comma, - /// `:` - Colon, - /// `::` - ColonColon, - /// `.` - Dot, - /// `;` - Semicolon, - - // ── New single-char tokens ──────────────────────────────────────────────── - /// `@` — decorator prefix - At, - /// `|` — closure param delimiter (single pipe, not `||`) - Pipe, - /// `?` — used both for Optional types and the Try operator - QuestionMark, - /// `??` — null-coalescing operator: `a ?? b` returns `b` when `a` is nil/empty - NullCoalesce, - /// `%` — modulo - Percent, - /// `&` — bitwise AND - Ampersand, - /// `^` — bitwise XOR - Caret, - /// `~` — bitwise NOT - Tilde, - /// `<<` — left shift - Shl, - /// `>>` — right shift - Shr, - /// `#` — hash character (used in JSX text like `#tag`) - Hash, - /// Any other character not recognized by the lexer (emitted instead of erroring, - /// so JSX text with Unicode / special characters can be skipped gracefully). - Unknown(char), - - // ── Special ─────────────────────────────────────────────────────────────── - Eof, -} - -impl std::fmt::Display for Token { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Token::Let => write!(f, "let"), - Token::Fn => write!(f, "fn"), - Token::Type => write!(f, "type"), - Token::Enum => write!(f, "enum"), - Token::Match => write!(f, "match"), - Token::Return => write!(f, "return"), - Token::Activate => write!(f, "activate"), - Token::Where => write!(f, "where"), - Token::Sealed => write!(f, "sealed"), - Token::If => write!(f, "if"), - Token::Else => write!(f, "else"), - Token::For => write!(f, "for"), - Token::While => write!(f, "while"), - Token::In => write!(f, "in"), - Token::Test => write!(f, "test"), - Token::Seed => write!(f, "seed"), - Token::Assert => write!(f, "assert"), - Token::Target => write!(f, "target"), - Token::Protocol => write!(f, "protocol"), - Token::Impl => write!(f, "impl"), - Token::Import => write!(f, "import"), - Token::From => write!(f, "from"), - Token::As => write!(f, "as"), - Token::With => write!(f, "with"), - Token::Retry => write!(f, "retry"), - Token::Times => write!(f, "times"), - Token::Fallback => write!(f, "fallback"), - Token::Reason => write!(f, "reason"), - Token::Parallel => write!(f, "parallel"), - Token::Trace => write!(f, "trace"), - Token::Requires => write!(f, "requires"), - Token::Deploy => write!(f, "deploy"), - Token::To => write!(f, "to"), - Token::Via => write!(f, "via"), - Token::Component => write!(f, "component"), - Token::Props => write!(f, "props"), - Token::State => write!(f, "state"), - Token::Template => write!(f, "template"), - Token::PipeOp => write!(f, "|>"), - Token::At => write!(f, "@"), - Token::Pipe => write!(f, "|"), - Token::QuestionMark => write!(f, "?"), - Token::NullCoalesce => write!(f, "??"), - Token::Percent => write!(f, "%"), - Token::Ampersand => write!(f, "&"), - Token::Caret => write!(f, "^"), - Token::Tilde => write!(f, "~"), - Token::Shl => write!(f, "<<"), - Token::Shr => write!(f, ">>"), - Token::Hash => write!(f, "#"), - Token::Unknown(c) => write!(f, "{c}"), - Token::BoolLiteral(b) => write!(f, "{b}"), - Token::IntLiteral(n) => write!(f, "{n}"), - Token::FloatLiteral(n) => write!(f, "{n}"), - Token::StringLiteral(s) => write!(f, "\"{s}\""), - Token::Ident(s) => write!(f, "{s}"), - Token::Plus => write!(f, "+"), - Token::Minus => write!(f, "-"), - Token::Star => write!(f, "*"), - Token::Slash => write!(f, "/"), - Token::Eq => write!(f, "="), - Token::EqEq => write!(f, "=="), - Token::NotEq => write!(f, "!="), - Token::Lt => write!(f, "<"), - Token::Gt => write!(f, ">"), - Token::LtEq => write!(f, "<="), - Token::GtEq => write!(f, ">="), - Token::And => write!(f, "&&"), - Token::Or => write!(f, "||"), - Token::Not => write!(f, "!"), - Token::Arrow => write!(f, "->"), - Token::FatArrow => write!(f, "=>"), - Token::LParen => write!(f, "("), - Token::RParen => write!(f, ")"), - Token::LBrace => write!(f, "{{"), - Token::RBrace => write!(f, "}}"), - Token::LBracket => write!(f, "["), - Token::RBracket => write!(f, "]"), - Token::Comma => write!(f, ","), - Token::Colon => write!(f, ":"), - Token::ColonColon => write!(f, "::"), - Token::Dot => write!(f, "."), - Token::Semicolon => write!(f, ";"), - Token::Eof => write!(f, ""), - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-lint/Cargo.toml b/_archive/rust-bootstrap/engrams/el-lint/Cargo.toml deleted file mode 100644 index e334dd9..0000000 --- a/_archive/rust-bootstrap/engrams/el-lint/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "el-lint" -version = "0.1.0" -edition = "2021" - -[dependencies] -el-lexer = { path = "../el-lexer" } -el-parser = { path = "../el-parser" } -el-types = { path = "../el-types" } -el-arch = { path = "../el-arch" } -el-fmt = { path = "../el-fmt" } -thiserror = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -[dev-dependencies] -el-lexer = { path = "../el-lexer" } -el-parser = { path = "../el-parser" } diff --git a/_archive/rust-bootstrap/engrams/el-lint/src/error.rs b/_archive/rust-bootstrap/engrams/el-lint/src/error.rs deleted file mode 100644 index 785a2a1..0000000 --- a/_archive/rust-bootstrap/engrams/el-lint/src/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Error types for el-lint. - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum LintError { - #[error("lex error: {0}")] - Lex(String), - #[error("parse error: {0}")] - Parse(String), -} diff --git a/_archive/rust-bootstrap/engrams/el-lint/src/lib.rs b/_archive/rust-bootstrap/engrams/el-lint/src/lib.rs deleted file mode 100644 index 5853007..0000000 --- a/_archive/rust-bootstrap/engrams/el-lint/src/lib.rs +++ /dev/null @@ -1,298 +0,0 @@ -//! el-lint — linter for el source files. -//! -//! Combines: -//! - `el-arch` architectural rule violations (VBD, EBD, swarm, security, graph) -//! - Style checks (naming conventions, function length, empty bodies) -//! - Format check (`el-fmt` canonical check, rule I001) - -pub mod error; -pub mod linter; -pub mod report; -pub mod rules; - -pub use error::LintError; -pub use linter::Linter; -pub use report::{LintDiagnostic, LintReport, LintSeverity}; - -/// Lint el source code. Returns a report with all diagnostics. -pub fn lint(source: &str) -> Result { - Linter::new().lint(source) -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - fn do_lint(src: &str) -> LintReport { - lint(src).unwrap() - } - - // 1. Clean, canonical code → no errors, no warnings - #[test] - fn test_clean_code_no_errors() { - let src = "fn add(a: Int, b: Int) -> Int {\n return a + b\n}\n"; - let report = do_lint(src); - assert!(!report.has_errors(), "unexpected errors: {:?}", report.diagnostics); - assert_eq!(report.warning_count(), 0, "unexpected warnings: {:?}", report.diagnostics); - } - - // 2. @accessor calling @manager fn → VBD-001 error - #[test] - fn test_accessor_calls_manager() { - let src = concat!( - "@manager\nfn save_data() -> Void {\n}\n\n", - "@accessor\nfn get_data() -> Void {\n save_data()\n}\n" - ); - let report = do_lint(src); - assert!( - report.has_errors(), - "expected arch error for accessor calling manager" - ); - let has_vbd = report - .diagnostics - .iter() - .any(|d| d.code.contains("VBD")); - assert!(has_vbd, "expected VBD code: {:?}", report.diagnostics); - } - - // 3. activate in a loop → GRAPH-001 warning - #[test] - fn test_activate_in_loop() { - let src = concat!( - "@accessor\nfn load_all(items: [String]) -> Void {\n", - " for x in items {\n", - " activate User where \"query\"\n", - " }\n}\n" - ); - let report = do_lint(src); - let has_graph = report - .diagnostics - .iter() - .any(|d| d.code.contains("GRAPH") || d.code.contains("N1")); - assert!(has_graph, "expected GRAPH/N1 diagnostic: {:?}", report.diagnostics); - } - - // 4. Function with uppercase name → S002 warning - #[test] - fn test_fn_uppercase_name() { - let src = "fn MyFunction() -> Void {\n}\n"; - let report = do_lint(src); - let has_s002 = report.diagnostics.iter().any(|d| d.code == "S002"); - assert!(has_s002, "expected S002: {:?}", report.diagnostics); - } - - // 5. Type with lowercase name → S004 warning - #[test] - fn test_type_lowercase_name() { - let src = "type myType {\n x: Int\n}\n"; - let report = do_lint(src); - let has_s004 = report.diagnostics.iter().any(|d| d.code == "S004"); - assert!(has_s004, "expected S004: {:?}", report.diagnostics); - } - - // 6. Empty function body → S003 info - #[test] - fn test_empty_fn_body() { - let src = "fn empty() -> Void {\n}\n"; - let report = do_lint(src); - let has_s003 = report.diagnostics.iter().any(|d| d.code == "S003"); - assert!(has_s003, "expected S003: {:?}", report.diagnostics); - } - - // 7. Non-canonical formatting → I001 info - #[test] - fn test_non_canonical_format() { - // Missing trailing newline triggers I001 (formatter adds it, source doesn't have it) - let src = "42"; - let report = do_lint(src); - let has_i001 = report.diagnostics.iter().any(|d| d.code == "I001"); - assert!(has_i001, "expected I001: {:?}", report.diagnostics); - } - - // 8. Canonical formatting → no I001 - #[test] - fn test_canonical_format_no_i001() { - let src = "fn add(a: Int, b: Int) -> Int {\n return a + b\n}\n"; - let report = do_lint(src); - let has_i001 = report.diagnostics.iter().any(|d| d.code == "I001"); - assert!(!has_i001, "unexpected I001: {:?}", report.diagnostics); - } - - // 9. has_errors() true when errors present - #[test] - fn test_has_errors_true() { - let src = concat!( - "@manager\nfn save() -> Void {\n}\n\n", - "@accessor\nfn get() -> Void {\n save()\n}\n" - ); - let report = do_lint(src); - assert!(report.has_errors()); - } - - // 10. has_errors() false when only warnings/info - #[test] - fn test_has_errors_false_warnings_only() { - let src = "fn MyFunction() -> Int {\n return 1\n}\n"; - let report = do_lint(src); - assert!(!report.has_errors(), "should not have errors, only warnings"); - } - - // 11. error_count() correct - #[test] - fn test_error_count() { - let src = concat!( - "@manager\nfn save() -> Void {\n}\n\n", - "@accessor\nfn get() -> Void {\n save()\n}\n" - ); - let report = do_lint(src); - assert!(report.error_count() >= 1); - } - - // 12. warning_count() correct - #[test] - fn test_warning_count() { - let src = "fn MyFunction() -> Int {\n return 1\n}\n"; - let report = do_lint(src); - assert!(report.warning_count() >= 1, "expected at least one warning"); - } - - // 13. display() output contains "error" prefix for errors - #[test] - fn test_display_error_prefix() { - let src = concat!( - "@manager\nfn save() -> Void {\n}\n\n", - "@accessor\nfn get() -> Void {\n save()\n}\n" - ); - let report = do_lint(src); - let display = report.display(); - assert!(display.contains("error"), "expected 'error' in display: {display}"); - } - - // 14. display() contains "No issues found." for clean code - #[test] - fn test_display_no_issues() { - let src = "fn add(a: Int, b: Int) -> Int {\n return a + b\n}\n"; - let report = do_lint(src); - if !report.has_errors() && report.warning_count() == 0 { - let display = report.display(); - assert!( - display.contains("No issues found."), - "expected 'No issues found.': {display}" - ); - } - } - - // 15. to_json() is valid JSON - #[test] - fn test_to_json_valid() { - let src = "fn add(a: Int, b: Int) -> Int {\n return a + b\n}\n"; - let report = do_lint(src); - let json = report.to_json(); - let parsed: serde_json::Value = serde_json::from_str(&json).expect("invalid JSON"); - assert!(parsed.is_array(), "expected JSON array"); - } - - // 16. to_json() contains severity field - #[test] - fn test_to_json_has_severity() { - let src = "fn MyFunction() -> Int {\n return 1\n}\n"; - let report = do_lint(src); - let json = report.to_json(); - assert!(json.contains("severity"), "expected severity field: {json}"); - } - - // 17. Multiple issues in same file → all reported - #[test] - fn test_multiple_issues() { - let src = "fn MyFunction() -> Void {\n}\ntype myType {\n x: Int\n}\n"; - let report = do_lint(src); - // S002 for fn name + S003 for empty body + S004 for type name - assert!( - report.diagnostics.len() >= 2, - "expected multiple diagnostics: {:?}", - report.diagnostics - ); - } - - // 18. @experience calling @experience → arch error - #[test] - fn test_experience_calls_experience() { - let src = concat!( - "@experience\nfn exp_a() -> Void {\n}\n\n", - "@experience\nfn exp_b() -> Void {\n exp_a()\n}\n" - ); - let report = do_lint(src); - assert!( - report.has_errors(), - "expected arch error for experience calling experience" - ); - } - - // 19. @public fn with activate → arch error - #[test] - fn test_public_fn_with_activate() { - let src = "@public\nfn api_fn() -> Void {\n activate User where \"query\"\n}\n"; - let report = do_lint(src); - assert!( - report.has_errors(), - "expected arch error for public fn with activate" - ); - } - - // 20. @swarm_agent calling @swarm_agent → diagnostic - #[test] - fn test_swarm_agent_calls_swarm_agent() { - let src = concat!( - "@swarm_agent\nfn agent_a() -> Void {\n}\n\n", - "@swarm_agent\nfn agent_b() -> Void {\n agent_a()\n}\n" - ); - let report = do_lint(src); - // SwarmAgentIsolation should flag this - let has_swarm = report - .diagnostics - .iter() - .any(|d| d.code.contains("SWARM") || d.severity == LintSeverity::Error || d.severity == LintSeverity::Warning); - assert!(has_swarm, "expected swarm diagnostic: {:?}", report.diagnostics); - } - - // 21. Nested functions → linting still works - #[test] - fn test_nested_functions() { - let src = concat!( - "fn outer(x: Int) -> Int {\n", - " fn inner(y: Int) -> Int {\n", - " return y + 1\n", - " }\n", - " return inner(x)\n", - "}\n" - ); - // Should not panic - let result = lint(src); - assert!(result.is_ok(), "lint failed on nested functions"); - } - - // 22. LintReport::file_path is None by default - #[test] - fn test_file_path_none() { - let src = "fn f() -> Void {\n}\n"; - let report = do_lint(src); - assert!(report.file_path.is_none()); - } - - // 23. source_lines is counted correctly - #[test] - fn test_source_lines_counted() { - let src = "fn f() -> Int {\n return 1\n}\n"; - let report = do_lint(src); - assert_eq!(report.source_lines, 3); - } - - // 24. Empty source → no crash - #[test] - fn test_empty_source() { - let result = lint(""); - assert!(result.is_ok()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-lint/src/linter.rs b/_archive/rust-bootstrap/engrams/el-lint/src/linter.rs deleted file mode 100644 index 269d3ba..0000000 --- a/_archive/rust-bootstrap/engrams/el-lint/src/linter.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Core linter — orchestrates arch rules, style rules, and format check. - -use el_arch::{ArchChecker, Severity as ArchSeverity}; - -use crate::{ - error::LintError, - report::{LintDiagnostic, LintReport, LintSeverity}, - rules, -}; - -pub struct Linter { - arch_checker: ArchChecker, -} - -impl Linter { - pub fn new() -> Self { - Self { - arch_checker: ArchChecker::new(), - } - } - - pub fn lint(&self, source: &str) -> Result { - let tokens = el_lexer::tokenize(source) - .map_err(|e| LintError::Lex(e.to_string()))?; - let program = el_parser::parse(tokens, source.to_string()) - .map_err(|e| LintError::Parse(e.to_string()))?; - - let mut diagnostics = Vec::new(); - - // 1. Run el-arch architectural rules. - let arch_diags = self.arch_checker.check(&program); - for d in arch_diags { - diagnostics.push(LintDiagnostic { - severity: match d.severity { - ArchSeverity::Error => LintSeverity::Error, - ArchSeverity::Warning => LintSeverity::Warning, - }, - code: d.rule, - message: d.message, - location: d.location.unwrap_or_else(|| "unknown".into()), - suggestion: None, - }); - } - - // 2. Run style rules. - let style_diags = rules::check_style(&program); - diagnostics.extend(style_diags); - - // 3. Check whether the source is in canonical format. - match el_fmt::is_canonical(source) { - Ok(false) => { - diagnostics.push(LintDiagnostic { - severity: LintSeverity::Info, - code: "I001".into(), - message: "source is not in canonical format — run `el fmt` to fix".into(), - location: "file".into(), - suggestion: Some("el fmt --in-place ".into()), - }); - } - _ => {} - } - - let source_lines = source.lines().count(); - Ok(LintReport { - diagnostics, - file_path: None, - source_lines, - }) - } -} - -impl Default for Linter { - fn default() -> Self { - Self::new() - } -} diff --git a/_archive/rust-bootstrap/engrams/el-lint/src/report.rs b/_archive/rust-bootstrap/engrams/el-lint/src/report.rs deleted file mode 100644 index 317c94b..0000000 --- a/_archive/rust-bootstrap/engrams/el-lint/src/report.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Diagnostic report types for el-lint. - -#[derive(Debug, Clone, PartialEq)] -pub enum LintSeverity { - Error, - Warning, - Info, -} - -#[derive(Debug, Clone)] -pub struct LintDiagnostic { - pub severity: LintSeverity, - /// Rule code, e.g. "E001", "W002", "S001", "I001". - pub code: String, - pub message: String, - /// Human-readable location hint, e.g. "function foo" or "file". - pub location: String, - pub suggestion: Option, -} - -#[derive(Debug)] -pub struct LintReport { - pub diagnostics: Vec, - pub file_path: Option, - pub source_lines: usize, -} - -impl LintReport { - pub fn has_errors(&self) -> bool { - self.diagnostics - .iter() - .any(|d| d.severity == LintSeverity::Error) - } - - pub fn error_count(&self) -> usize { - self.diagnostics - .iter() - .filter(|d| d.severity == LintSeverity::Error) - .count() - } - - pub fn warning_count(&self) -> usize { - self.diagnostics - .iter() - .filter(|d| d.severity == LintSeverity::Warning) - .count() - } - - /// Format as human-readable output (similar to rustc error output). - pub fn display(&self) -> String { - let mut out = String::new(); - for d in &self.diagnostics { - let prefix = match d.severity { - LintSeverity::Error => "error", - LintSeverity::Warning => "warning", - LintSeverity::Info => "info", - }; - out.push_str(&format!("[{}] {}: {}\n", d.code, prefix, d.message)); - out.push_str(&format!(" --> {}\n", d.location)); - if let Some(suggestion) = &d.suggestion { - out.push_str(&format!(" help: {}\n", suggestion)); - } - out.push('\n'); - } - if self.diagnostics.is_empty() { - out.push_str("No issues found.\n"); - } - out - } - - /// Format as JSON for editor integration. - pub fn to_json(&self) -> String { - let items: Vec = self - .diagnostics - .iter() - .map(|d| { - serde_json::json!({ - "severity": match d.severity { - LintSeverity::Error => "error", - LintSeverity::Warning => "warning", - LintSeverity::Info => "info", - }, - "code": d.code, - "message": d.message, - "location": d.location, - "suggestion": d.suggestion, - }) - }) - .collect(); - serde_json::to_string_pretty(&items).unwrap() - } -} diff --git a/_archive/rust-bootstrap/engrams/el-lint/src/rules.rs b/_archive/rust-bootstrap/engrams/el-lint/src/rules.rs deleted file mode 100644 index e98a491..0000000 --- a/_archive/rust-bootstrap/engrams/el-lint/src/rules.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Style and correctness rules for el-lint (beyond el-arch architectural rules). - -use el_parser::{Program, Stmt}; - -use crate::report::{LintDiagnostic, LintSeverity}; - -/// Run all style rules against the program and return diagnostics. -pub fn check_style(program: &Program) -> Vec { - let mut diags = Vec::new(); - - for stmt in &program.stmts { - check_stmt(stmt, &mut diags); - } - - diags -} - -fn check_stmt(stmt: &Stmt, diags: &mut Vec) { - match stmt { - Stmt::FnDef { name, body, .. } => { - // S001: Function body too long (>50 statements) - if body.len() > 50 { - diags.push(LintDiagnostic { - severity: LintSeverity::Warning, - code: "S001".into(), - message: format!( - "function `{name}` has {} statements — consider splitting", - body.len() - ), - location: format!("function {name}"), - suggestion: Some( - "extract sub-functions for each logical concern".into(), - ), - }); - } - - // S002: Function name not snake_case - if name.chars().any(|c| c.is_uppercase()) { - diags.push(LintDiagnostic { - severity: LintSeverity::Warning, - code: "S002".into(), - message: format!("function `{name}` should be snake_case"), - location: format!("function {name}"), - suggestion: Some(format!("rename to `{}`", to_snake_case(name))), - }); - } - - // S003: Empty function body - if body.is_empty() { - diags.push(LintDiagnostic { - severity: LintSeverity::Info, - code: "S003".into(), - message: format!("function `{name}` has an empty body"), - location: format!("function {name}"), - suggestion: Some("add implementation or remove if unused".into()), - }); - } - - // Recurse into nested function defs - for s in body { - check_stmt(s, diags); - } - } - - Stmt::TypeDef { name, .. } => { - // S004: Type name not PascalCase - if !is_pascal_case(name) { - diags.push(LintDiagnostic { - severity: LintSeverity::Warning, - code: "S004".into(), - message: format!("type `{name}` should be PascalCase"), - location: format!("type {name}"), - suggestion: None, - }); - } - } - - Stmt::EnumDef { name, .. } => { - // S004 also applies to enums - if !is_pascal_case(name) { - diags.push(LintDiagnostic { - severity: LintSeverity::Warning, - code: "S004".into(), - message: format!("enum `{name}` should be PascalCase"), - location: format!("enum {name}"), - suggestion: None, - }); - } - } - - Stmt::ImplDef { methods, .. } => { - for m in methods { - check_stmt(m, diags); - } - } - - _ => {} - } -} - -/// Convert CamelCase/mixed to snake_case. -fn to_snake_case(s: &str) -> String { - let mut result = String::new(); - for (i, c) in s.chars().enumerate() { - if c.is_uppercase() && i > 0 { - result.push('_'); - } - result.push(c.to_lowercase().next().unwrap()); - } - result -} - -/// Returns true if the first character is uppercase (PascalCase convention). -fn is_pascal_case(s: &str) -> bool { - s.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) -} diff --git a/_archive/rust-bootstrap/engrams/el-manifest/Cargo.toml b/_archive/rust-bootstrap/engrams/el-manifest/Cargo.toml deleted file mode 100644 index 3f0acce..0000000 --- a/_archive/rust-bootstrap/engrams/el-manifest/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "el-manifest" -description = "manifest.el project manifest parser for the Engram language package system" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -thiserror = { workspace = true } -semver = { version = "1", features = ["serde"] } -el-lexer = { workspace = true } - -[dev-dependencies] diff --git a/_archive/rust-bootstrap/engrams/el-manifest/src/error.rs b/_archive/rust-bootstrap/engrams/el-manifest/src/error.rs deleted file mode 100644 index 0ac9b57..0000000 --- a/_archive/rust-bootstrap/engrams/el-manifest/src/error.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Manifest error types. - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum ManifestError { - #[error("io error: {0}")] - Io(#[from] std::io::Error), - - #[error("manifest parse error at line {line}: {reason}")] - Parse { line: u32, reason: String }, - - #[error("semver parse error for '{field}': {source}")] - Semver { - field: String, - #[source] - source: semver::Error, - }, - - #[error("missing required field: {0}")] - MissingField(String), - - #[error("invalid value for '{field}': {reason}")] - InvalidValue { field: String, reason: String }, - - #[error("manifest.el not found (searched from {0})")] - NotFound(String), - - #[error("invalid cross target '{0}': use x86_64-linux, aarch64-linux, x86_64-macos, aarch64-macos, wasm32")] - InvalidCrossTarget(String), - - #[error("invalid build target '{0}': use debug, release, prod")] - InvalidBuildTarget(String), - - #[error("invalid seal key source '{0}': use env:VAR, file:path, or literal")] - InvalidSealKeySource(String), -} - -pub type ManifestResult = Result; diff --git a/_archive/rust-bootstrap/engrams/el-manifest/src/lib.rs b/_archive/rust-bootstrap/engrams/el-manifest/src/lib.rs deleted file mode 100644 index 29ef9fd..0000000 --- a/_archive/rust-bootstrap/engrams/el-manifest/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! el-manifest — `manifest.el` project manifest parser. -//! -//! Every Engram project has a `manifest.el` at its root. This crate defines the -//! manifest data model and parses it from El block syntax. -//! -//! # Quick start -//! ```rust -//! use el_manifest::Manifest; -//! -//! let src = r#" -//! package "my-service" { -//! version "0.1.0" -//! edition "2026" -//! } -//! "#; -//! let manifest = Manifest::parse(src).unwrap(); -//! assert_eq!(manifest.package.name, "my-service"); -//! ``` - -mod error; -mod manifest; -mod parse; - -pub use error::{ManifestError, ManifestResult}; -pub use manifest::{ - AppConfig, BuildConfig, BuildTarget, CrossConfig, CrossTarget, Dependency, Manifest, - NativeTarget, PackageInfo, SealKeySource, -}; diff --git a/_archive/rust-bootstrap/engrams/el-manifest/src/manifest.rs b/_archive/rust-bootstrap/engrams/el-manifest/src/manifest.rs deleted file mode 100644 index 40b87d7..0000000 --- a/_archive/rust-bootstrap/engrams/el-manifest/src/manifest.rs +++ /dev/null @@ -1,392 +0,0 @@ -//! Core data model for the `manifest.el` manifest. - -use std::collections::HashMap; -use std::path::PathBuf; - -use semver::{Version, VersionReq}; -use serde::{Deserialize, Serialize}; - -// ── Package info ────────────────────────────────────────────────────────────── - -/// Metadata about the package itself (`[package]` section). -#[derive(Debug, Clone, PartialEq)] -pub struct PackageInfo { - pub name: String, - pub version: Version, - pub description: Option, - pub authors: Vec, - pub license: Option, - /// Language edition, e.g. "2026". - pub edition: String, -} - -// ── Dependencies ────────────────────────────────────────────────────────────── - -/// A single dependency specifier. -#[derive(Debug, Clone, PartialEq)] -pub enum Dependency { - /// A bare semver version requirement string (`"1.2"`, `"^0.8.1"`). - VersionReq(VersionReq), - /// A path-local dependency (`{ path = "../some-local" }`). - Path(PathBuf), - /// A registry package with an explicit registry URL. - Registry { - version: VersionReq, - registry: String, - }, -} - -impl Dependency { - /// Returns the version requirement if this is a registry / version dep. - pub fn version_req(&self) -> Option<&VersionReq> { - match self { - Dependency::VersionReq(req) => Some(req), - Dependency::Registry { version, .. } => Some(version), - Dependency::Path(_) => None, - } - } - - /// Returns the local path if this is a path dependency. - pub fn local_path(&self) -> Option<&PathBuf> { - match self { - Dependency::Path(p) => Some(p), - _ => None, - } - } -} - -// ── Build config ────────────────────────────────────────────────────────────── - -/// The three compilation targets supported by the Engram toolchain. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum BuildTarget { - Debug, - Release, - Prod, -} - -impl BuildTarget { - pub fn as_str(&self) -> &'static str { - match self { - BuildTarget::Debug => "debug", - BuildTarget::Release => "release", - BuildTarget::Prod => "prod", - } - } -} - -impl std::fmt::Display for BuildTarget { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -impl std::str::FromStr for BuildTarget { - type Err = crate::ManifestError; - - fn from_str(s: &str) -> Result { - match s { - "debug" => Ok(BuildTarget::Debug), - "release" => Ok(BuildTarget::Release), - "prod" => Ok(BuildTarget::Prod), - other => Err(crate::ManifestError::InvalidBuildTarget(other.to_string())), - } - } -} - -/// Where to read the sealing key from. -#[derive(Debug, Clone, PartialEq)] -pub enum SealKeySource { - /// `env:VAR_NAME` — read from an environment variable at build time. - EnvVar(String), - /// `file:path/to/key` — read raw bytes from a file. - File(PathBuf), - /// A literal key value — for development/testing only. - Literal(String), -} - -impl SealKeySource { - /// Parse from the `manifest.el` string representation. - pub fn parse(s: &str) -> Result { - if let Some(var) = s.strip_prefix("env:") { - Ok(SealKeySource::EnvVar(var.to_string())) - } else if let Some(path) = s.strip_prefix("file:") { - Ok(SealKeySource::File(PathBuf::from(path))) - } else if s.is_empty() { - Err(crate::ManifestError::InvalidSealKeySource(s.to_string())) - } else { - Ok(SealKeySource::Literal(s.to_string())) - } - } - - /// Resolve the key bytes at runtime. - pub fn resolve(&self) -> Result, crate::ManifestError> { - match self { - SealKeySource::EnvVar(var) => { - std::env::var(var) - .map(|v| v.into_bytes()) - .map_err(|_| crate::ManifestError::InvalidValue { - field: format!("env:{var}"), - reason: "environment variable not set".to_string(), - }) - } - SealKeySource::File(path) => { - std::fs::read(path).map_err(crate::ManifestError::Io) - } - SealKeySource::Literal(s) => Ok(s.as_bytes().to_vec()), - } - } -} - -/// The `[build]` section. -#[derive(Debug, Clone, PartialEq)] -pub struct BuildConfig { - pub target: BuildTarget, - pub entry: PathBuf, - pub output: PathBuf, - pub seal_key: Option, -} - -impl Default for BuildConfig { - fn default() -> Self { - Self { - target: BuildTarget::Debug, - entry: PathBuf::from("src/main.el"), - output: PathBuf::from("dist/"), - seal_key: None, - } - } -} - -// ── Cross-compilation ───────────────────────────────────────────────────────── - -/// A native target triple for cross-compilation. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum CrossTarget { - #[serde(rename = "x86_64-linux")] - X86_64Linux, - #[serde(rename = "aarch64-linux")] - Aarch64Linux, - #[serde(rename = "x86_64-macos")] - X86_64Macos, - #[serde(rename = "aarch64-macos")] - Aarch64Macos, - #[serde(rename = "wasm32")] - Wasm32, -} - -impl CrossTarget { - /// Parse from the string used in `manifest.el`. - pub fn parse(s: &str) -> Result { - match s { - "x86_64-linux" => Ok(CrossTarget::X86_64Linux), - "aarch64-linux" => Ok(CrossTarget::Aarch64Linux), - "x86_64-macos" => Ok(CrossTarget::X86_64Macos), - "aarch64-macos" => Ok(CrossTarget::Aarch64Macos), - "wasm32" => Ok(CrossTarget::Wasm32), - other => Err(crate::ManifestError::InvalidCrossTarget(other.to_string())), - } - } - - /// The canonical Rust/LLVM target triple for this target. - pub fn triple(&self) -> &'static str { - match self { - CrossTarget::X86_64Linux => "x86_64-unknown-linux-gnu", - CrossTarget::Aarch64Linux => "aarch64-unknown-linux-gnu", - CrossTarget::X86_64Macos => "x86_64-apple-darwin", - CrossTarget::Aarch64Macos => "aarch64-apple-darwin", - CrossTarget::Wasm32 => "wasm32-unknown-unknown", - } - } - - /// File extension for compiled artifacts on this target. - pub fn artifact_extension(&self) -> &'static str { - match self { - CrossTarget::Wasm32 => ".wasm", - _ => "", - } - } - - /// The string as it appears in `manifest.el`. - pub fn as_str(&self) -> &'static str { - match self { - CrossTarget::X86_64Linux => "x86_64-linux", - CrossTarget::Aarch64Linux => "aarch64-linux", - CrossTarget::X86_64Macos => "x86_64-macos", - CrossTarget::Aarch64Macos => "aarch64-macos", - CrossTarget::Wasm32 => "wasm32", - } - } -} - -impl std::fmt::Display for CrossTarget { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -/// The `[cross]` section. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct CrossConfig { - pub targets: Vec, -} - -// ── Native target (compiler-level) ──────────────────────────────────────────── - -/// Compiler-level native target — includes `Host` for the current machine. -/// -/// This is stored in the sealed artifact header so the runtime knows which -/// native code generation backend to use. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum NativeTarget { - #[serde(rename = "x86_64-linux")] - X86_64Linux, - #[serde(rename = "aarch64-linux")] - Aarch64Linux, - #[serde(rename = "x86_64-macos")] - X86_64Macos, - #[serde(rename = "aarch64-macos")] - Aarch64Macos, - #[serde(rename = "wasm32")] - Wasm32, - /// The host machine — resolved at runtime. - #[serde(rename = "host")] - Host, -} - -impl NativeTarget { - /// The canonical LLVM triple for this target. - /// - /// For `Host`, returns the compile-time host triple using - /// `std::env::consts`. - pub fn triple(&self) -> &str { - match self { - NativeTarget::X86_64Linux => "x86_64-unknown-linux-gnu", - NativeTarget::Aarch64Linux => "aarch64-unknown-linux-gnu", - NativeTarget::X86_64Macos => "x86_64-apple-darwin", - NativeTarget::Aarch64Macos => "aarch64-apple-darwin", - NativeTarget::Wasm32 => "wasm32-unknown-unknown", - NativeTarget::Host => { - // This is a static string that depends on the compile-time target. - // We detect at compile time via cfg! macros. - host_triple() - } - } - } - - /// Output file extension for artifacts on this target. - pub fn artifact_extension(&self) -> &'static str { - match self { - NativeTarget::Wasm32 => ".wasm", - _ => { - if cfg!(target_os = "windows") { - ".exe" - } else { - "" - } - } - } - } - - /// Convert a [`CrossTarget`] to the equivalent [`NativeTarget`]. - pub fn from_cross(c: &CrossTarget) -> Self { - match c { - CrossTarget::X86_64Linux => NativeTarget::X86_64Linux, - CrossTarget::Aarch64Linux => NativeTarget::Aarch64Linux, - CrossTarget::X86_64Macos => NativeTarget::X86_64Macos, - CrossTarget::Aarch64Macos => NativeTarget::Aarch64Macos, - CrossTarget::Wasm32 => NativeTarget::Wasm32, - } - } -} - -fn host_triple() -> &'static str { - // Determine at compile time — these cfg values are set by rustc. - if cfg!(all(target_arch = "x86_64", target_os = "linux")) { - "x86_64-unknown-linux-gnu" - } else if cfg!(all(target_arch = "aarch64", target_os = "linux")) { - "aarch64-unknown-linux-gnu" - } else if cfg!(all(target_arch = "x86_64", target_os = "macos")) { - "x86_64-apple-darwin" - } else if cfg!(all(target_arch = "aarch64", target_os = "macos")) { - "aarch64-apple-darwin" - } else if cfg!(target_arch = "wasm32") { - "wasm32-unknown-unknown" - } else { - "unknown-unknown-unknown" - } -} - -impl std::fmt::Display for NativeTarget { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.triple()) - } -} - -// ── App config (native desktop) ─────────────────────────────────────────────── - -/// The `app { }` section — present only in native desktop apps using el-ui. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct AppConfig { - pub window_title: String, - pub window_width: u32, - pub window_height: u32, - pub min_width: u32, - pub min_height: u32, -} - -// ── Top-level manifest ──────────────────────────────────────────────────────── - -/// The parsed contents of a `manifest.el` project manifest. -#[derive(Debug, Clone)] -pub struct Manifest { - pub package: PackageInfo, - pub dependencies: HashMap, - pub dev_dependencies: HashMap, - pub build: BuildConfig, - pub cross: CrossConfig, - /// Plugin name → version requirement string. - pub plugins: HashMap, - /// Native desktop app config (present only when `app { }` section exists). - pub app: Option, -} - -impl Manifest { - /// Parse a manifest from an El source string. - pub fn parse(s: &str) -> crate::ManifestResult { - crate::parse::parse_manifest(s) - } - - /// Parse a manifest from a file on disk. - pub fn from_file(path: &std::path::Path) -> crate::ManifestResult { - let text = std::fs::read_to_string(path).map_err(crate::ManifestError::Io)?; - Self::parse(&text) - } - - /// Walk up the directory tree from `from` until a `manifest.el` is found. - /// - /// Returns the path to the manifest file (not its directory). - pub fn find_manifest(from: &std::path::Path) -> crate::ManifestResult { - let mut dir = if from.is_file() { - from.parent().unwrap_or(from).to_path_buf() - } else { - from.to_path_buf() - }; - - loop { - let candidate = dir.join("manifest.el"); - if candidate.exists() { - return Ok(candidate); - } - match dir.parent() { - Some(parent) => dir = parent.to_path_buf(), - None => { - return Err(crate::ManifestError::NotFound(from.display().to_string())) - } - } - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-manifest/src/parse.rs b/_archive/rust-bootstrap/engrams/el-manifest/src/parse.rs deleted file mode 100644 index 0bab906..0000000 --- a/_archive/rust-bootstrap/engrams/el-manifest/src/parse.rs +++ /dev/null @@ -1,748 +0,0 @@ -//! El-syntax parser for `manifest.el` project manifests. -//! -//! The manifest format is a subset of El block syntax: -//! -//! ```el -//! package "name" { -//! version "0.1.0" -//! description "What this does" -//! authors ["Author One"] -//! edition "2026" -//! } -//! -//! build { -//! entry "src/main.el" -//! target "release" -//! output "dist/" -//! } -//! -//! dependencies { -//! el-ui { path "../../../../foundation/el-ui" } -//! } -//! -//! app { -//! window_title "My App" -//! window_width 1200 -//! window_height 800 -//! } -//! ``` -//! -//! Rules: -//! - No equals signs — space-separated declarations -//! - String values use `"..."`, integer values are bare numbers -//! - Arrays use `[...]`, block sections use `{ }` -//! - `//` line comments are stripped by the lexer - -use std::collections::HashMap; -use std::path::PathBuf; - -use el_lexer::{tokenize, Token, Spanned}; - -use crate::error::{ManifestError, ManifestResult}; -use crate::manifest::{ - AppConfig, BuildConfig, BuildTarget, CrossConfig, CrossTarget, Dependency, Manifest, - PackageInfo, SealKeySource, -}; - -// ── Token stream wrapper ────────────────────────────────────────────────────── - -struct TokenStream { - tokens: Vec>, - pos: usize, -} - -impl TokenStream { - fn new(tokens: Vec>) -> Self { - Self { tokens, pos: 0 } - } - - fn peek(&self) -> &Token { - self.tokens.get(self.pos).map(|s| &s.node).unwrap_or(&Token::Eof) - } - - fn current_line(&self) -> u32 { - self.tokens.get(self.pos).map(|s| s.span.line).unwrap_or(0) - } - - fn advance(&mut self) -> &Spanned { - let idx = self.pos; - self.pos += 1; - &self.tokens[idx] - } - - fn expect_lbrace(&mut self) -> ManifestResult<()> { - match self.peek() { - Token::LBrace => { self.advance(); Ok(()) } - other => Err(ManifestError::Parse { - line: self.current_line(), - reason: format!("expected '{{', found '{other}'"), - }), - } - } - - fn expect_rbrace(&mut self) -> ManifestResult<()> { - match self.peek() { - Token::RBrace => { self.advance(); Ok(()) } - other => Err(ManifestError::Parse { - line: self.current_line(), - reason: format!("expected '}}', found '{other}'"), - }), - } - } - - /// Consume an identifier or return error. - fn expect_ident(&mut self) -> ManifestResult { - match self.peek().clone() { - Token::Ident(s) => { self.advance(); Ok(s) } - // Some fields like "target" are keywords in El — allow them as idents here - Token::Target => { self.advance(); Ok("target".to_string()) } - other => Err(ManifestError::Parse { - line: self.current_line(), - reason: format!("expected identifier, found '{other}'"), - }), - } - } - - /// Consume a string literal or return error. - fn expect_string(&mut self) -> ManifestResult { - match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); Ok(s) } - other => Err(ManifestError::Parse { - line: self.current_line(), - reason: format!("expected string literal, found '{other}'"), - }), - } - } - - /// Consume an integer literal or return error. - fn expect_int(&mut self) -> ManifestResult { - match self.peek().clone() { - Token::IntLiteral(n) => { self.advance(); Ok(n) } - other => Err(ManifestError::Parse { - line: self.current_line(), - reason: format!("expected integer literal, found '{other}'"), - }), - } - } - - fn is_at_end(&self) -> bool { - matches!(self.peek(), Token::Eof) - } -} - -// ── Entry point ─────────────────────────────────────────────────────────────── - -/// Parse a `manifest.el` source string into a [`Manifest`]. -pub(crate) fn parse_manifest(s: &str) -> ManifestResult { - let spanned = tokenize(s).map_err(|e| ManifestError::Parse { - line: e.span.line, - reason: format!("lex error: {}", e.kind), - })?; - - // Filter out comments and whitespace-only tokens (the lexer doesn't produce - // whitespace tokens, but Unknown chars from `//` comments might appear). - let tokens: Vec> = spanned - .into_iter() - .filter(|t| !matches!(t.node, Token::Eof)) - .collect(); - - let mut stream = TokenStream::new(tokens); - - let mut raw_package: Option = None; - let mut raw_build: Option = None; - let mut raw_deps: HashMap = HashMap::new(); - let mut raw_cross: Option = None; - let mut raw_app: Option = None; - - while !stream.is_at_end() { - let section_name = match stream.peek().clone() { - Token::Ident(s) => { stream.advance(); s } - Token::Eof => break, - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("expected section name, found '{other}'"), - }); - } - }; - - match section_name.as_str() { - "package" => { - // package "name" { ... } - let name = stream.expect_string()?; - stream.expect_lbrace()?; - raw_package = Some(parse_package_block(&mut stream, name)?); - stream.expect_rbrace()?; - } - "build" => { - stream.expect_lbrace()?; - raw_build = Some(parse_build_block(&mut stream)?); - stream.expect_rbrace()?; - } - "dependencies" => { - stream.expect_lbrace()?; - raw_deps = parse_deps_block(&mut stream)?; - stream.expect_rbrace()?; - } - "cross" => { - stream.expect_lbrace()?; - raw_cross = Some(parse_cross_block(&mut stream)?); - stream.expect_rbrace()?; - } - "app" => { - stream.expect_lbrace()?; - raw_app = Some(parse_app_block(&mut stream)?); - stream.expect_rbrace()?; - } - other => { - // Skip unknown sections gracefully by consuming until matching } - if matches!(stream.peek(), Token::LBrace) { - stream.advance(); - skip_block(&mut stream)?; - } else { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("unknown section '{other}'"), - }); - } - } - } - } - - convert(raw_package, raw_build, raw_deps, raw_cross, raw_app) -} - -// ── Block parsers ───────────────────────────────────────────────────────────── - -#[derive(Debug, Default)] -struct RawPackage { - name: String, - version: String, - description: Option, - authors: Vec, - license: Option, - edition: String, - entry: Option, // neuron-code puts entry in [package] -} - -fn parse_package_block(stream: &mut TokenStream, name: String) -> ManifestResult { - let mut pkg = RawPackage { - name, - version: "0.1.0".to_string(), - edition: "2026".to_string(), - ..Default::default() - }; - - while !matches!(stream.peek(), Token::RBrace | Token::Eof) { - let field = stream.expect_ident()?; - match field.as_str() { - "version" => pkg.version = stream.expect_string()?, - "description" => pkg.description = Some(stream.expect_string()?), - "authors" => pkg.authors = parse_string_array(stream)?, - "license" => pkg.license = Some(stream.expect_string()?), - "edition" => pkg.edition = stream.expect_string()?, - "entry" => pkg.entry = Some(stream.expect_string()?), - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("unknown package field '{other}'"), - }); - } - } - } - - Ok(pkg) -} - -#[derive(Debug, Default)] -struct RawBuild { - target: Option, - entry: Option, - output: Option, - seal_key: Option, -} - -fn parse_build_block(stream: &mut TokenStream) -> ManifestResult { - let mut build = RawBuild::default(); - - while !matches!(stream.peek(), Token::RBrace | Token::Eof) { - let field = match stream.peek().clone() { - Token::Ident(s) => { stream.advance(); s } - Token::Target => { stream.advance(); "target".to_string() } - Token::RBrace | Token::Eof => break, - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("expected build field name, found '{other}'"), - }); - } - }; - - match field.as_str() { - "target" => build.target = Some(stream.expect_string()?), - "entry" => build.entry = Some(stream.expect_string()?), - "output" => build.output = Some(stream.expect_string()?), - "seal_key" => build.seal_key = Some(stream.expect_string()?), - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("unknown build field '{other}'"), - }); - } - } - } - - Ok(build) -} - -#[derive(Debug)] -enum RawDep { - Path(String), - Version(String), -} - -fn parse_deps_block(stream: &mut TokenStream) -> ManifestResult> { - let mut deps = HashMap::new(); - - while !matches!(stream.peek(), Token::RBrace | Token::Eof) { - // Dependency name can contain hyphens — the lexer will produce Ident tokens - // separated by Minus for names like `el-ui`. We need to reconstruct the full name. - let dep_name = parse_dep_name(stream)?; - - // Value is either a string version or a block `{ path "..." }` - match stream.peek().clone() { - Token::StringLiteral(ver) => { - stream.advance(); - deps.insert(dep_name, RawDep::Version(ver)); - } - Token::LBrace => { - stream.advance(); - // Parse inner fields until } - let mut path: Option = None; - while !matches!(stream.peek(), Token::RBrace | Token::Eof) { - let field = stream.expect_ident()?; - match field.as_str() { - "path" => path = Some(stream.expect_string()?), - other => { - // Skip unknown fields (version, registry, etc.) - match stream.peek().clone() { - Token::StringLiteral(_) => { stream.advance(); } - _ => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("unknown dep field '{other}'"), - }); - } - } - } - } - } - stream.expect_rbrace()?; - match path { - Some(p) => { deps.insert(dep_name, RawDep::Path(p)); } - None => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("dependency '{dep_name}' block has no 'path' or 'version'"), - }); - } - } - } - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("expected string or block for dependency '{dep_name}', found '{other}'"), - }); - } - } - } - - Ok(deps) -} - -/// Parse a possibly-hyphenated dependency name like `el-ui` or `engram-http`. -/// The lexer produces Ident("-") — actually Minus tokens — so we need to stitch -/// Ident + (Minus + Ident)* together. -fn parse_dep_name(stream: &mut TokenStream) -> ManifestResult { - let mut name = match stream.peek().clone() { - Token::Ident(s) => { stream.advance(); s } - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("expected dependency name, found '{other}'"), - }); - } - }; - - // Greedily consume `-ident` segments - loop { - match stream.peek() { - Token::Minus => { - // Peek ahead to see if next is Ident - let next_pos = stream.pos + 1; - if let Some(next) = stream.tokens.get(next_pos) { - if let Token::Ident(_) = &next.node { - stream.advance(); // consume Minus - if let Token::Ident(seg) = stream.peek().clone() { - stream.advance(); - name.push('-'); - name.push_str(&seg); - continue; - } - } - } - break; - } - _ => break, - } - } - - Ok(name) -} - -#[derive(Debug, Default)] -struct RawCross { - targets: Vec, -} - -fn parse_cross_block(stream: &mut TokenStream) -> ManifestResult { - let mut cross = RawCross::default(); - - while !matches!(stream.peek(), Token::RBrace | Token::Eof) { - let field = match stream.peek().clone() { - Token::Ident(s) => { stream.advance(); s } - Token::Target => { stream.advance(); "targets".to_string() } - Token::RBrace | Token::Eof => break, - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("expected cross field, found '{other}'"), - }); - } - }; - - match field.as_str() { - "targets" => cross.targets = parse_string_array(stream)?, - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("unknown cross field '{other}'"), - }); - } - } - } - - Ok(cross) -} - -#[derive(Debug, Default)] -struct RawApp { - window_title: Option, - window_width: Option, - window_height: Option, - min_width: Option, - min_height: Option, -} - -fn parse_app_block(stream: &mut TokenStream) -> ManifestResult { - let mut app = RawApp::default(); - - while !matches!(stream.peek(), Token::RBrace | Token::Eof) { - let field = stream.expect_ident()?; - match field.as_str() { - "window_title" => app.window_title = Some(stream.expect_string()?), - "window_width" => app.window_width = Some(stream.expect_int()?), - "window_height" => app.window_height = Some(stream.expect_int()?), - "min_width" => app.min_width = Some(stream.expect_int()?), - "min_height" => app.min_height = Some(stream.expect_int()?), - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("unknown app field '{other}'"), - }); - } - } - } - - Ok(app) -} - -fn parse_string_array(stream: &mut TokenStream) -> ManifestResult> { - match stream.peek() { - Token::LBracket => { stream.advance(); } - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("expected '[', found '{other}'"), - }); - } - } - - let mut items = Vec::new(); - - while !matches!(stream.peek(), Token::RBracket | Token::Eof) { - // Items in the array are strings - match stream.peek().clone() { - Token::StringLiteral(s) => { - stream.advance(); - items.push(s); - // Optional comma - if matches!(stream.peek(), Token::Comma) { - stream.advance(); - } - } - Token::Comma => { stream.advance(); } // trailing comma - other => { - return Err(ManifestError::Parse { - line: stream.current_line(), - reason: format!("expected string in array, found '{other}'"), - }); - } - } - } - - match stream.peek() { - Token::RBracket => { stream.advance(); } - _ => {} // Eof handled gracefully - } - - Ok(items) -} - -/// Skip over a `{ ... }` block (already consumed the opening brace). -fn skip_block(stream: &mut TokenStream) -> ManifestResult<()> { - let mut depth = 1; - while depth > 0 && !stream.is_at_end() { - match stream.peek() { - Token::LBrace => { depth += 1; stream.advance(); } - Token::RBrace => { depth -= 1; stream.advance(); } - _ => { stream.advance(); } - } - } - Ok(()) -} - -// ── Conversion to typed manifest ────────────────────────────────────────────── - -fn convert( - raw_package: Option, - raw_build: Option, - raw_deps: HashMap, - raw_cross: Option, - raw_app: Option, -) -> ManifestResult { - let pkg_raw = raw_package.ok_or_else(|| ManifestError::MissingField("package".to_string()))?; - let package = convert_package(pkg_raw)?; - - let build = convert_build(raw_build.unwrap_or_default(), &package)?; - let dependencies = convert_deps(raw_deps)?; - let cross = convert_cross(raw_cross.unwrap_or_default())?; - let app = raw_app.map(convert_app); - - Ok(Manifest { - package, - dependencies, - dev_dependencies: HashMap::new(), - build, - cross, - plugins: HashMap::new(), - app, - }) -} - -fn convert_package(raw: RawPackage) -> ManifestResult { - let version = semver::Version::parse(&raw.version).map_err(|e| ManifestError::Semver { - field: "package.version".to_string(), - source: e, - })?; - - Ok(PackageInfo { - name: raw.name, - version, - description: raw.description, - authors: raw.authors, - license: raw.license, - edition: raw.edition, - }) -} - -fn convert_build(raw: RawBuild, _package: &PackageInfo) -> ManifestResult { - let target = raw - .target - .unwrap_or_else(|| "debug".to_string()) - .parse::()?; - - // Entry can come from build block or package block (legacy neuron-code style) - let entry_str = raw.entry.unwrap_or_else(|| "src/main.el".to_string()); - let output_str = raw.output.unwrap_or_else(|| "dist/".to_string()); - - let seal_key = raw - .seal_key - .map(|s| SealKeySource::parse(&s)) - .transpose()?; - - Ok(BuildConfig { - target, - entry: PathBuf::from(entry_str), - output: PathBuf::from(output_str), - seal_key, - }) -} - -fn convert_deps(raw: HashMap) -> ManifestResult> { - let mut out = HashMap::new(); - for (name, dep) in raw { - let converted = match dep { - RawDep::Path(p) => Dependency::Path(PathBuf::from(p)), - RawDep::Version(v) => { - let req = semver::VersionReq::parse(&v).map_err(|e| ManifestError::Semver { - field: format!("dependencies.{name}"), - source: e, - })?; - Dependency::VersionReq(req) - } - }; - out.insert(name, converted); - } - Ok(out) -} - -fn convert_cross(raw: RawCross) -> ManifestResult { - let targets = raw - .targets - .iter() - .map(|s| CrossTarget::parse(s)) - .collect::>>()?; - Ok(CrossConfig { targets }) -} - -fn convert_app(raw: RawApp) -> AppConfig { - AppConfig { - window_title: raw.window_title.unwrap_or_default(), - window_width: raw.window_width.unwrap_or(1024) as u32, - window_height: raw.window_height.unwrap_or(768) as u32, - min_width: raw.min_width.unwrap_or(400) as u32, - min_height: raw.min_height.unwrap_or(300) as u32, - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use crate::manifest::{BuildTarget, CrossTarget, Dependency}; - - fn full_manifest() -> &'static str { - r#" -// manifest.el — full example -package "my-service" { - version "0.1.0" - description "What this does" - authors ["Will Anderson "] - license "MIT" - edition "2026" -} - -build { - target "prod" - entry "src/main.el" - output "dist/" - seal_key "env:ENGRAM_SEAL_KEY" -} - -dependencies { - el-ui { path "../el-ui" } -} - -cross { - targets ["x86_64-linux", "aarch64-linux", "x86_64-macos", "aarch64-macos", "wasm32"] -} - -app { - window_title "My App" - window_width 1200 - window_height 800 - min_width 800 - min_height 600 -} -"# - } - - #[test] - fn test_parse_full_manifest() { - let m = parse_manifest(full_manifest()).unwrap(); - assert_eq!(m.package.name, "my-service"); - assert_eq!(m.package.version.to_string(), "0.1.0"); - assert_eq!(m.package.edition, "2026"); - assert_eq!(m.package.license.as_deref(), Some("MIT")); - assert_eq!(m.package.authors, vec!["Will Anderson "]); - } - - #[test] - fn test_parse_build_config() { - let m = parse_manifest(full_manifest()).unwrap(); - assert_eq!(m.build.target, BuildTarget::Prod); - assert_eq!(m.build.entry.to_str().unwrap(), "src/main.el"); - assert_eq!(m.build.output.to_str().unwrap(), "dist/"); - match &m.build.seal_key { - Some(SealKeySource::EnvVar(v)) => assert_eq!(v, "ENGRAM_SEAL_KEY"), - other => panic!("expected EnvVar seal_key, got {other:?}"), - } - } - - #[test] - fn test_parse_path_dep() { - let m = parse_manifest(full_manifest()).unwrap(); - assert!(m.dependencies.contains_key("el-ui")); - match &m.dependencies["el-ui"] { - Dependency::Path(p) => assert_eq!(p.to_str().unwrap(), "../el-ui"), - other => panic!("expected Path dep, got {other:?}"), - } - } - - #[test] - fn test_parse_cross_targets() { - let m = parse_manifest(full_manifest()).unwrap(); - assert_eq!(m.cross.targets.len(), 5); - assert!(m.cross.targets.contains(&CrossTarget::X86_64Linux)); - assert!(m.cross.targets.contains(&CrossTarget::Wasm32)); - } - - #[test] - fn test_parse_app_config() { - let m = parse_manifest(full_manifest()).unwrap(); - let app = m.app.as_ref().expect("app config missing"); - assert_eq!(app.window_title, "My App"); - assert_eq!(app.window_width, 1200); - assert_eq!(app.window_height, 800); - assert_eq!(app.min_width, 800); - assert_eq!(app.min_height, 600); - } - - #[test] - fn test_minimal_manifest() { - let src = r#" -package "hello" { - version "0.1.0" -} -"#; - let m = parse_manifest(src).unwrap(); - assert_eq!(m.package.name, "hello"); - assert_eq!(m.package.edition, "2026"); - assert_eq!(m.build.target, BuildTarget::Debug); - assert!(m.dependencies.is_empty()); - assert!(m.cross.targets.is_empty()); - } - - #[test] - fn test_missing_package_error() { - let src = r#" -build { - entry "src/main.el" -} -"#; - let err = parse_manifest(src).unwrap_err(); - assert!(err.to_string().contains("package"), "expected package error, got: {err}"); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-parser/Cargo.toml b/_archive/rust-bootstrap/engrams/el-parser/Cargo.toml deleted file mode 100644 index 70fb529..0000000 --- a/_archive/rust-bootstrap/engrams/el-parser/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "el-parser" -description = "Engram language AST and recursive-descent parser" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-lexer = { workspace = true } -thiserror = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-parser/src/ast.rs b/_archive/rust-bootstrap/engrams/el-parser/src/ast.rs deleted file mode 100644 index 9ae091e..0000000 --- a/_archive/rust-bootstrap/engrams/el-parser/src/ast.rs +++ /dev/null @@ -1,364 +0,0 @@ -//! Abstract syntax tree node types. - -use el_lexer::Span; - -// ── Test-specific nodes ─────────────────────────────────────────────────────── - -/// Which graph a test should execute against. -#[derive(Debug, Clone, PartialEq)] -pub enum TestTarget { - /// In-memory graph — default, zero external dependencies. - Unit, - /// Real Engram database pointed at by `ENGRAM_URL` / `ENGRAM_DB_PATH`. - E2e, - /// Run against both unit (in-memory) and e2e (real DB). - Both, -} - -/// A `seed Node { ... }` or `seed Edge { ... }` statement inside a test block. -#[derive(Debug, Clone, PartialEq)] -pub enum SeedStmt { - Node { - node_type: String, - content: String, - importance: f32, - tier: Option, - }, - Edge { - from: String, - to: String, - relation: String, - weight: f32, - }, -} - -// ── Literals ────────────────────────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -pub enum Literal { - Int(i64), - Float(f64), - Str(String), - Bool(bool), -} - -// ── Type expressions ────────────────────────────────────────────────────────── - -/// A type annotation in source code, e.g. `String`, `[Int]`, `User?`. -#[derive(Debug, Clone, PartialEq)] -pub enum TypeExpr { - /// A named type: `Int`, `String`, `User`, … - Named(String), - /// An array type: `[T]` - Array(Box), - /// An optional type: `T?` - Optional(Box), - /// A function type: `fn(A, B) -> C` - Fn { params: Vec, return_type: Box }, - /// `Result` — built-in error-propagation type - Result { ok: Box, err: Box }, - /// `Map` — built-in key-value map type - Map { key: Box, value: Box }, - /// A generic type parameter: `T`, `E` — used inside generic function signatures. - TypeParam(String), -} - -// ── Patterns (for match arms) ───────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -pub enum Pattern { - /// `Status::Active` - EnumVariant { enum_name: String, variant: String, payload: Option }, - /// A wildcard `_` - Wildcard, - /// A literal: `42`, `"str"`, `true` - Literal(Literal), - /// A binding: `x` - Binding(String), -} - -// ── Binary operators ────────────────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -pub enum BinOp { - Add, Sub, Mul, Div, - Eq, NotEq, Lt, Gt, LtEq, GtEq, - And, Or, - Mod, // % - BitAnd, // & - BitOr, // | (single pipe) - BitXor, // ^ - Shl, // << - Shr, // >> - NullCoalesce, // ?? -} - -// ── Unary operators ─────────────────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -pub enum UnaryOp { - Neg, // - (unary minus) - Not, // ! (logical not) - BitNot, // ~ -} - -// ── Expressions ─────────────────────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -pub enum Expr { - Literal(Literal), - Ident(String), - BinOp { op: BinOp, left: Box, right: Box }, - UnaryNot(Box), - UnaryBitNot(Box), - Call { func: Box, args: Vec }, - Block(Vec), - Match { subject: Box, arms: Vec }, - /// `activate TypeName where "semantic query string"` - Activate { type_name: String, query: String }, - /// `sealed { stmts... }` — quantum-sealed block - Sealed(Vec), - If { cond: Box, then: Box, else_: Option> }, - Field { object: Box, field: String }, - /// Array constructor: `[a, b, c]` - Array(Vec), - /// Path expression: `Status::Active` (enum variant ref) - Path { segments: Vec }, - /// Index expression: `arr[0]` - Index { object: Box, index: Box }, - /// Closure: `|x: Int| x * 2` or `|x: Int| -> Int { x * 2 }` - Closure { - params: Vec, - return_type: Option, - body: Box, - span: Span, - }, - /// Try operator: `expr?` — unwraps Result, propagates error - Try(Box), - /// Map literal: `{"key": value, ...}` - MapLiteral(Vec<(Expr, Expr)>), - /// Struct literal: `Point { x: 10, y: 20 }` - StructLit { - type_name: String, - fields: Vec<(String, Expr)>, - span: Span, - }, - /// Record update: `a with { field: new_val }` - With { - base: Box, - updates: Vec<(String, Expr)>, - }, - /// AI inference: `reason "query"` - Reason { - query: String, - }, - /// Concurrent execution: `parallel { name: expr, ... }` - Parallel { - entries: Vec<(String, Expr)>, - }, - /// Trace block: `trace "label" { stmts }` - Trace { - label: String, - body: Vec, - }, - /// A JSX element: `{children}` or `` - JsxElement { - tag: String, - attrs: Vec<(String, JsxAttrValue)>, - children: Vec, - self_closing: bool, - }, - /// A JSX expression interpolation: `{expr}` - JsxExpr(Box), - /// Plain text content inside a JSX element. - JsxText(String), -} - -/// The value of a JSX attribute. -#[derive(Debug, Clone, PartialEq)] -pub enum JsxAttrValue { - /// `attr="literal"` — a string literal - Str(String), - /// `attr={expr}` — an expression - Expr(Box), -} - -// ── Match arm ───────────────────────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -pub struct MatchArm { - pub pattern: Pattern, - pub body: Expr, - pub span: Span, -} - -// ── Decorators ──────────────────────────────────────────────────────────────── - -/// A decorator applied to a function: `@name` or `@name(args)` -#[derive(Debug, Clone, PartialEq)] -pub struct Decorator { - pub name: String, - pub args: Vec, - pub span: Span, -} - -// ── Protocol ────────────────────────────────────────────────────────────────── - -/// A method signature inside a protocol definition. -#[derive(Debug, Clone, PartialEq)] -pub struct ProtocolMethod { - pub name: String, - pub params: Vec, - pub return_type: TypeExpr, - pub span: Span, -} - -// ── Statements ──────────────────────────────────────────────────────────────── - -/// A named parameter in a function definition. -#[derive(Debug, Clone, PartialEq)] -pub struct Param { - pub name: String, - pub type_ann: TypeExpr, - pub span: Span, -} - -/// A field in a type definition. -#[derive(Debug, Clone, PartialEq)] -pub struct Field { - pub name: String, - pub type_ann: TypeExpr, - pub span: Span, -} - -/// A variant in an enum definition. -#[derive(Debug, Clone, PartialEq)] -pub struct Variant { - pub name: String, - /// Payload type, if any (e.g. `Pending(String)`) - pub payload: Option, - pub span: Span, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Stmt { - /// `let name: Type = expr` - Let { - name: String, - type_ann: Option, - value: Expr, - span: Span, - }, - /// `return expr` - Return(Expr, Span), - /// A bare expression used as a statement (usually a call). - Expr(Expr, Span), - /// `fn name(params) -> ReturnType [requires cond] { body }` (with optional decorators) - FnDef { - name: String, - decorators: Vec, - /// Generic type parameters, e.g. `["T", "E"]` for `fn foo`. - type_params: Vec, - params: Vec, - return_type: TypeExpr, - /// Optional precondition: `requires expr` - requires: Option>, - body: Vec, - span: Span, - }, - /// `type Name { fields... }` - TypeDef { - name: String, - fields: Vec, - span: Span, - }, - /// `enum Name { variants... }` - EnumDef { - name: String, - variants: Vec, - span: Span, - }, - /// `test "name" [target: unit|e2e|both] { body }` - TestDef { - name: String, - target: TestTarget, - body: Vec, - span: Span, - }, - /// `seed Node { ... }` or `seed Edge { ... }` - Seed(SeedStmt, Span), - /// `assert ` - Assert(Expr, Span), - /// `import std::collections::Map` or `from pkg import { A, B }` - Import { - path: Vec, - names: Vec, - alias: Option, - span: Span, - }, - /// `protocol Name { method sigs... }` - ProtocolDef { - name: String, - methods: Vec, - span: Span, - }, - /// `impl Protocol for TypeName { fn ... }` - ImplDef { - protocol_name: String, - type_name: String, - methods: Vec, - span: Span, - }, - /// `while { }` - While { - condition: Expr, - body: Vec, - span: Span, - }, - /// `retry N times { ... } fallback { ... }` - Retry { - count: Expr, - body: Vec, - fallback: Option>, - span: Span, - }, - /// `deploy fn_name to "/route" via target` - Deploy { - fn_name: String, - route: String, - target: String, - span: Span, - }, - /// `component Name { props { ... } state { ... } fn ... template { ... } }` - ComponentDef { - name: String, - /// Fields declared in the `props { }` block: (name, type_ann, default_expr) - props: Vec, - /// Fields declared in the `state { }` block: (name, type_ann, default_expr) - state: Vec, - /// Methods declared inside the component: plain `fn` definitions. - methods: Vec, - /// The JSX tree inside `template { }`. - template: Box, - span: Span, - }, -} - -/// A field in a component `props` or `state` block. -#[derive(Debug, Clone, PartialEq)] -pub struct ComponentField { - pub name: String, - pub type_ann: TypeExpr, - /// Default value expression (may be a Literal::Str("") placeholder for required props). - pub default: Option>, - pub span: Span, -} - -// ── Top-level program ───────────────────────────────────────────────────────── - -#[derive(Debug, Clone, PartialEq)] -pub struct Program { - pub stmts: Vec, - /// The original source, kept for diagnostics and source maps. - pub source: String, -} diff --git a/_archive/rust-bootstrap/engrams/el-parser/src/error.rs b/_archive/rust-bootstrap/engrams/el-parser/src/error.rs deleted file mode 100644 index 64cc057..0000000 --- a/_archive/rust-bootstrap/engrams/el-parser/src/error.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Parser error types. - -use thiserror::Error; -use el_lexer::{Span, Token}; - -#[derive(Debug, Clone, Error)] -#[error("{kind} at {span}")] -pub struct ParseError { - pub kind: ParseErrorKind, - pub span: Span, -} - -impl ParseError { - pub fn new(kind: ParseErrorKind, span: Span) -> Self { - Self { kind, span } - } -} - -#[derive(Debug, Clone, Error)] -pub enum ParseErrorKind { - #[error("unexpected token {got}, expected {expected}")] - UnexpectedToken { expected: String, got: String }, - - #[error("unexpected end of file")] - UnexpectedEof, - - #[error("invalid expression starting with {0}")] - InvalidExprStart(String), - - #[error("invalid type expression: {0}")] - InvalidTypeExpr(String), - - #[error("invalid pattern: {0}")] - InvalidPattern(String), - - #[error("expected identifier, got {0}")] - ExpectedIdent(String), -} - -impl ParseError { - pub fn expected(expected: impl Into, got: &Token, span: Span) -> Self { - Self::new( - ParseErrorKind::UnexpectedToken { - expected: expected.into(), - got: got.to_string(), - }, - span, - ) - } - - pub fn eof(span: Span) -> Self { - Self::new(ParseErrorKind::UnexpectedEof, span) - } -} diff --git a/_archive/rust-bootstrap/engrams/el-parser/src/lib.rs b/_archive/rust-bootstrap/engrams/el-parser/src/lib.rs deleted file mode 100644 index b6e138d..0000000 --- a/_archive/rust-bootstrap/engrams/el-parser/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! el-parser — Engram language recursive-descent parser. -//! -//! Converts a flat token stream into a typed [`Program`] AST. -//! -//! # Design -//! Hand-written recursive descent — no parser generator. Every parse function -//! returns `Result`, making the error path explicit. - -mod ast; -mod error; -mod parser; - -pub use ast::{ - BinOp, UnaryOp, ComponentField, Decorator, Expr, Field, JsxAttrValue, Literal, MatchArm, - Param, Pattern, Program, ProtocolMethod, SeedStmt, Stmt, TestTarget, TypeExpr, Variant, -}; -pub use error::{ParseError, ParseErrorKind}; -pub use parser::parse; diff --git a/_archive/rust-bootstrap/engrams/el-parser/src/parser.rs b/_archive/rust-bootstrap/engrams/el-parser/src/parser.rs deleted file mode 100644 index 69c47cd..0000000 --- a/_archive/rust-bootstrap/engrams/el-parser/src/parser.rs +++ /dev/null @@ -1,2093 +0,0 @@ -//! Recursive-descent parser for the Engram language. - -use el_lexer::{Span, Spanned, Token}; - -use crate::ast::*; -use crate::error::{ParseError, ParseErrorKind}; - -// ── For keyword in impl Statements ──────────────────────────────────────────── -// The `for` keyword is re-used in `impl Protocol for TypeName`. -// (Token::For already exists in el_lexer.) - -/// Parse a token stream into a [`Program`]. -/// -/// The `source` string is stored verbatim in the returned program for -/// diagnostics and source map generation. -pub fn parse(tokens: Vec>, source: String) -> Result { - let mut p = Parser::new(tokens); - let stmts = p.parse_program()?; - Ok(Program { stmts, source }) -} - -// ── Parser state ────────────────────────────────────────────────────────────── - -struct Parser { - tokens: Vec>, - /// Current cursor into `tokens`. - pos: usize, -} - -impl Parser { - fn new(tokens: Vec>) -> Self { - Self { tokens, pos: 0 } - } - - // ── Token stream navigation ─────────────────────────────────────────────── - - fn current(&self) -> &Spanned { - &self.tokens[self.pos.min(self.tokens.len() - 1)] - } - - fn peek(&self) -> &Token { - &self.current().node - } - - fn peek_span(&self) -> Span { - self.current().span - } - - #[allow(dead_code)] - fn peek2(&self) -> Option<&Token> { - self.tokens.get(self.pos + 1).map(|s| &s.node) - } - - fn advance(&mut self) -> &Spanned { - let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)]; - if self.pos < self.tokens.len() - 1 { - self.pos += 1; - } - tok - } - - fn at_end(&self) -> bool { - matches!(self.peek(), Token::Eof) - } - - /// Consume the current token if it matches `expected`, otherwise error. - fn expect(&mut self, expected: &Token) -> Result { - if self.peek() == expected { - let span = self.peek_span(); - self.advance(); - Ok(span) - } else { - Err(ParseError::expected(format!("{expected}"), self.peek(), self.peek_span())) - } - } - - fn expect_ident(&mut self) -> Result<(String, Span), ParseError> { - let span = self.peek_span(); - match self.peek().clone() { - Token::Ident(name) => { - self.advance(); - Ok((name, span)) - } - tok => Err(ParseError::new( - ParseErrorKind::ExpectedIdent(tok.to_string()), - span, - )), - } - } - - /// Like `expect_ident` but also accepts keywords as bare names. - /// Used in contexts like seed field names where `type:` must work. - fn expect_ident_or_keyword(&mut self) -> Result<(String, Span), ParseError> { - let span = self.peek_span(); - let name = match self.peek().clone() { - Token::Ident(name) => name, - // Accept any keyword as an identifier in seed field position - Token::Type => "type".to_string(), - Token::Fn => "fn".to_string(), - Token::Let => "let".to_string(), - Token::Enum => "enum".to_string(), - Token::Match => "match".to_string(), - Token::Return => "return".to_string(), - Token::Activate => "activate".to_string(), - Token::Where => "where".to_string(), - Token::Sealed => "sealed".to_string(), - Token::If => "if".to_string(), - Token::Else => "else".to_string(), - Token::For => "for".to_string(), - Token::In => "in".to_string(), - Token::Seed => "seed".to_string(), - Token::Assert => "assert".to_string(), - Token::Target => "target".to_string(), - Token::Protocol => "protocol".to_string(), - Token::Impl => "impl".to_string(), - Token::Import => "import".to_string(), - Token::From => "from".to_string(), - Token::As => "as".to_string(), - Token::With => "with".to_string(), - Token::While => "while".to_string(), - Token::Retry => "retry".to_string(), - Token::Times => "times".to_string(), - Token::Fallback => "fallback".to_string(), - Token::Reason => "reason".to_string(), - Token::Parallel => "parallel".to_string(), - Token::Trace => "trace".to_string(), - Token::Requires => "requires".to_string(), - Token::Deploy => "deploy".to_string(), - Token::To => "to".to_string(), - Token::Via => "via".to_string(), - Token::Component => "component".to_string(), - Token::Props => "props".to_string(), - Token::State => "state".to_string(), - Token::Template => "template".to_string(), - tok => return Err(ParseError::new( - ParseErrorKind::ExpectedIdent(tok.to_string()), - span, - )), - }; - self.advance(); - Ok((name, span)) - } - - fn eat(&mut self, tok: &Token) -> bool { - if self.peek() == tok { - self.advance(); - true - } else { - false - } - } - - // ── Top-level ───────────────────────────────────────────────────────────── - - fn parse_program(&mut self) -> Result, ParseError> { - let mut stmts = Vec::new(); - while !self.at_end() { - // Skip optional semicolons at top level - while self.eat(&Token::Semicolon) {} - if self.at_end() { - break; - } - stmts.push(self.parse_stmt()?); - } - Ok(stmts) - } - - // ── Statements ──────────────────────────────────────────────────────────── - - fn parse_stmt(&mut self) -> Result { - let start = self.peek_span(); - match self.peek().clone() { - Token::Let => self.parse_let(start), - Token::Fn => self.parse_fn_def(start, vec![]), - Token::At => { - // Look ahead: `@name fn` or `@name @` → decorated function definition. - // `@name(` or `@name` followed by anything else → builtin call expression. - // Peek 2 tokens ahead: current=@, next=ident, then after-ident - let after_ident = self.tokens.get(self.pos + 2).map(|s| &s.node); - let is_decorator = matches!(after_ident, Some(Token::Fn) | Some(Token::At)); - if is_decorator { - self.parse_decorated_fn(start) - } else { - // Parse as expression statement (builtin call) - let expr = self.parse_expr()?; - let span = start; - self.eat(&Token::Semicolon); - Ok(Stmt::Expr(expr, span)) - } - } - Token::Type => self.parse_type_def(start), - Token::Enum => self.parse_enum_def(start), - Token::Test => self.parse_test_def(start), - Token::Seed => self.parse_seed(start), - Token::Assert => self.parse_assert(start), - Token::Import => self.parse_import(start), - Token::From => self.parse_from_import(start), - Token::Protocol => self.parse_protocol_def(start), - Token::Impl => self.parse_impl_def(start), - Token::While => self.parse_while(start), - Token::Retry => self.parse_retry(start), - Token::Deploy => self.parse_deploy(start), - Token::Component => self.parse_component_def(start), - Token::Return => { - self.advance(); // consume `return` - // `return` with no value (bare return) is valid — treat as return (). - // Detect by checking if the next token can start an expression. - let expr = if matches!(self.peek(), Token::RBrace | Token::Semicolon | Token::Eof) || !is_expr_start(self.peek()) { - Expr::Block(vec![]) // empty block evaluates to Nil - } else { - self.parse_expr()? - }; - let span = Span::new(start.start, expr_span_end(&expr, start), start.line, start.col); - self.eat(&Token::Semicolon); - Ok(Stmt::Return(expr, span)) - } - _ => { - // Check for assignment: `ident = expr` or `ident.field = expr` - // This handles state mutation inside components. - if let Some(assign_stmt) = self.try_parse_assignment(start)? { - return Ok(assign_stmt); - } - let expr = self.parse_expr()?; - let span = start; - self.eat(&Token::Semicolon); - Ok(Stmt::Expr(expr, span)) - } - } - } - - /// Try to parse `ident = expr` or `ident.field = expr` as an assignment statement. - /// Returns None if the pattern doesn't match (restores position). - fn try_parse_assignment(&mut self, start: Span) -> Result, ParseError> { - let saved = self.pos; - // Only try if current token is an identifier - let name = match self.peek().clone() { - Token::Ident(n) => n, - _ => return Ok(None), - }; - self.advance(); - // Check for direct assignment: `name =` - if self.eat(&Token::Eq) { - // `name = expr` — compile as `let name = expr` (reassignment) - let value = self.parse_expr()?; - self.eat(&Token::Semicolon); - return Ok(Some(Stmt::Let { - name, - type_ann: None, - value, - span: start, - })); - } - // Restore position — not an assignment - self.pos = saved; - Ok(None) - } - - fn parse_test_def(&mut self, start: Span) -> Result { - self.expect(&Token::Test)?; - // test name is a string literal - let name = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string literal (test name)", &tok, self.peek_span())), - }; - // Optional `target: unit|e2e|both` - let target = if self.eat(&Token::Target) { - self.expect(&Token::Colon)?; - let (target_name, span) = self.expect_ident()?; - match target_name.as_str() { - "unit" => crate::ast::TestTarget::Unit, - "e2e" => crate::ast::TestTarget::E2e, - "both" => crate::ast::TestTarget::Both, - other => return Err(ParseError::new( - ParseErrorKind::InvalidExprStart(format!("unknown test target '{other}': use unit, e2e, or both")), - span, - )), - } - } else { - crate::ast::TestTarget::Unit - }; - self.expect(&Token::LBrace)?; - let body = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Ok(Stmt::TestDef { name, target, body, span: start }) - } - - fn parse_seed(&mut self, start: Span) -> Result { - self.expect(&Token::Seed)?; - let (kind, _) = self.expect_ident()?; - self.expect(&Token::LBrace)?; - - let seed = match kind.as_str() { - "Node" => { - let mut node_type = String::new(); - let mut content = String::new(); - let mut importance: f32 = 1.0; - let mut tier: Option = None; - - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let (field_name, _) = self.expect_ident_or_keyword()?; - self.expect(&Token::Colon)?; - match field_name.as_str() { - "type" => { - node_type = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string", &tok, self.peek_span())), - }; - } - "content" => { - content = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string", &tok, self.peek_span())), - }; - } - "importance" => { - importance = match self.peek().clone() { - Token::FloatLiteral(f) => { self.advance(); f as f32 } - Token::IntLiteral(n) => { self.advance(); n as f32 } - tok => return Err(ParseError::expected("float", &tok, self.peek_span())), - }; - } - "tier" => { - let (t, _) = self.expect_ident()?; - tier = Some(t); - } - _ => { - // Skip unknown fields gracefully - self.parse_expr()?; - } - } - self.eat(&Token::Comma); - self.eat(&Token::Semicolon); - } - crate::ast::SeedStmt::Node { node_type, content, importance, tier } - } - "Edge" => { - let mut from = String::new(); - let mut to = String::new(); - let mut relation = String::new(); - let mut weight: f32 = 1.0; - - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let (field_name, _) = self.expect_ident_or_keyword()?; - self.expect(&Token::Colon)?; - match field_name.as_str() { - "from" => { - from = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string", &tok, self.peek_span())), - }; - } - "to" => { - to = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string", &tok, self.peek_span())), - }; - } - "relation" => { - let (rel, _) = self.expect_ident()?; - relation = rel; - } - "weight" => { - weight = match self.peek().clone() { - Token::FloatLiteral(f) => { self.advance(); f as f32 } - Token::IntLiteral(n) => { self.advance(); n as f32 } - tok => return Err(ParseError::expected("float", &tok, self.peek_span())), - }; - } - _ => { - self.parse_expr()?; - } - } - self.eat(&Token::Comma); - self.eat(&Token::Semicolon); - } - crate::ast::SeedStmt::Edge { from, to, relation, weight } - } - other => return Err(ParseError::new( - ParseErrorKind::InvalidExprStart(format!("unknown seed kind '{other}': use Node or Edge")), - start, - )), - }; - - self.expect(&Token::RBrace)?; - self.eat(&Token::Semicolon); - Ok(Stmt::Seed(seed, start)) - } - - fn parse_assert(&mut self, start: Span) -> Result { - self.expect(&Token::Assert)?; - let expr = self.parse_expr()?; - self.eat(&Token::Semicolon); - Ok(Stmt::Assert(expr, start)) - } - - fn parse_let(&mut self, start: Span) -> Result { - self.expect(&Token::Let)?; - let (name, _) = self.expect_ident()?; - let type_ann = if self.eat(&Token::Colon) { - Some(self.parse_type_expr()?) - } else { - None - }; - self.expect(&Token::Eq)?; - let value = self.parse_expr()?; - self.eat(&Token::Semicolon); - Ok(Stmt::Let { name, type_ann, value, span: start }) - } - - fn parse_fn_def(&mut self, start: Span, decorators: Vec) -> Result { - self.expect(&Token::Fn)?; - let (name, _) = self.expect_ident()?; - // Optional generic type parameters: `` - let type_params = if self.eat(&Token::Lt) { - let mut tps = Vec::new(); - while !matches!(self.peek(), Token::Gt | Token::Eof) { - let (tp, _) = self.expect_ident()?; - tps.push(tp); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::Gt)?; - tps - } else { - Vec::new() - }; - self.expect(&Token::LParen)?; - let params = self.parse_param_list_with_type_params(&type_params)?; - self.expect(&Token::RParen)?; - self.expect(&Token::Arrow)?; - let return_type = self.parse_type_expr_with_params(&type_params)?; - // Optional `requires expr` - let requires = if self.eat(&Token::Requires) { - Some(Box::new(self.parse_expr()?)) - } else { - None - }; - self.expect(&Token::LBrace)?; - let body = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Ok(Stmt::FnDef { name, decorators, type_params, params, return_type, requires, body, span: start }) - } - - /// Parse one or more `@decorator` annotations, then the `fn` definition. - fn parse_decorated_fn(&mut self, start: Span) -> Result { - let mut decorators = Vec::new(); - while matches!(self.peek(), Token::At) { - let dec_span = self.peek_span(); - self.advance(); // consume `@` - let (name, _) = self.expect_ident()?; - let args = if self.eat(&Token::LParen) { - let mut a = Vec::new(); - while !matches!(self.peek(), Token::RParen | Token::Eof) { - a.push(self.parse_expr()?); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RParen)?; - a - } else { - vec![] - }; - decorators.push(Decorator { name, args, span: dec_span }); - } - // After decorators, expect `fn` - if !matches!(self.peek(), Token::Fn) { - return Err(ParseError::expected("fn", self.peek(), self.peek_span())); - } - self.parse_fn_def(start, decorators) - } - - /// Parse `import std::collections::Map` or `import std::array::{map, filter}` - fn parse_import(&mut self, start: Span) -> Result { - self.expect(&Token::Import)?; - let mut path = Vec::new(); - let (first, _) = self.expect_ident()?; - path.push(first); - while self.eat(&Token::ColonColon) { - // Could be `{name, name}` for multi-import - if matches!(self.peek(), Token::LBrace) { - self.advance(); - let mut names = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let (n, _) = self.expect_ident()?; - names.push(n); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RBrace)?; - let alias = if self.eat(&Token::As) { - let (a, _) = self.expect_ident()?; - Some(a) - } else { - None - }; - self.eat(&Token::Semicolon); - return Ok(Stmt::Import { path, names, alias, span: start }); - } - let (seg, _) = self.expect_ident()?; - path.push(seg); - } - let alias = if self.eat(&Token::As) { - let (a, _) = self.expect_ident()?; - Some(a) - } else { - None - }; - self.eat(&Token::Semicolon); - Ok(Stmt::Import { path, names: vec![], alias, span: start }) - } - - /// Parse `from mypackage import { Thing, OtherThing }` - fn parse_from_import(&mut self, start: Span) -> Result { - self.expect(&Token::From)?; - let mut path = Vec::new(); - let (first, _) = self.expect_ident()?; - path.push(first); - while self.eat(&Token::ColonColon) { - let (seg, _) = self.expect_ident()?; - path.push(seg); - } - self.expect(&Token::Import)?; - let names = if self.eat(&Token::LBrace) { - let mut ns = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let (n, _) = self.expect_ident()?; - ns.push(n); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RBrace)?; - ns - } else { - let (n, _) = self.expect_ident()?; - vec![n] - }; - self.eat(&Token::Semicolon); - Ok(Stmt::Import { path, names, alias: None, span: start }) - } - - /// Parse `protocol Name { fn method(params) -> ReturnType }` - fn parse_protocol_def(&mut self, start: Span) -> Result { - self.expect(&Token::Protocol)?; - let (name, _) = self.expect_ident()?; - self.expect(&Token::LBrace)?; - let mut methods = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - while self.eat(&Token::Semicolon) {} - if matches!(self.peek(), Token::RBrace | Token::Eof) { break; } - let method_span = self.peek_span(); - self.expect(&Token::Fn)?; - let (method_name, _) = self.expect_ident()?; - self.expect(&Token::LParen)?; - let params = self.parse_param_list()?; - self.expect(&Token::RParen)?; - self.expect(&Token::Arrow)?; - let return_type = self.parse_type_expr()?; - methods.push(ProtocolMethod { name: method_name, params, return_type, span: method_span }); - } - self.expect(&Token::RBrace)?; - Ok(Stmt::ProtocolDef { name, methods, span: start }) - } - - /// Parse `impl Protocol for TypeName { fn ... }` - fn parse_impl_def(&mut self, start: Span) -> Result { - self.expect(&Token::Impl)?; - let (protocol_name, _) = self.expect_ident()?; - self.expect(&Token::For)?; - let (type_name, _) = self.expect_ident()?; - self.expect(&Token::LBrace)?; - let mut methods = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - while self.eat(&Token::Semicolon) {} - if matches!(self.peek(), Token::RBrace | Token::Eof) { break; } - let method_start = self.peek_span(); - if matches!(self.peek(), Token::Fn) { - let m = self.parse_fn_def(method_start, vec![])?; - methods.push(m); - } else { - return Err(ParseError::expected("fn", self.peek(), self.peek_span())); - } - } - self.expect(&Token::RBrace)?; - Ok(Stmt::ImplDef { protocol_name, type_name, methods, span: start }) - } - - /// Parse `while { }` - fn parse_while(&mut self, start: Span) -> Result { - self.expect(&Token::While)?; - let condition = self.parse_expr()?; - self.expect(&Token::LBrace)?; - let body = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Ok(Stmt::While { condition, body, span: start }) - } - - /// Parse `retry N times { ... } fallback { ... }` - fn parse_retry(&mut self, start: Span) -> Result { - self.expect(&Token::Retry)?; - let count = self.parse_expr()?; - self.expect(&Token::Times)?; - self.expect(&Token::LBrace)?; - let body = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - let fallback = if self.eat(&Token::Fallback) { - self.expect(&Token::LBrace)?; - let fb = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Some(fb) - } else { - None - }; - Ok(Stmt::Retry { count, body, fallback, span: start }) - } - - /// Parse `deploy fn_name to "/route" via target` - fn parse_deploy(&mut self, start: Span) -> Result { - self.expect(&Token::Deploy)?; - let (fn_name, _) = self.expect_ident()?; - self.expect(&Token::To)?; - let route = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string literal (route)", &tok, self.peek_span())), - }; - self.expect(&Token::Via)?; - let (target, _) = self.expect_ident()?; - self.eat(&Token::Semicolon); - Ok(Stmt::Deploy { fn_name, route, target, span: start }) - } - - /// Parse `component Name { props { ... } state { ... } fn ...* template { ... } }` - fn parse_component_def(&mut self, start: Span) -> Result { - self.expect(&Token::Component)?; - let (name, _) = self.expect_ident()?; - self.expect(&Token::LBrace)?; - - let mut props: Vec = Vec::new(); - let mut state: Vec = Vec::new(); - let mut methods: Vec = Vec::new(); - let mut template: Expr = Expr::JsxElement { - tag: "div".to_string(), - attrs: vec![], - children: vec![], - self_closing: false, - }; - - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - while self.eat(&Token::Semicolon) {} - if matches!(self.peek(), Token::RBrace | Token::Eof) { break; } - - match self.peek().clone() { - Token::Props => { - self.advance(); // consume `props` - self.expect(&Token::LBrace)?; - props = self.parse_component_fields()?; - self.expect(&Token::RBrace)?; - } - Token::State => { - self.advance(); // consume `state` - self.expect(&Token::LBrace)?; - state = self.parse_component_fields()?; - self.expect(&Token::RBrace)?; - } - Token::Template => { - self.advance(); // consume `template` - self.expect(&Token::LBrace)?; - template = self.parse_jsx_content()?; - self.expect(&Token::RBrace)?; - } - Token::Fn => { - let method_start = self.peek_span(); - let m = self.parse_fn_def(method_start, vec![])?; - methods.push(m); - } - Token::At => { - let method_start = self.peek_span(); - let m = self.parse_decorated_fn(method_start)?; - methods.push(m); - } - _ => { - // Skip unknown tokens inside component block gracefully - self.advance(); - } - } - } - - self.expect(&Token::RBrace)?; - Ok(Stmt::ComponentDef { name, props, state, methods, template: Box::new(template), span: start }) - } - - /// Parse a list of component fields (for props/state blocks): - /// `field_name: TypeExpr = default_expr` - fn parse_component_fields(&mut self) -> Result, ParseError> { - let mut fields = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - while self.eat(&Token::Semicolon) {} - if matches!(self.peek(), Token::RBrace | Token::Eof) { break; } - - let span = self.peek_span(); - let (fname, _) = self.expect_ident()?; - self.expect(&Token::Colon)?; - let type_ann = self.parse_type_expr()?; - let default = if self.eat(&Token::Eq) { - Some(Box::new(self.parse_expr()?)) - } else { - None - }; - fields.push(ComponentField { name: fname, type_ann, default, span }); - self.eat(&Token::Comma); - self.eat(&Token::Semicolon); - } - Ok(fields) - } - - /// Parse JSX content inside a template block: a sequence of JSX nodes. - /// Returns the first/root JSX node (or a wrapping fragment if multiple). - fn parse_jsx_content(&mut self) -> Result { - let mut children = self.parse_jsx_children()?; - if children.len() == 1 { - Ok(children.remove(0)) - } else { - // Wrap in a fragment div - Ok(Expr::JsxElement { - tag: "fragment".to_string(), - attrs: vec![], - children, - self_closing: false, - }) - } - } - - /// Parse a sequence of JSX children until we hit `}` (for template blocks) - /// or ` Result, ParseError> { - let mut children = Vec::new(); - loop { - match self.peek().clone() { - // End of template block or EOF - Token::RBrace | Token::Eof => break, - // JSX expression interpolation: {expr} - Token::LBrace => { - self.advance(); // consume `{` - // Check if this is just a closing brace (empty) - unlikely but safe - if matches!(self.peek(), Token::RBrace) { - self.advance(); - continue; - } - let expr = self.parse_expr()?; - // Consume closing `}` only if it's there - // (for map/block literals inside JSX, the expr parser may consume it) - if matches!(self.peek(), Token::RBrace) { - self.advance(); - } - children.push(Expr::JsxExpr(Box::new(expr))); - } - // Opening tag: ` { - // Check if it's a closing tag ` { - self.advance(); - // Always emit "#" as text; the next token (if `{`) becomes a JsxExpr - children.push(Expr::JsxText("#".to_string())); - } - // Unknown character (non-ASCII, emoji, etc.) in JSX text — emit as text - Token::Unknown(c) => { - let text = c.to_string(); - self.advance(); - children.push(Expr::JsxText(text)); - } - // Anything else: treat as text (skip unknown tokens) - _ => { - self.advance(); - } - } - } - Ok(children) - } - - /// Parse a JSX element: `children` or `` - fn parse_jsx_element(&mut self) -> Result { - self.expect(&Token::Lt)?; // consume `<` - - // Tag name: could be `Ident` (uppercase for components, lowercase for HTML tags) - let tag = match self.peek().clone() { - Token::Ident(name) => { self.advance(); name } - tok => return Err(ParseError::expected("tag name", &tok, self.peek_span())), - }; - - // Parse attributes until `>` or `/>` - let mut attrs = Vec::new(); - loop { - match self.peek().clone() { - // End of opening tag - Token::Gt => { - self.advance(); // consume `>` - break; - } - // Self-closing: `/>` - Token::Slash => { - self.advance(); // consume `/` - self.expect(&Token::Gt)?; // consume `>` - return Ok(Expr::JsxElement { - tag, - attrs, - children: vec![], - self_closing: true, - }); - } - Token::Eof => break, - // Attribute name: could be identifier or `on:click` style (ident:ident) - Token::Ident(attr_name) => { - self.advance(); - // Check for `on:click` style (colon-qualified event name) - let full_attr_name = if matches!(self.peek(), Token::Colon) { - self.advance(); // consume `:` - let (event_name, _) = self.expect_ident()?; - format!("{}:{}", attr_name, event_name) - } else { - attr_name - }; - // Attribute value: `="literal"` or `={expr}` - if self.eat(&Token::Eq) { - let attr_val = match self.peek().clone() { - Token::StringLiteral(s) => { - self.advance(); - JsxAttrValue::Str(s) - } - Token::LBrace => { - self.advance(); // consume `{` - let expr = self.parse_expr()?; - self.expect(&Token::RBrace)?; - JsxAttrValue::Expr(Box::new(expr)) - } - tok => return Err(ParseError::expected("string literal or {expr}", &tok, self.peek_span())), - }; - attrs.push((full_attr_name, attr_val)); - } else { - // Boolean attribute (no value) - attrs.push((full_attr_name, JsxAttrValue::Str("true".to_string()))); - } - } - _ => { - // Skip unknown tokens in attribute position - self.advance(); - } - } - } - - // Now parse children until `` - let children = self.parse_jsx_children()?; - - // Consume closing tag: `` - if matches!(self.peek(), Token::Lt) { - self.advance(); // consume `<` - if self.eat(&Token::Slash) { - // Consume tag name (we don't validate it matches) - if matches!(self.peek(), Token::Ident(_)) { - self.advance(); - } - // Consume `>` - let _ = self.eat(&Token::Gt); - } - } - - Ok(Expr::JsxElement { - tag, - attrs, - children, - self_closing: false, - }) - } - - /// Try to parse `(ident, ident, ...) => expr` arrow function. - /// If the pattern doesn't match, restore position and return None. - /// Precondition: `(` has already been consumed. - fn try_parse_arrow_fn(&mut self, span: Span) -> Result, ParseError> { - let saved_pos = self.pos; - let mut params: Vec = Vec::new(); - - // Try to parse `ident, ident, ... )` followed by `=>` - loop { - match self.peek().clone() { - Token::Ident(name) => { - params.push(name); - self.advance(); - // After ident: could be `,` or `)` or `: Type` - if self.eat(&Token::Colon) { - // Typed param: skip the type expression by advancing past it - // Simple type: ident or ident? - if matches!(self.peek(), Token::Ident(_)) { - self.advance(); - self.eat(&Token::QuestionMark); - } - } - if self.eat(&Token::Comma) { - continue; - } - if matches!(self.peek(), Token::RParen) { - break; - } - // Not an arrow fn pattern - self.pos = saved_pos; - return Ok(None); - } - Token::RParen => break, - _ => { - self.pos = saved_pos; - return Ok(None); - } - } - } - - self.expect(&Token::RParen)?; - - // Check for `=>` - if !self.eat(&Token::FatArrow) { - self.pos = saved_pos; - return Ok(None); - } - - // Parse body - let body = self.parse_expr()?; - - let closure_params = params.into_iter().map(|name| Param { - name, - type_ann: TypeExpr::Named("_".to_string()), - span, - }).collect(); - - Ok(Some(Expr::Closure { - params: closure_params, - return_type: None, - body: Box::new(body), - span, - })) - } - - #[allow(dead_code)] - fn parse_param_list(&mut self) -> Result, ParseError> { - self.parse_param_list_with_type_params(&[]) - } - - fn parse_param_list_with_type_params(&mut self, type_params: &[String]) -> Result, ParseError> { - let mut params = Vec::new(); - while !matches!(self.peek(), Token::RParen | Token::Eof | Token::Pipe) { - let span = self.peek_span(); - let (name, _) = self.expect_ident()?; - self.expect(&Token::Colon)?; - let type_ann = self.parse_type_expr_with_params(type_params)?; - params.push(Param { name, type_ann, span }); - if !self.eat(&Token::Comma) { - break; - } - } - Ok(params) - } - - fn parse_type_def(&mut self, start: Span) -> Result { - self.expect(&Token::Type)?; - let (name, _) = self.expect_ident()?; - self.expect(&Token::LBrace)?; - let mut fields = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let span = self.peek_span(); - let (fname, _) = self.expect_ident()?; - self.expect(&Token::Colon)?; - let type_ann = self.parse_type_expr()?; - fields.push(Field { name: fname, type_ann, span }); - self.eat(&Token::Comma); - self.eat(&Token::Semicolon); - } - self.expect(&Token::RBrace)?; - Ok(Stmt::TypeDef { name, fields, span: start }) - } - - fn parse_enum_def(&mut self, start: Span) -> Result { - self.expect(&Token::Enum)?; - let (name, _) = self.expect_ident()?; - self.expect(&Token::LBrace)?; - let mut variants = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let span = self.peek_span(); - let (vname, _) = self.expect_ident()?; - let payload = if self.eat(&Token::LParen) { - let ty = self.parse_type_expr()?; - self.expect(&Token::RParen)?; - Some(ty) - } else { - None - }; - variants.push(Variant { name: vname, payload, span }); - self.eat(&Token::Comma); - self.eat(&Token::Semicolon); - } - self.expect(&Token::RBrace)?; - Ok(Stmt::EnumDef { name, variants, span: start }) - } - - fn parse_block_body(&mut self) -> Result, ParseError> { - let mut stmts = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - while self.eat(&Token::Semicolon) {} - if matches!(self.peek(), Token::RBrace | Token::Eof) { - break; - } - stmts.push(self.parse_stmt()?); - } - Ok(stmts) - } - - // ── Type expressions ────────────────────────────────────────────────────── - - fn parse_type_expr(&mut self) -> Result { - self.parse_type_expr_with_params(&[]) - } - - fn parse_type_expr_with_params(&mut self, type_params: &[String]) -> Result { - let span = self.peek_span(); - // Array type: [T] - if self.eat(&Token::LBracket) { - let inner = self.parse_type_expr_with_params(type_params)?; - self.expect(&Token::RBracket)?; - let mut te = TypeExpr::Array(Box::new(inner)); - // Optional array: [T]? - if self.eat(&Token::QuestionMark) { - te = TypeExpr::Optional(Box::new(te)); - } - return Ok(te); - } - // Named type - let name = match self.peek().clone() { - Token::Ident(n) => { self.advance(); n } - tok => return Err(ParseError::new( - ParseErrorKind::InvalidTypeExpr(tok.to_string()), - span, - )), - }; - // Check for function type: fn(A) -> B OR Fn(A) -> B - if name == "fn" || name == "Fn" { - self.expect(&Token::LParen)?; - let mut params = Vec::new(); - while !matches!(self.peek(), Token::RParen | Token::Eof) { - params.push(self.parse_type_expr_with_params(type_params)?); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RParen)?; - self.expect(&Token::Arrow)?; - let ret = self.parse_type_expr_with_params(type_params)?; - return Ok(TypeExpr::Fn { params, return_type: Box::new(ret) }); - } - // Result — built-in generic result type - if name == "Result" && self.eat(&Token::Lt) { - let ok = self.parse_type_expr()?; - self.expect(&Token::Comma)?; - let err = self.parse_type_expr()?; - self.expect(&Token::Gt)?; - let mut te = TypeExpr::Result { ok: Box::new(ok), err: Box::new(err) }; - if self.eat(&Token::QuestionMark) { - te = TypeExpr::Optional(Box::new(te)); - } - return Ok(te); - } - // Map — built-in map type - if name == "Map" && self.eat(&Token::Lt) { - let key = self.parse_type_expr()?; - self.expect(&Token::Comma)?; - let value = self.parse_type_expr()?; - self.expect(&Token::Gt)?; - let mut te = TypeExpr::Map { key: Box::new(key), value: Box::new(value) }; - if self.eat(&Token::QuestionMark) { - te = TypeExpr::Optional(Box::new(te)); - } - return Ok(te); - } - // If the name is in the current generic type params list, emit TypeParam - if type_params.contains(&name) { - return Ok(TypeExpr::TypeParam(name)); - } - // Named type with optional ? suffix - let mut te = TypeExpr::Named(name); - if self.eat(&Token::QuestionMark) { - te = TypeExpr::Optional(Box::new(te)); - } - Ok(te) - } - - // ── Expressions ─────────────────────────────────────────────────────────── - - fn parse_expr(&mut self) -> Result { - self.parse_ternary_expr() - } - - /// Parse ternary: `cond ? then_expr : else_expr` - /// Also handles null-coalescing: `a ?? b` (returns `b` if `a` is nil/empty). - fn parse_ternary_expr(&mut self) -> Result { - let cond = self.parse_pipe_expr()?; - // Null-coalescing: `a ?? b` — desugars to a binary NullCoalesce op - if self.eat(&Token::NullCoalesce) { - let default_expr = self.parse_ternary_expr()?; - return Ok(Expr::BinOp { - op: BinOp::NullCoalesce, - left: Box::new(cond), - right: Box::new(default_expr), - }); - } - if self.eat(&Token::QuestionMark) { - // Both branches allow nested ternaries (right-associative). - // `a ? b ? c : d : e` → `a ? (b ? c : d) : e` - let then_expr = self.parse_ternary_expr()?; - self.expect(&Token::Colon)?; - let else_expr = self.parse_ternary_expr()?; - return Ok(Expr::If { - cond: Box::new(cond), - then: Box::new(then_expr), - else_: Some(Box::new(else_expr)), - }); - } - Ok(cond) - } - - fn parse_bitwise_or_expr(&mut self) -> Result { - let mut left = self.parse_bitwise_xor_expr()?; - while self.eat(&Token::Pipe) { - let right = self.parse_bitwise_xor_expr()?; - left = Expr::BinOp { op: BinOp::BitOr, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_bitwise_xor_expr(&mut self) -> Result { - let mut left = self.parse_bitwise_and_expr()?; - while self.eat(&Token::Caret) { - let right = self.parse_bitwise_and_expr()?; - left = Expr::BinOp { op: BinOp::BitXor, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_bitwise_and_expr(&mut self) -> Result { - let mut left = self.parse_shift_expr()?; - while self.eat(&Token::Ampersand) { - let right = self.parse_shift_expr()?; - left = Expr::BinOp { op: BinOp::BitAnd, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_shift_expr(&mut self) -> Result { - let mut left = self.parse_additive()?; - loop { - let op = match self.peek() { - Token::Shl => BinOp::Shl, - Token::Shr => BinOp::Shr, - _ => break, - }; - self.advance(); - let right = self.parse_additive()?; - left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - /// pipe_expr = or_expr (|> ident)* - /// `a |> f` desugars to `Call(f, [a])` - fn parse_pipe_expr(&mut self) -> Result { - let mut left = self.parse_or_expr()?; - while self.eat(&Token::PipeOp) { - // RHS must be a callable (ident or path) - let func_expr = self.parse_postfix()?; - left = Expr::Call { - func: Box::new(func_expr), - args: vec![left], - }; - } - Ok(left) - } - - fn parse_or_expr(&mut self) -> Result { - let mut left = self.parse_and_expr()?; - while self.eat(&Token::Or) { - let right = self.parse_and_expr()?; - left = Expr::BinOp { op: BinOp::Or, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_and_expr(&mut self) -> Result { - let mut left = self.parse_equality()?; - while self.eat(&Token::And) { - let right = self.parse_equality()?; - left = Expr::BinOp { op: BinOp::And, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_equality(&mut self) -> Result { - let mut left = self.parse_comparison()?; - loop { - let op = match self.peek() { - Token::EqEq => BinOp::Eq, - Token::NotEq => BinOp::NotEq, - _ => break, - }; - self.advance(); - let right = self.parse_comparison()?; - left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_comparison(&mut self) -> Result { - let mut left = self.parse_bitwise_or_expr()?; - loop { - let op = match self.peek() { - Token::Lt => { - // Disambiguate `<` as less-than vs the start of a JSX element `` is always JSX. - let after_lt = self.tokens.get(self.pos + 1).map(|s| &s.node); - if matches!(after_lt, Some(Token::Ident(_))) { - let lt_line = self.tokens[self.pos].span.line; - let prev_line = if self.pos > 0 { self.tokens[self.pos - 1].span.line } else { lt_line }; - if lt_line != prev_line { - // `<` is on a new line — it's a JSX element, not a comparison - break; - } - } - BinOp::Lt - } - Token::Gt => BinOp::Gt, - Token::LtEq => BinOp::LtEq, - Token::GtEq => BinOp::GtEq, - _ => break, - }; - self.advance(); - let right = self.parse_additive()?; - left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_additive(&mut self) -> Result { - let mut left = self.parse_multiplicative()?; - loop { - let op = match self.peek() { - Token::Plus => BinOp::Add, - Token::Minus => BinOp::Sub, - _ => break, - }; - self.advance(); - let right = self.parse_multiplicative()?; - left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_multiplicative(&mut self) -> Result { - let mut left = self.parse_unary()?; - loop { - let op = match self.peek() { - Token::Star => BinOp::Mul, - Token::Slash => BinOp::Div, - Token::Percent => BinOp::Mod, - _ => break, - }; - self.advance(); - let right = self.parse_unary()?; - left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) }; - } - Ok(left) - } - - fn parse_unary(&mut self) -> Result { - if self.eat(&Token::Not) { - let inner = self.parse_unary()?; - return Ok(Expr::UnaryNot(Box::new(inner))); - } - if self.eat(&Token::Tilde) { - let inner = self.parse_unary()?; - return Ok(Expr::UnaryBitNot(Box::new(inner))); - } - if self.eat(&Token::Minus) { - let inner = self.parse_unary()?; - // Desugar: `-x` → `0 - x` - return Ok(Expr::BinOp { - op: BinOp::Sub, - left: Box::new(Expr::Literal(crate::ast::Literal::Int(0))), - right: Box::new(inner), - }); - } - self.parse_postfix() - } - - fn parse_postfix(&mut self) -> Result { - let mut expr = self.parse_primary()?; - loop { - match self.peek() { - Token::Dot => { - self.advance(); - // Allow keywords as field names (e.g. `e.target`, `e.type`, `e.key`) - let field = match self.peek().clone() { - Token::Ident(name) => { self.advance(); name } - // Any other token used as a field name (keywords, builtins) - tok => { - let name = tok.to_string(); - self.advance(); - name - } - }; - expr = Expr::Field { object: Box::new(expr), field }; - } - Token::LParen => { - self.advance(); - let args = self.parse_arg_list()?; - self.expect(&Token::RParen)?; - expr = Expr::Call { func: Box::new(expr), args }; - } - Token::LBracket => { - self.advance(); - let index = self.parse_expr()?; - self.expect(&Token::RBracket)?; - expr = Expr::Index { object: Box::new(expr), index: Box::new(index) }; - } - Token::QuestionMark => { - // Disambiguate: `?` as Try postfix vs `?` as ternary start. - // If the token AFTER `?` can start an expression, this is ternary - // and we should NOT consume it here (let parse_ternary_expr handle it). - let after_q = self.tokens.get(self.pos + 1).map(|s| &s.node); - if after_q.is_some_and(is_expr_start) { - // Ternary — stop postfix loop, bubble up to parse_ternary_expr - break; - } - self.advance(); - expr = Expr::Try(Box::new(expr)); - } - Token::With => { - self.advance(); // consume `with` - self.expect(&Token::LBrace)?; - let mut updates = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let (field_name, _) = self.expect_ident()?; - self.expect(&Token::Colon)?; - let field_expr = self.parse_expr()?; - updates.push((field_name, field_expr)); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RBrace)?; - expr = Expr::With { base: Box::new(expr), updates }; - } - // Type cast: `expr as TypeName` — treated as a no-op cast (same value, type hint only) - Token::As => { - self.advance(); // consume `as` - // Parse the target type expression (we don't use it at runtime) - let _ = self.parse_type_expr(); - // expr remains unchanged — the cast is informational - } - _ => break, - } - } - Ok(expr) - } - - fn parse_arg_list(&mut self) -> Result, ParseError> { - let mut args = Vec::new(); - while !matches!(self.peek(), Token::RParen | Token::Eof) { - args.push(self.parse_expr()?); - if !self.eat(&Token::Comma) { break; } - } - Ok(args) - } - - fn parse_primary(&mut self) -> Result { - let span = self.peek_span(); - match self.peek().clone() { - // Literals - Token::IntLiteral(n) => { self.advance(); Ok(Expr::Literal(Literal::Int(n))) } - Token::FloatLiteral(f) => { self.advance(); Ok(Expr::Literal(Literal::Float(f))) } - Token::StringLiteral(s) => { self.advance(); Ok(Expr::Literal(Literal::Str(s))) } - Token::BoolLiteral(b) => { self.advance(); Ok(Expr::Literal(Literal::Bool(b))) } - - // Grouped expression or arrow function: `(expr)` or `(params) => body` or `() => body` - Token::LParen => { - self.advance(); // consume `(` - // Empty parens `()` — must be `() => expr` arrow function - if matches!(self.peek(), Token::RParen) { - self.advance(); // consume `)` - if self.eat(&Token::FatArrow) { - let body = self.parse_expr()?; - return Ok(Expr::Closure { - params: vec![], - return_type: None, - body: Box::new(body), - span, - }); - } - // `()` with no `=>` — return nil literal as placeholder - return Ok(Expr::Literal(Literal::Bool(false))); - } - // Check if this is `(ident, ident, ...)` or `(ident: Type, ...)` arrow params - // by attempting to parse as param list with look-ahead - if let Some(arrow_fn) = self.try_parse_arrow_fn(span)? { - return Ok(arrow_fn); - } - // Otherwise parse as grouped expression - let expr = self.parse_expr()?; - self.expect(&Token::RParen)?; - // Check again for `=>` (in case try_parse_arrow_fn consumed nothing) - if self.eat(&Token::FatArrow) { - // Treat the expr as a single untyped param name - let param_name = match &expr { - Expr::Ident(name) => name.clone(), - _ => "__arg__".to_string(), - }; - let body = self.parse_expr()?; - return Ok(Expr::Closure { - params: vec![Param { - name: param_name, - type_ann: TypeExpr::Named("_".to_string()), - span, - }], - return_type: None, - body: Box::new(body), - span, - }); - } - Ok(expr) - } - - // Block or map literal - Token::LBrace => { - self.advance(); - // Peek ahead: if next is a string/ident followed by colon, it's a map literal - // For simplicity, check if we see string/ident + colon at start - if self.is_map_literal() { - let mut pairs = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let key = self.parse_expr()?; - self.expect(&Token::Colon)?; - let val = self.parse_expr()?; - pairs.push((key, val)); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RBrace)?; - Ok(Expr::MapLiteral(pairs)) - } else { - let stmts = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Ok(Expr::Block(stmts)) - } - } - - // Array literal - Token::LBracket => { - self.advance(); - let mut elems = Vec::new(); - while !matches!(self.peek(), Token::RBracket | Token::Eof) { - elems.push(self.parse_expr()?); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RBracket)?; - Ok(Expr::Array(elems)) - } - - // Closure: |params| expr or |params| -> ReturnType { body } - Token::Pipe => { - self.advance(); // consume first `|` - let mut params = Vec::new(); - while !matches!(self.peek(), Token::Pipe | Token::Eof) { - let p_span = self.peek_span(); - let (pname, _) = self.expect_ident()?; - self.expect(&Token::Colon)?; - let type_ann = self.parse_type_expr()?; - params.push(Param { name: pname, type_ann, span: p_span }); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::Pipe)?; // consume closing `|` - // Optional return type annotation - let return_type = if self.eat(&Token::Arrow) { - Some(self.parse_type_expr()?) - } else { - None - }; - // Body is a block `{ stmts }` or a bare expression - let body = if matches!(self.peek(), Token::LBrace) { - self.advance(); - let stmts = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Expr::Block(stmts) - } else { - self.parse_expr()? - }; - Ok(Expr::Closure { params, return_type, body: Box::new(body), span }) - } - - // match expression - Token::Match => { - self.advance(); - let subject = self.parse_expr()?; - self.expect(&Token::LBrace)?; - let arms = self.parse_match_arms()?; - self.expect(&Token::RBrace)?; - Ok(Expr::Match { subject: Box::new(subject), arms }) - } - - // activate Type where "query" - Token::Activate => { - self.advance(); - let (type_name, _) = self.expect_ident()?; - self.expect(&Token::Where)?; - let query = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string literal", &tok, self.peek_span())), - }; - Ok(Expr::Activate { type_name, query }) - } - - // sealed { stmts } - Token::Sealed => { - self.advance(); - self.expect(&Token::LBrace)?; - let stmts = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Ok(Expr::Sealed(stmts)) - } - - // if/else - Token::If => { - self.advance(); - let cond = self.parse_expr()?; - let then = self.parse_primary()?; // expects block - let else_ = if self.eat(&Token::Else) { - Some(Box::new(self.parse_primary()?)) - } else { - None - }; - Ok(Expr::If { cond: Box::new(cond), then: Box::new(then), else_ }) - } - - // Identifier — could be plain name, path (Foo::Bar), or struct literal (Foo { ... }) - Token::Ident(name) => { - self.advance(); - // Check for path: Foo::Bar or Foo::Bar::Baz - if matches!(self.peek(), Token::ColonColon) { - let mut segments = vec![name]; - while self.eat(&Token::ColonColon) { - let (seg, _) = self.expect_ident()?; - segments.push(seg); - } - Ok(Expr::Path { segments }) - } else if matches!(self.peek(), Token::LBrace) - && name.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) - && self.is_struct_literal_start() - { - // Struct literal: TypeName { field: expr, ...spread, field: expr, ... } - self.advance(); // consume `{` - let mut fields = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - // Skip spread syntax: `...expr` (e.g. `...base`) - // Three dots followed by an expression — skip the spread for now (use base fields) - if matches!(self.peek(), Token::Dot) { - // Try to consume `...expr` — eat up to 3 dots then an expression - let mut dots = 0; - while matches!(self.peek(), Token::Dot) && dots < 3 { - self.advance(); - dots += 1; - } - // Parse the spread source expression (we discard it for now) - let _ = self.parse_primary()?; - if !self.eat(&Token::Comma) { break; } - continue; - } - let (field_name, _) = self.expect_ident()?; - self.expect(&Token::Colon)?; - let field_expr = self.parse_expr()?; - fields.push((field_name, field_expr)); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RBrace)?; - Ok(Expr::StructLit { type_name: name, fields, span }) - } else { - Ok(Expr::Ident(name)) - } - } - - // reason "query" - Token::Reason => { - self.advance(); - let query = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string literal", &tok, self.peek_span())), - }; - Ok(Expr::Reason { query }) - } - - // parallel { name: expr, ... } - Token::Parallel => { - self.advance(); - self.expect(&Token::LBrace)?; - let mut entries = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let (entry_name, _) = self.expect_ident()?; - self.expect(&Token::Colon)?; - let entry_expr = self.parse_expr()?; - entries.push((entry_name, entry_expr)); - if !self.eat(&Token::Comma) { - self.eat(&Token::Semicolon); - } - if matches!(self.peek(), Token::RBrace) { break; } - } - self.expect(&Token::RBrace)?; - Ok(Expr::Parallel { entries }) - } - - // trace "label" { stmts } - Token::Trace => { - self.advance(); - let label = match self.peek().clone() { - Token::StringLiteral(s) => { self.advance(); s } - tok => return Err(ParseError::expected("string literal (trace label)", &tok, self.peek_span())), - }; - self.expect(&Token::LBrace)?; - let body = self.parse_block_body()?; - self.expect(&Token::RBrace)?; - Ok(Expr::Trace { label, body }) - } - - // JSX element: `` — heuristic: `<` followed by ident - Token::Lt => { - let next = self.tokens.get(self.pos + 1).map(|s| &s.node); - if matches!(next, Some(Token::Ident(_))) { - self.parse_jsx_element() - } else { - // It's a comparison operator — shouldn't be in primary, error - Err(ParseError::new( - ParseErrorKind::InvalidExprStart(Token::Lt.to_string()), - span, - )) - } - } - - // `@builtin_name(args)` — runtime/syscall expression. - // Compiled as Call { name: "__builtin_name__", arity }. - // Also handles `@call(...)`, `@emit(...)`, `@on(...)`, `@uuid()` etc. - Token::At => { - self.advance(); // consume `@` - let (name, _) = self.expect_ident()?; - let args = if self.eat(&Token::LParen) { - let mut a = Vec::new(); - while !matches!(self.peek(), Token::RParen | Token::Eof) { - a.push(self.parse_expr()?); - if !self.eat(&Token::Comma) { break; } - } - self.expect(&Token::RParen)?; - a - } else { - vec![] - }; - // Emit as Call { func: Ident("@name"), args } - Ok(Expr::Call { - func: Box::new(Expr::Ident(format!("@{name}"))), - args, - }) - } - - tok => Err(ParseError::new( - ParseErrorKind::InvalidExprStart(tok.to_string()), - span, - )), - } - } - - /// Heuristic: are we at the start of a map literal? - /// We peek ahead for `string/ident :` pattern. - /// Check if the current `{` (already consumed by outer code for TypeName {) - /// looks like a struct literal body rather than a block body. - /// Heuristic: if token at pos+1 (first inside `{`) is: - /// - `Ident Colon` → struct field → yes - /// - `Dot Dot Dot` → spread → yes - /// - `RBrace` (empty) → yes (empty struct) - /// - anything else → no (treat as block / enum variant) - /// Called when current token is `{` (LBrace) and we're checking if TypeName { } is a struct. - fn is_struct_literal_start(&self) -> bool { - // self.pos points to `{` (the LBrace we haven't consumed yet) - // Look at pos+1: first token inside `{` - let first = self.tokens.get(self.pos + 1).map(|t| &t.node); - match first { - Some(Token::RBrace) => true, // empty struct literal `TypeName {}` - Some(Token::Dot) => true, // spread `...expr` - Some(Token::Ident(_)) => { - // Check if the token AFTER the ident is `:` - let after_ident = self.tokens.get(self.pos + 2).map(|t| &t.node); - matches!(after_ident, Some(Token::Colon)) - } - _ => false, // Not a struct literal — treat as a block/expression context - } - } - - fn is_map_literal(&self) -> bool { - // Look at current token (first inside `{`) - match self.peek() { - Token::StringLiteral(_) => { - // Check if next is Colon - self.tokens.get(self.pos + 1).is_some_and(|t| matches!(t.node, Token::Colon)) - } - Token::RBrace => false, // empty block `{}` - _ => false, - } - } - - // ── Match arms ──────────────────────────────────────────────────────────── - - fn parse_match_arms(&mut self) -> Result, ParseError> { - let mut arms = Vec::new(); - while !matches!(self.peek(), Token::RBrace | Token::Eof) { - let span = self.peek_span(); - let pattern = self.parse_pattern()?; - self.expect(&Token::FatArrow)?; - let body = self.parse_expr()?; - arms.push(MatchArm { pattern, body, span }); - self.eat(&Token::Comma); - self.eat(&Token::Semicolon); - } - Ok(arms) - } - - fn parse_pattern(&mut self) -> Result { - let span = self.peek_span(); - match self.peek().clone() { - // Wildcard - Token::Ident(ref s) if s == "_" => { - self.advance(); - Ok(Pattern::Wildcard) - } - // Could be: binding, enum variant, or path - Token::Ident(name) => { - self.advance(); - if self.eat(&Token::ColonColon) { - // EnumVariant pattern: Status::Active or Status::Pending(reason) - let (variant, _) = self.expect_ident()?; - let payload = if self.eat(&Token::LParen) { - let (bind, _) = self.expect_ident()?; - self.expect(&Token::RParen)?; - Some(bind) - } else { - None - }; - Ok(Pattern::EnumVariant { enum_name: name, variant, payload }) - } else { - // Simple binding - Ok(Pattern::Binding(name)) - } - } - Token::IntLiteral(n) => { self.advance(); Ok(Pattern::Literal(Literal::Int(n))) } - Token::StringLiteral(s) => { self.advance(); Ok(Pattern::Literal(Literal::Str(s))) } - Token::BoolLiteral(b) => { self.advance(); Ok(Pattern::Literal(Literal::Bool(b))) } - tok => Err(ParseError::new( - ParseErrorKind::InvalidPattern(tok.to_string()), - span, - )), - } - } -} - -// ── Helpers ─────────────────────────────────────────────────────────────────── - -fn expr_span_end(_expr: &Expr, fallback: Span) -> usize { - fallback.end -} - -/// Returns true if `tok` can start an expression. -/// Used to disambiguate `?` as Try vs ternary. -fn is_expr_start(tok: &Token) -> bool { - matches!( - tok, - Token::Ident(_) - | Token::IntLiteral(_) - | Token::FloatLiteral(_) - | Token::StringLiteral(_) - | Token::BoolLiteral(_) - | Token::LParen - | Token::LBracket - | Token::LBrace - | Token::Minus - | Token::Not - | Token::Tilde - | Token::Pipe // closure - | Token::Match - | Token::If - | Token::Activate - | Token::Sealed - | Token::Reason - | Token::Parallel - | Token::Trace - | Token::Lt // JSX element - | Token::At // @builtin call - ) -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use el_lexer::tokenize; - use super::*; - - fn parse_src(src: &str) -> Program { - let tokens = tokenize(src).expect("lex failed"); - parse(tokens, src.to_string()).expect("parse failed") - } - - #[test] - fn test_parse_let() { - let p = parse_src("let x: Int = 42"); - assert!(matches!(p.stmts[0], Stmt::Let { ref name, .. } if name == "x")); - } - - #[test] - fn test_parse_fn_def() { - let src = r#"fn greet(name: String) -> String { return "Hello" }"#; - let p = parse_src(src); - assert!(matches!(&p.stmts[0], Stmt::FnDef { name, .. } if name == "greet")); - } - - #[test] - fn test_parse_type_def() { - let src = "type User { id: Uuid name: String email: String }"; - let p = parse_src(src); - assert!(matches!(&p.stmts[0], Stmt::TypeDef { name, fields, .. } if name == "User" && fields.len() == 3)); - } - - #[test] - fn test_parse_enum_def() { - let src = "enum Status { Active Inactive Pending(String) }"; - let p = parse_src(src); - match &p.stmts[0] { - Stmt::EnumDef { name, variants, .. } => { - assert_eq!(name, "Status"); - assert_eq!(variants.len(), 3); - assert_eq!(variants[2].name, "Pending"); - assert!(variants[2].payload.is_some()); - } - _ => panic!("expected EnumDef"), - } - } - - #[test] - fn test_parse_match() { - let src = r#" -match status { - Status::Active => "active" - Status::Inactive => "inactive" -} -"#; - let p = parse_src(src); - assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Match { arms, .. }, _) if arms.len() == 2)); - } - - #[test] - fn test_parse_activate() { - let src = r#"activate User where "customer who purchased recently""#; - let p = parse_src(src); - assert!(matches!( - &p.stmts[0], - Stmt::Expr(Expr::Activate { type_name, query }, _) - if type_name == "User" && query.contains("customer") - )); - } - - #[test] - fn test_parse_sealed_block() { - let src = r#"sealed { let key: String = "secret" }"#; - let p = parse_src(src); - assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Sealed(_), _))); - } - - #[test] - fn test_parse_binary_ops() { - let p = parse_src("let result = 1 + 2 * 3"); - match &p.stmts[0] { - Stmt::Let { value: Expr::BinOp { op: BinOp::Add, right, .. }, .. } => { - // Right side should be 2*3 - assert!(matches!(**right, Expr::BinOp { op: BinOp::Mul, .. })); - } - _ => panic!("unexpected AST"), - } - } - - #[test] - fn test_parse_fn_call() { - let p = parse_src(r#"greet("Will")"#); - assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Call { .. }, _))); - } - - #[test] - fn test_parse_field_access() { - let p = parse_src("user.name"); - assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Field { field, .. }, _) if field == "name")); - } - - #[test] - fn test_parse_if_else() { - let src = r#"if x == 1 { return "yes" } else { return "no" }"#; - let p = parse_src(src); - assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::If { else_: Some(_), .. }, _))); - } - - #[test] - fn test_parse_array_literal() { - let p = parse_src("[1, 2, 3]"); - assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Array(elems), _) if elems.len() == 3)); - } - - #[test] - fn test_parse_path_expr() { - let p = parse_src("Status::Active"); - assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Path { segments }, _) if segments.len() == 2)); - } - - #[test] - fn test_parse_decorator() { - let src = r#"@public fn hello() -> String { return "hi" }"#; - let p = parse_src(src); - match &p.stmts[0] { - Stmt::FnDef { name, decorators, .. } => { - assert_eq!(name, "hello"); - assert_eq!(decorators.len(), 1); - assert_eq!(decorators[0].name, "public"); - } - _ => panic!("expected FnDef"), - } - } - - #[test] - fn test_parse_decorator_with_args() { - let src = r#"@cache(300) fn fetch() -> String { return "data" }"#; - let p = parse_src(src); - match &p.stmts[0] { - Stmt::FnDef { decorators, .. } => { - assert_eq!(decorators.len(), 1); - assert_eq!(decorators[0].name, "cache"); - assert_eq!(decorators[0].args.len(), 1); - } - _ => panic!("expected FnDef"), - } - } - - #[test] - fn test_parse_multiple_decorators() { - let src = r#"@authenticate @trace fn secure() -> Void { }"#; - let p = parse_src(src); - match &p.stmts[0] { - Stmt::FnDef { decorators, .. } => { - assert_eq!(decorators.len(), 2); - assert_eq!(decorators[0].name, "authenticate"); - assert_eq!(decorators[1].name, "trace"); - } - _ => panic!("expected FnDef"), - } - } - - #[test] - fn test_parse_import() { - let p = parse_src("import std::collections::Map"); - match &p.stmts[0] { - Stmt::Import { path, names, alias, .. } => { - assert_eq!(path, &["std", "collections", "Map"]); - assert!(names.is_empty()); - assert!(alias.is_none()); - } - _ => panic!("expected Import"), - } - } - - #[test] - fn test_parse_import_multi() { - let p = parse_src("import std::array::{map, filter}"); - match &p.stmts[0] { - Stmt::Import { path, names, .. } => { - assert_eq!(path, &["std", "array"]); - assert_eq!(names, &["map", "filter"]); - } - _ => panic!("expected Import"), - } - } - - #[test] - fn test_parse_from_import() { - let p = parse_src("from mypackage import { Thing, OtherThing }"); - match &p.stmts[0] { - Stmt::Import { path, names, .. } => { - assert_eq!(path, &["mypackage"]); - assert_eq!(names, &["Thing", "OtherThing"]); - } - _ => panic!("expected Import"), - } - } - - #[test] - fn test_parse_protocol_def() { - let src = r#"protocol Printable { fn print(self: String) -> Void }"#; - let p = parse_src(src); - match &p.stmts[0] { - Stmt::ProtocolDef { name, methods, .. } => { - assert_eq!(name, "Printable"); - assert_eq!(methods.len(), 1); - assert_eq!(methods[0].name, "print"); - } - _ => panic!("expected ProtocolDef"), - } - } - - #[test] - fn test_parse_impl_def() { - let src = r#"impl Printable for User { fn print(self: String) -> Void { } }"#; - let p = parse_src(src); - match &p.stmts[0] { - Stmt::ImplDef { protocol_name, type_name, methods, .. } => { - assert_eq!(protocol_name, "Printable"); - assert_eq!(type_name, "User"); - assert_eq!(methods.len(), 1); - } - _ => panic!("expected ImplDef"), - } - } - - #[test] - fn test_parse_closure() { - let p = parse_src("let double = |x: Int| x"); - match &p.stmts[0] { - Stmt::Let { value: Expr::Closure { params, .. }, .. } => { - assert_eq!(params.len(), 1); - assert_eq!(params[0].name, "x"); - } - _ => panic!("expected Let with Closure"), - } - } - - #[test] - fn test_parse_closure_with_block() { - let p = parse_src("let add = |x: Int, y: Int| -> Int { x }"); - match &p.stmts[0] { - Stmt::Let { value: Expr::Closure { params, return_type, .. }, .. } => { - assert_eq!(params.len(), 2); - assert!(return_type.is_some()); - } - _ => panic!("expected Let with Closure"), - } - } - - #[test] - fn test_parse_result_type() { - let p = parse_src("fn fetch() -> Result { return fetch() }"); - match &p.stmts[0] { - Stmt::FnDef { return_type, .. } => { - assert!(matches!(return_type, TypeExpr::Result { .. })); - } - _ => panic!("expected FnDef"), - } - } - - #[test] - fn test_parse_map_type() { - let p = parse_src("let m: Map = m"); - match &p.stmts[0] { - Stmt::Let { type_ann: Some(TypeExpr::Map { .. }), .. } => {} - _ => panic!("expected Let with Map type"), - } - } - - #[test] - fn test_parse_optional_type_questionmark() { - let p = parse_src("let x: Int? = x"); - match &p.stmts[0] { - Stmt::Let { type_ann: Some(TypeExpr::Optional(inner)), .. } => { - assert!(matches!(inner.as_ref(), TypeExpr::Named(n) if n == "Int")); - } - _ => panic!("expected Let with Optional type"), - } - } - - #[test] - fn test_parse_try_operator() { - let p = parse_src("let x = fetch()"); - // Just verify it parses OK for now; try is tested via `fetch()?` - assert!(p.stmts.len() == 1); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-registry/Cargo.toml b/_archive/rust-bootstrap/engrams/el-registry/Cargo.toml deleted file mode 100644 index 1669bb8..0000000 --- a/_archive/rust-bootstrap/engrams/el-registry/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "el-registry" -description = "Package registry client for the Engram language toolchain" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-manifest = { path = "../el-manifest" } -serde = { workspace = true } -serde_json = { workspace = true } -thiserror = { workspace = true } -blake3 = { workspace = true } -semver = { version = "1", features = ["serde"] } -reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } -tokio = { version = "1", features = ["fs", "io-util"] } diff --git a/_archive/rust-bootstrap/engrams/el-registry/src/client.rs b/_archive/rust-bootstrap/engrams/el-registry/src/client.rs deleted file mode 100644 index 8be2aeb..0000000 --- a/_archive/rust-bootstrap/engrams/el-registry/src/client.rs +++ /dev/null @@ -1,316 +0,0 @@ -//! HTTP client for the Engram package registry. - -use std::collections::HashMap; -use std::path::{Path, PathBuf}; - -use serde::{Deserialize, Serialize}; -use semver::{Version, VersionReq}; - -use el_manifest::{Dependency, Manifest}; - -use crate::error::{RegistryError, RegistryResult}; - -/// The default registry URL. -pub const DEFAULT_REGISTRY_URL: &str = "https://packages.neurontechnologies.ai"; - -/// The local cache directory for downloaded packages. -pub fn cache_dir() -> PathBuf { - let home = std::env::var("HOME") - .or_else(|_| std::env::var("USERPROFILE")) - .unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home).join(".engram").join("packages") -} - -// ── Package metadata ────────────────────────────────────────────────────────── - -/// Metadata for a single package version, as returned by the registry API. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PackageMetadata { - pub name: String, - pub version: Version, - pub description: String, - pub authors: Vec, - /// SHA-256 hex digest of the package tarball. - pub checksum: String, - /// URL to download the package tarball. - pub download_url: String, - /// Direct dependencies of this package. - #[serde(default)] - pub dependencies: HashMap, -} - -impl PackageMetadata { - /// Compute the local cache path for this package. - pub fn cache_path(&self) -> PathBuf { - cache_dir() - .join(&self.name) - .join(self.version.to_string()) - } - - /// Check whether this package is already cached locally. - pub fn is_cached(&self) -> bool { - self.cache_path().exists() - } -} - -// ── Registry API response shapes ────────────────────────────────────────────── - -/// Response from `GET /api/v1/packages/{name}` — all versions. -#[derive(Debug, Deserialize)] -struct VersionListResponse { - versions: Vec, -} - -/// Response from `GET /api/v1/search?q=...`. -#[derive(Debug, Deserialize)] -struct SearchResponse { - results: Vec, -} - -/// Body sent to `POST /api/v1/publish`. -#[derive(Debug, Serialize)] -struct PublishRequest { - name: String, - version: String, - description: Option, - authors: Vec, - checksum: String, -} - -// ── Client ──────────────────────────────────────────────────────────────────── - -/// An HTTP client for the Engram package registry. -/// -/// The registry server is at `https://packages.neurontechnologies.ai` (not yet -/// deployed). This client is built to the planned API contract. -pub struct RegistryClient { - pub registry_url: String, - http: reqwest::Client, -} - -impl RegistryClient { - /// Create a new client pointing at the default registry. - pub fn new() -> Self { - Self::with_url(DEFAULT_REGISTRY_URL) - } - - /// Create a client with a custom registry URL (for testing / private registries). - pub fn with_url(url: impl Into) -> Self { - let http = reqwest::Client::builder() - .user_agent(concat!("el-registry/", env!("CARGO_PKG_VERSION"))) - .build() - .expect("failed to build HTTP client"); - Self { - registry_url: url.into(), - http, - } - } - - /// Fetch the metadata for the best matching version of a package. - pub async fn fetch_metadata( - &self, - name: &str, - version_req: &VersionReq, - ) -> RegistryResult { - let url = format!("{}/api/v1/packages/{name}", self.registry_url); - let resp = self.http.get(&url).send().await?; - - if resp.status() == reqwest::StatusCode::NOT_FOUND { - return Err(RegistryError::NotFound(name.to_string())); - } - if !resp.status().is_success() { - let status = resp.status().as_u16(); - let message = resp.text().await.unwrap_or_default(); - return Err(RegistryError::RegistryError { status, message }); - } - - let list: VersionListResponse = resp.json().await?; - - // Pick the highest version that satisfies the requirement. - let mut candidates: Vec = list - .versions - .into_iter() - .filter(|m| version_req.matches(&m.version)) - .collect(); - candidates.sort_by(|a, b| b.version.cmp(&a.version)); - - candidates.into_iter().next().ok_or_else(|| { - RegistryError::NoMatchingVersion { - name: name.to_string(), - req: version_req.to_string(), - } - }) - } - - /// Download a package tarball to a local directory. - /// - /// Verifies the SHA-256 checksum before accepting the download. - /// The package is extracted into `~/.engram/packages/{name}/{version}/`. - pub async fn download(&self, metadata: &PackageMetadata, dest: &Path) -> RegistryResult<()> { - // Download the tarball. - let resp = self - .http - .get(&metadata.download_url) - .send() - .await?; - - if !resp.status().is_success() { - let status = resp.status().as_u16(); - let message = resp.text().await.unwrap_or_default(); - return Err(RegistryError::RegistryError { status, message }); - } - - let bytes = resp.bytes().await?; - - // Verify checksum. - let actual_checksum = hex::encode(blake3::hash(&bytes).as_bytes()); - // Note: the registry uses SHA-256 in the metadata description, but we - // use BLAKE3 here for consistency with the rest of the toolchain. - // When the server is deployed this will be reconciled. - if !actual_checksum.starts_with(&metadata.checksum[..8]) && !metadata.checksum.is_empty() { - // Relaxed check: only error on definitive mismatch (non-empty expected checksum - // that doesn't share the same prefix). In production the server will send - // a full BLAKE3 hex and we do a full equality check. - } - - // Write to destination. - tokio::fs::create_dir_all(dest).await?; - let tarball_path = dest.join(format!("{}-{}.tar.gz", metadata.name, metadata.version)); - tokio::fs::write(&tarball_path, &bytes).await?; - - Ok(()) - } - - /// Publish a package to the registry. - pub async fn publish( - &self, - manifest: &Manifest, - artifact: &Path, - api_key: &str, - ) -> RegistryResult<()> { - let artifact_bytes = tokio::fs::read(artifact).await?; - let checksum = hex::encode(blake3::hash(&artifact_bytes).as_bytes()); - - let body = PublishRequest { - name: manifest.package.name.clone(), - version: manifest.package.version.to_string(), - description: manifest.package.description.clone(), - authors: manifest.package.authors.clone(), - checksum, - }; - - let url = format!("{}/api/v1/publish", self.registry_url); - let resp = self - .http - .post(&url) - .bearer_auth(api_key) - .json(&body) - .send() - .await?; - - if !resp.status().is_success() { - let status = resp.status().as_u16(); - let message = resp.text().await.unwrap_or_default(); - return Err(RegistryError::RegistryError { status, message }); - } - - Ok(()) - } - - /// Search the registry for packages matching `query`. - pub async fn search(&self, query: &str) -> RegistryResult> { - let url = format!("{}/api/v1/search", self.registry_url); - let resp = self - .http - .get(&url) - .query(&[("q", query)]) - .send() - .await?; - - if !resp.status().is_success() { - let status = resp.status().as_u16(); - let message = resp.text().await.unwrap_or_default(); - return Err(RegistryError::RegistryError { status, message }); - } - - let result: SearchResponse = resp.json().await?; - Ok(result.results) - } - - /// Resolve a set of dependency specs to concrete package versions. - /// - /// For path dependencies this is a no-op (they resolve locally). - /// For version/registry dependencies, this calls the registry. - pub async fn resolve( - &self, - deps: &HashMap, - ) -> RegistryResult> { - crate::resolve::resolve_deps(self, deps).await - } -} - -impl Default for RegistryClient { - fn default() -> Self { - Self::new() - } -} - -// ── hex helper (avoid pulling in the hex crate) ─────────────────────────────── - -mod hex { - pub fn encode(bytes: &[u8]) -> String { - bytes.iter().map(|b| format!("{b:02x}")).collect() - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_cache_path_format() { - let meta = PackageMetadata { - name: "engram-http".to_string(), - version: Version::new(1, 2, 3), - description: "HTTP library".to_string(), - authors: vec![], - checksum: "abc123".to_string(), - download_url: "https://example.com/pkg.tar.gz".to_string(), - dependencies: HashMap::new(), - }; - let path = meta.cache_path(); - assert!(path.to_str().unwrap().contains("engram-http")); - assert!(path.to_str().unwrap().contains("1.2.3")); - } - - #[test] - fn test_registry_client_default_url() { - let client = RegistryClient::new(); - assert_eq!(client.registry_url, DEFAULT_REGISTRY_URL); - } - - #[test] - fn test_registry_client_custom_url() { - let client = RegistryClient::with_url("https://my.registry.io"); - assert_eq!(client.registry_url, "https://my.registry.io"); - } - - #[test] - fn test_package_metadata_serialize_roundtrip() { - let meta = PackageMetadata { - name: "el-core".to_string(), - version: Version::new(0, 3, 0), - description: "Core library".to_string(), - authors: vec!["Will ".to_string()], - checksum: "deadbeef".to_string(), - download_url: "https://packages.neurontechnologies.ai/el-core-0.3.0.tar.gz".to_string(), - dependencies: HashMap::new(), - }; - let json = serde_json::to_string(&meta).unwrap(); - let de: PackageMetadata = serde_json::from_str(&json).unwrap(); - assert_eq!(de.name, meta.name); - assert_eq!(de.version, meta.version); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-registry/src/error.rs b/_archive/rust-bootstrap/engrams/el-registry/src/error.rs deleted file mode 100644 index adbca21..0000000 --- a/_archive/rust-bootstrap/engrams/el-registry/src/error.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Registry error types. - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum RegistryError { - #[error("http error: {0}")] - Http(#[from] reqwest::Error), - - #[error("io error: {0}")] - Io(#[from] std::io::Error), - - #[error("json error: {0}")] - Json(#[from] serde_json::Error), - - #[error("no version of '{name}' satisfies '{req}'")] - NoMatchingVersion { name: String, req: String }, - - #[error("package '{0}' not found in registry")] - NotFound(String), - - #[error("checksum mismatch for '{name}': expected {expected}, got {actual}")] - ChecksumMismatch { - name: String, - expected: String, - actual: String, - }, - - #[error("authentication required: provide an API key")] - AuthRequired, - - #[error("registry returned error {status}: {message}")] - RegistryError { status: u16, message: String }, - - #[error("manifest error: {0}")] - Manifest(#[from] el_manifest::ManifestError), -} - -pub type RegistryResult = Result; diff --git a/_archive/rust-bootstrap/engrams/el-registry/src/lib.rs b/_archive/rust-bootstrap/engrams/el-registry/src/lib.rs deleted file mode 100644 index 45a431f..0000000 --- a/_archive/rust-bootstrap/engrams/el-registry/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! el-registry — Package registry client for the Engram language toolchain. -//! -//! The registry is at `https://packages.neurontechnologies.ai`. This crate -//! provides a client that can fetch package metadata, download tarballs, and -//! publish packages. -//! -//! Local package cache: `~/.engram/packages/{name}/{version}/` -//! -//! # Note -//! The registry server does not yet exist. The client is implemented to the -//! planned API contract and will work once the server is deployed. - -pub mod client; -mod error; -mod resolve; - -pub use client::{cache_dir, PackageMetadata, RegistryClient, DEFAULT_REGISTRY_URL}; -pub use error::{RegistryError, RegistryResult}; -pub use resolve::resolve_deps; diff --git a/_archive/rust-bootstrap/engrams/el-registry/src/resolve.rs b/_archive/rust-bootstrap/engrams/el-registry/src/resolve.rs deleted file mode 100644 index dc7a382..0000000 --- a/_archive/rust-bootstrap/engrams/el-registry/src/resolve.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Dependency resolution — convert a manifest's dependency map into a flat -//! ordered list of resolved packages. - -use std::collections::HashMap; - -use el_manifest::Dependency; - -use crate::client::{PackageMetadata, RegistryClient}; -use crate::error::RegistryResult; - -/// Resolve all registry dependencies in `deps` to concrete versions. -/// -/// Path dependencies are skipped (they are local and do not need network -/// resolution). -pub async fn resolve_deps( - client: &RegistryClient, - deps: &HashMap, -) -> RegistryResult> { - let mut resolved = Vec::new(); - - for (name, dep) in deps { - match dep { - Dependency::VersionReq(req) => { - let meta = client.fetch_metadata(name, req).await?; - resolved.push(meta); - } - Dependency::Registry { version, .. } => { - let meta = client.fetch_metadata(name, version).await?; - resolved.push(meta); - } - Dependency::Path(_) => { - // Path deps resolve to the local directory — nothing to fetch. - } - } - } - - // Sort by name for deterministic ordering. - resolved.sort_by(|a, b| a.name.cmp(&b.name)); - Ok(resolved) -} diff --git a/_archive/rust-bootstrap/engrams/el-seal/Cargo.toml b/_archive/rust-bootstrap/engrams/el-seal/Cargo.toml deleted file mode 100644 index b73fc60..0000000 --- a/_archive/rust-bootstrap/engrams/el-seal/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "el-seal" -description = "Quantum-sealed production compilation target for Engram language" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -engram-crypto = { workspace = true } -thiserror = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -blake3 = { workspace = true } -aes-gcm = { workspace = true } -rand = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-seal/src/artifact.rs b/_archive/rust-bootstrap/engrams/el-seal/src/artifact.rs deleted file mode 100644 index 44c9c63..0000000 --- a/_archive/rust-bootstrap/engrams/el-seal/src/artifact.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! Sealed artifact format definition. -//! -//! Binary layout (big-endian): -//! -//! ```text -//! Offset Size Field -//! ────── ───── ───────────────────────────────────────────────────────── -//! 0 8 magic: b"ENGRAM01" -//! 8 2 version: u16 (currently 1) -//! 10 * JSON-encoded SealedArtifact body (algorithm_id, nonce, …) -//! ``` -//! -//! The body is JSON so the format is self-describing and forward-compatible. -//! Future versions can add new fields without breaking older parsers. - -use serde::{Deserialize, Serialize}; - -/// Magic header bytes — identify an Engram sealed artifact. -pub const MAGIC: [u8; 8] = *b"ENGRAM01"; - -/// Current artifact format version. -pub const FORMAT_VERSION: u16 = 1; - -// ── Configuration ───────────────────────────────────────────────────────────── - -/// Which algorithm was used to seal the artifact. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum SealAlgorithm { - /// AES-256-GCM — current default, quantum-resistant at 256-bit. - Aes256Gcm, - /// CRYSTALS-Kyber 768 — when ml-kem crate stabilizes. - MlKem768, - /// CRYSTALS-Kyber 1024 — when ml-kem crate stabilizes. - MlKem1024, -} - -impl SealAlgorithm { - pub fn id(&self) -> &'static str { - match self { - SealAlgorithm::Aes256Gcm => "aes256gcm-v1", - SealAlgorithm::MlKem768 => "mlkem768-v1", - SealAlgorithm::MlKem1024 => "mlkem1024-v1", - } - } -} - -/// How the deployment key is derived / bound. -#[derive(Debug, Clone)] -pub enum DeploymentBinding { - /// Read the seal key from an environment variable (e.g. `ENGRAM_SEAL_KEY`). - EnvironmentKey(String), - /// Bind to this machine's hostname + OS + CPU model (BLAKE3 hash of all three). - MachineFingerprint, - /// No binding — key is the zero vector. For testing only; offers no security. - None, -} - -/// Configuration for the sealing operation. -#[derive(Debug, Clone)] -pub struct SealConfig { - pub algorithm: SealAlgorithm, - pub deployment_binding: DeploymentBinding, -} - -impl Default for SealConfig { - fn default() -> Self { - Self { - algorithm: SealAlgorithm::Aes256Gcm, - deployment_binding: DeploymentBinding::EnvironmentKey("ENGRAM_SEAL_KEY".into()), - } - } -} - -// ── Artifact ────────────────────────────────────────────────────────────────── - -/// A quantum-sealed bytecode artifact. -/// -/// This is the output of `el seal` / the `prod` compilation target. -/// It is serialized to disk as: `MAGIC || VERSION_u16_be || JSON(body)`. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SealedArtifact { - /// Algorithm identifier — matches [`SealAlgorithm::id()`]. - pub algorithm_id: String, - - /// BLAKE3-keyed MAC over `(algorithm_id || nonce || ciphertext)`. - /// Used to detect tampering before attempting decryption. - /// In the PQ upgrade path, this becomes an ML-DSA signature. - pub signature: Vec, - - /// The binding-protected symmetric key. - /// - /// In the current scheme: `symmetric_key XOR BLAKE3(binding_material)`. - /// Without the deployment key, the binding material cannot be derived, - /// so the symmetric key cannot be recovered. - /// - /// In the ML-KEM upgrade: this becomes the KEM-encapsulated key ciphertext. - pub encapsulated_key: Vec, - - /// 96-bit AES-GCM nonce. - pub nonce: Vec, - - /// Encrypted bytecode (AES-256-GCM ciphertext including the 128-bit auth tag). - pub ciphertext: Vec, - - /// BLAKE3 hash of the binding material. Allows the unsealer to verify - /// it is running in the correct deployment environment before decryption. - /// `None` if [`DeploymentBinding::None`] was used. - pub deployment_fingerprint: Option>, -} - -impl SealedArtifact { - /// Serialize to the on-disk wire format: `MAGIC || version_be16 || JSON`. - pub fn to_bytes(&self) -> Result, serde_json::Error> { - let mut out = Vec::with_capacity(256); - out.extend_from_slice(&MAGIC); - out.extend_from_slice(&FORMAT_VERSION.to_be_bytes()); - let json = serde_json::to_vec(self)?; - out.extend_from_slice(&json); - Ok(out) - } - - /// Deserialize from the on-disk wire format. - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < 10 { - return Err(crate::SealError::Serialization("artifact too short".into())); - } - let magic: [u8; 8] = bytes[..8].try_into().unwrap(); - if magic != MAGIC { - return Err(crate::SealError::InvalidMagic(magic)); - } - // Version check (bytes 8..10) — currently we only support v1 - let version = u16::from_be_bytes([bytes[8], bytes[9]]); - if version != FORMAT_VERSION { - return Err(crate::SealError::UnsupportedAlgorithm(format!("format v{version}"))); - } - let body = &bytes[10..]; - serde_json::from_slice(body).map_err(|e| crate::SealError::Serialization(e.to_string())) - } -} diff --git a/_archive/rust-bootstrap/engrams/el-seal/src/error.rs b/_archive/rust-bootstrap/engrams/el-seal/src/error.rs deleted file mode 100644 index dda419b..0000000 --- a/_archive/rust-bootstrap/engrams/el-seal/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Seal/unseal error types. - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum SealError { - #[error("encryption failed: {0}")] - EncryptionFailed(String), - - #[error("decryption failed: {0}")] - DecryptionFailed(String), - - #[error("signature verification failed — artifact may be tampered")] - SignatureInvalid, - - #[error("invalid magic header: expected ENGRAM01, got {0:?}")] - InvalidMagic([u8; 8]), - - #[error("unsupported algorithm version: {0}")] - UnsupportedAlgorithm(String), - - #[error("deployment binding mismatch — wrong key or wrong machine")] - BindingMismatch, - - #[error("serialization error: {0}")] - Serialization(String), - - #[error("environment variable {0} not set — cannot unseal")] - MissingEnvKey(String), - - #[error("crypto engine error: {0}")] - CryptoEngine(String), -} - -pub type SealResult = Result; diff --git a/_archive/rust-bootstrap/engrams/el-seal/src/lib.rs b/_archive/rust-bootstrap/engrams/el-seal/src/lib.rs deleted file mode 100644 index 5072022..0000000 --- a/_archive/rust-bootstrap/engrams/el-seal/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! el-seal — Quantum-sealed production compilation target. -//! -//! The `prod` compilation target encrypts Engram bytecode into a -//! `SealedArtifact` that cannot be decompiled without the deployment key. -//! -//! # Sealing Process -//! -//! 1. Generate a random 256-bit symmetric key. -//! 2. Encrypt the bytecode with AES-256-GCM (authenticated encryption). -//! 3. Derive the deployment binding from the environment key via BLAKE3. -//! 4. "Encapsulate" the symmetric key: XOR it with the BLAKE3 hash of the -//! binding material, so the symmetric key can only be recovered if you -//! know the deployment secret. -//! 5. Sign `(algorithm_id || nonce || ciphertext)` with a BLAKE3 keyed MAC -//! using the symmetric key as the MAC key. -//! 6. Serialize into a `SealedArtifact` with the magic header `ENGRAM01`. -//! -//! # Why "quantum-sealed"? -//! -//! AES-256-GCM is the current NIST standard for symmetric authenticated -//! encryption. Grover's algorithm reduces the effective key space from 2^256 -//! to 2^128 — still computationally infeasible for any foreseeable quantum -//! computer. The algorithm_id field reserves space for upgrading to ML-KEM -//! (CRYSTALS-Kyber) when those crates stabilize, without changing the -//! artifact format. -//! -//! # Decompilation resistance -//! -//! Without the deployment key, the `ciphertext` field is indistinguishable -//! from random bytes. Every static analysis tool, disassembler, and -//! decompiler sees garbage. The GCM auth tag additionally makes any -//! tampering detectable. - -mod artifact; -mod error; -mod seal; - -pub use artifact::{DeploymentBinding, SealAlgorithm, SealConfig, SealedArtifact}; -pub use error::{SealError, SealResult}; -pub use seal::{seal, unseal, verify}; diff --git a/_archive/rust-bootstrap/engrams/el-seal/src/seal.rs b/_archive/rust-bootstrap/engrams/el-seal/src/seal.rs deleted file mode 100644 index 412f2a5..0000000 --- a/_archive/rust-bootstrap/engrams/el-seal/src/seal.rs +++ /dev/null @@ -1,371 +0,0 @@ -//! Core seal/unseal operations. - -use aes_gcm::{ - aead::{Aead, AeadCore, KeyInit, OsRng}, - Aes256Gcm, Key, Nonce, -}; -use rand::RngCore; - -use crate::artifact::{DeploymentBinding, SealAlgorithm, SealConfig, SealedArtifact}; -use crate::error::{SealError, SealResult}; - -// ── Public API ──────────────────────────────────────────────────────────────── - -/// Seal `bytecode` into a [`SealedArtifact`] using the given [`SealConfig`]. -/// -/// # Sealing steps -/// -/// 1. Resolve the deployment binding material. -/// 2. Generate a random 256-bit symmetric key. -/// 3. Encrypt bytecode with AES-256-GCM. -/// 4. XOR the symmetric key with `BLAKE3(binding_material)` to produce -/// the `encapsulated_key` field. Possession of the binding secret is -/// required to recover the symmetric key. -/// 5. MAC the header + ciphertext with the symmetric key. -/// 6. Serialize into [`SealedArtifact`]. -pub fn seal(bytecode: &[u8], config: &SealConfig) -> SealResult { - match &config.algorithm { - SealAlgorithm::Aes256Gcm => seal_aes256gcm(bytecode, config), - SealAlgorithm::MlKem768 | SealAlgorithm::MlKem1024 => { - Err(SealError::UnsupportedAlgorithm(config.algorithm.id().to_string())) - } - } -} - -/// Unseal a [`SealedArtifact`], recovering the original bytecode. -/// -/// The `binding_key` must match the key that was used during sealing. -/// For [`DeploymentBinding::EnvironmentKey`], this is the raw env var bytes. -/// For [`DeploymentBinding::MachineFingerprint`], this is the fingerprint bytes. -/// For [`DeploymentBinding::None`], pass `&[]`. -pub fn unseal(artifact: &SealedArtifact, binding_key: &[u8]) -> SealResult> { - match artifact.algorithm_id.as_str() { - "aes256gcm-v1" => unseal_aes256gcm(artifact, binding_key), - other => Err(SealError::UnsupportedAlgorithm(other.to_string())), - } -} - -/// Verify the MAC/signature on a [`SealedArtifact`] without decrypting. -/// -/// Returns `true` if the artifact is intact. This only proves the artifact -/// has not been tampered with — it does not prove the deployment key is -/// correct. -/// -/// Note: verification requires the symmetric key, which requires the -/// binding material. For a lightweight integrity check, use the GCM auth -/// tag (which is verified implicitly by [`unseal`]). -pub fn verify(artifact: &SealedArtifact) -> SealResult { - // Without the binding key we can't recover the symmetric key to verify - // the MAC. What we *can* do is check structural integrity: - // - Magic and version are checked in from_bytes(). - // - Nonce must be 12 bytes (AES-GCM). - // - Ciphertext must be non-empty. - let structural_ok = artifact.nonce.len() == 12 - && !artifact.ciphertext.is_empty() - && !artifact.encapsulated_key.is_empty() - && !artifact.signature.is_empty(); - Ok(structural_ok) -} - -// ── AES-256-GCM sealing ─────────────────────────────────────────────────────── - -fn seal_aes256gcm(bytecode: &[u8], config: &SealConfig) -> SealResult { - let algorithm_id = SealAlgorithm::Aes256Gcm.id().to_string(); - - // 1. Resolve the binding material - let (binding_material, fingerprint) = resolve_binding(&config.deployment_binding)?; - - // 2. Generate a random 256-bit symmetric key - let mut sym_key = [0u8; 32]; - OsRng.fill_bytes(&mut sym_key); - - // 3. Encrypt bytecode - let aes_key = Key::::from_slice(&sym_key); - let cipher = Aes256Gcm::new(aes_key); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - - let ciphertext = cipher - .encrypt(&nonce, bytecode) - .map_err(|e| SealError::EncryptionFailed(e.to_string()))?; - - // 4. Encapsulate the symmetric key: XOR with BLAKE3(binding_material) - let binding_hash = blake3_32(&binding_material); - let encapsulated_key: Vec = sym_key.iter().zip(binding_hash.iter()).map(|(a, b)| a ^ b).collect(); - - // 5. MAC: BLAKE3 keyed over (algorithm_id || nonce || ciphertext) - let signature = compute_mac(&sym_key, &algorithm_id, nonce.as_slice(), &ciphertext); - - Ok(SealedArtifact { - algorithm_id, - signature, - encapsulated_key, - nonce: nonce.to_vec(), - ciphertext, - deployment_fingerprint: fingerprint, - }) -} - -fn unseal_aes256gcm(artifact: &SealedArtifact, binding_key: &[u8]) -> SealResult> { - // 1. Derive binding hash from the provided key. - // If binding_key is empty, use the zero vector (matches DeploymentBinding::None). - let effective_key = if binding_key.is_empty() { - vec![0u8; 32] - } else { - binding_key.to_vec() - }; - let binding_hash = blake3_32(&effective_key); - - // 1b. If a fingerprint was embedded, verify the binding key matches - if let Some(ref fp) = artifact.deployment_fingerprint { - let expected_fp = blake3_hash(&effective_key); - if expected_fp.as_slice() != fp.as_slice() { - return Err(SealError::BindingMismatch); - } - } - - // 2. Recover the symmetric key: XOR encapsulated_key with binding_hash - if artifact.encapsulated_key.len() != 32 { - return Err(SealError::DecryptionFailed("encapsulated key wrong length".into())); - } - let sym_key: Vec = artifact.encapsulated_key.iter().zip(binding_hash.iter()).map(|(a, b)| a ^ b).collect(); - let sym_key_arr: [u8; 32] = sym_key.try_into().unwrap(); - - // 3. Verify MAC before decrypting - let expected_mac = compute_mac(&sym_key_arr, &artifact.algorithm_id, &artifact.nonce, &artifact.ciphertext); - if expected_mac != artifact.signature { - return Err(SealError::SignatureInvalid); - } - - // 4. Decrypt - if artifact.nonce.len() != 12 { - return Err(SealError::DecryptionFailed("invalid nonce length".into())); - } - let nonce = Nonce::from_slice(&artifact.nonce); - let aes_key = Key::::from_slice(&sym_key_arr); - let cipher = Aes256Gcm::new(aes_key); - let plaintext = cipher - .decrypt(nonce, artifact.ciphertext.as_slice()) - .map_err(|e| SealError::DecryptionFailed(e.to_string()))?; - - Ok(plaintext) -} - -// ── Binding resolution ──────────────────────────────────────────────────────── - -/// Resolve a deployment binding to raw bytes and an optional fingerprint. -/// -/// Returns `(binding_material, deployment_fingerprint)`. -/// The fingerprint is stored in the artifact; the binding material is never stored. -fn resolve_binding(binding: &DeploymentBinding) -> SealResult<(Vec, Option>)> { - match binding { - DeploymentBinding::EnvironmentKey(var_name) => { - let val = std::env::var(var_name) - .map_err(|_| SealError::MissingEnvKey(var_name.clone()))?; - let material = val.into_bytes(); - let fingerprint = blake3_hash(&material); - Ok((material, Some(fingerprint))) - } - DeploymentBinding::MachineFingerprint => { - // Derive from hostname + OS - let hostname = get_hostname(); - let os = std::env::consts::OS; - let arch = std::env::consts::ARCH; - let raw = format!("{hostname}::{os}::{arch}"); - let material = raw.into_bytes(); - let fingerprint = blake3_hash(&material); - Ok((material, Some(fingerprint))) - } - DeploymentBinding::None => { - // Zero vector — trivially recoverable, testing only - Ok((vec![0u8; 32], None)) - } - } -} - -fn get_hostname() -> String { - std::env::var("HOSTNAME") - .or_else(|_| std::env::var("COMPUTERNAME")) - .unwrap_or_else(|_| "unknown-host".into()) -} - -// ── Crypto helpers ──────────────────────────────────────────────────────────── - -fn blake3_32(data: &[u8]) -> [u8; 32] { - *blake3::hash(data).as_bytes() -} - -fn blake3_hash(data: &[u8]) -> Vec { - blake3::hash(data).as_bytes().to_vec() -} - -fn compute_mac(key: &[u8; 32], algorithm_id: &str, nonce: &[u8], ciphertext: &[u8]) -> Vec { - let mut hasher = blake3::Hasher::new_keyed(key); - hasher.update(algorithm_id.as_bytes()); - hasher.update(nonce); - hasher.update(ciphertext); - hasher.finalize().as_bytes().to_vec() -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use crate::artifact::{DeploymentBinding, SealAlgorithm, SealConfig}; - - fn no_binding_config() -> SealConfig { - SealConfig { - algorithm: SealAlgorithm::Aes256Gcm, - deployment_binding: DeploymentBinding::None, - } - } - - fn env_binding_config(var: &str) -> SealConfig { - SealConfig { - algorithm: SealAlgorithm::Aes256Gcm, - deployment_binding: DeploymentBinding::EnvironmentKey(var.to_string()), - } - } - - #[test] - fn test_seal_unseal_roundtrip_no_binding() { - let bytecode = b"PUSH 42\nCALL print\nRETURN"; - let config = no_binding_config(); - let artifact = seal(bytecode, &config).unwrap(); - let recovered = unseal(&artifact, &[]).unwrap(); - assert_eq!(recovered, bytecode); - } - - #[test] - fn test_seal_unseal_roundtrip_env_key() { - std::env::set_var("_EL_TEST_SEAL_KEY", "super-secret-deployment-key"); - let bytecode = b"sealed bytecode payload"; - let config = env_binding_config("_EL_TEST_SEAL_KEY"); - let artifact = seal(bytecode, &config).unwrap(); - let recovered = unseal(&artifact, b"super-secret-deployment-key").unwrap(); - assert_eq!(recovered, bytecode); - std::env::remove_var("_EL_TEST_SEAL_KEY"); - } - - #[test] - fn test_wrong_binding_key_rejected() { - let bytecode = b"secret bytecode"; - let config = no_binding_config(); - let artifact = seal(bytecode, &config).unwrap(); - // Use wrong key — MAC should fail - let mut bad_artifact = artifact.clone(); - bad_artifact.encapsulated_key = vec![0xAA; 32]; // wrong key - let result = unseal(&bad_artifact, &[]); - assert!(result.is_err()); - } - - #[test] - fn test_tampered_ciphertext_rejected() { - let bytecode = b"important bytecode"; - let config = no_binding_config(); - let mut artifact = seal(bytecode, &config).unwrap(); - // Flip a byte in the ciphertext - if let Some(b) = artifact.ciphertext.first_mut() { - *b ^= 0xFF; - } - let result = unseal(&artifact, &[]); - assert!(result.is_err()); - } - - #[test] - fn test_tampered_mac_rejected() { - let bytecode = b"important bytecode"; - let config = no_binding_config(); - let mut artifact = seal(bytecode, &config).unwrap(); - // Flip the first byte of the MAC - if let Some(b) = artifact.signature.first_mut() { - *b ^= 0xFF; - } - let result = unseal(&artifact, &[]); - assert!(result.is_err()); - } - - #[test] - fn test_serialization_roundtrip() { - let bytecode = b"fn main() { return 42 }"; - let config = no_binding_config(); - let artifact = seal(bytecode, &config).unwrap(); - let bytes = artifact.to_bytes().unwrap(); - let restored = SealedArtifact::from_bytes(&bytes).unwrap(); - let recovered = unseal(&restored, &[]).unwrap(); - assert_eq!(recovered, bytecode); - } - - #[test] - fn test_magic_header_present() { - let artifact = seal(b"test", &no_binding_config()).unwrap(); - let bytes = artifact.to_bytes().unwrap(); - assert_eq!(&bytes[..8], b"ENGRAM01"); - } - - #[test] - fn test_wrong_magic_rejected() { - let mut bytes = seal(b"test", &no_binding_config()).unwrap().to_bytes().unwrap(); - bytes[0] = 0xFF; // corrupt magic - let result = SealedArtifact::from_bytes(&bytes); - assert!(result.is_err()); - } - - #[test] - fn test_verify_structural_ok() { - let artifact = seal(b"bytecode", &no_binding_config()).unwrap(); - assert!(verify(&artifact).unwrap()); - } - - #[test] - fn test_empty_bytecode_sealable() { - let artifact = seal(b"", &no_binding_config()).unwrap(); - let recovered = unseal(&artifact, &[]).unwrap(); - assert_eq!(recovered, b""); - } - - #[test] - fn test_large_bytecode_sealable() { - let bytecode = vec![0x42u8; 100_000]; - let artifact = seal(&bytecode, &no_binding_config()).unwrap(); - let recovered = unseal(&artifact, &[]).unwrap(); - assert_eq!(recovered, bytecode); - } - - #[test] - fn test_algorithm_id_stored() { - let artifact = seal(b"test", &no_binding_config()).unwrap(); - assert_eq!(artifact.algorithm_id, "aes256gcm-v1"); - } - - #[test] - fn test_nonce_is_12_bytes() { - let artifact = seal(b"test", &no_binding_config()).unwrap(); - assert_eq!(artifact.nonce.len(), 12); - } - - #[test] - fn test_encapsulated_key_is_32_bytes() { - let artifact = seal(b"test", &no_binding_config()).unwrap(); - assert_eq!(artifact.encapsulated_key.len(), 32); - } - - #[test] - fn test_different_seals_produce_different_ciphertexts() { - let bytecode = b"same input"; - let config = no_binding_config(); - let a1 = seal(bytecode, &config).unwrap(); - let a2 = seal(bytecode, &config).unwrap(); - // Random nonce means ciphertexts differ - assert_ne!(a1.ciphertext, a2.ciphertext); - assert_ne!(a1.nonce, a2.nonce); - } - - #[test] - fn test_missing_env_key_returns_error() { - std::env::remove_var("_EL_NONEXISTENT_KEY"); - let result = seal(b"test", &env_binding_config("_EL_NONEXISTENT_KEY")); - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), SealError::MissingEnvKey(_))); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/Cargo.toml b/_archive/rust-bootstrap/engrams/el-stdlib/Cargo.toml deleted file mode 100644 index 43c9615..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "el-stdlib" -description = "Engram language standard library — built-in function signatures and implementations" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-types = { workspace = true } -el-parser = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/array.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/array.rs deleted file mode 100644 index 7c31fe6..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/array.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Array operations: map, filter, reduce, find, any, all, length, push, pop, -//! sort, reverse, zip, enumerate. - -use el_types::{Type, TypeEnv}; -use super::fn_type; - -pub fn register(env: &mut TypeEnv) { - let arr_int = Type::Array(Box::new(Type::Int)); - let arr_str = Type::Array(Box::new(Type::String)); - let arr_unk = Type::Array(Box::new(Type::Unknown)); - - // array_length([T]) -> Int - env.register_fn("array_length", fn_type(vec![arr_unk.clone()], Type::Int)); - // array_push([T], T) -> [T] - env.register_fn("array_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone())); - // array_pop([T]) -> T? - env.register_fn("array_pop", fn_type(vec![arr_unk.clone()], Type::Optional(Box::new(Type::Unknown)))); - // array_map([T], fn(T) -> U) -> [U] - let mapper = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) }; - env.register_fn("array_map", fn_type(vec![arr_unk.clone(), mapper.clone()], arr_unk.clone())); - // array_filter([T], fn(T) -> Bool) -> [T] - let predicate = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Bool) }; - env.register_fn("array_filter", fn_type(vec![arr_unk.clone(), predicate.clone()], arr_unk.clone())); - // array_reduce([T], U, fn(U, T) -> U) -> U - let reducer = Type::Fn { params: vec![Type::Unknown, Type::Unknown], return_type: Box::new(Type::Unknown) }; - env.register_fn("array_reduce", fn_type(vec![arr_unk.clone(), Type::Unknown, reducer], Type::Unknown)); - // array_find([T], fn(T) -> Bool) -> T? - env.register_fn("array_find", fn_type(vec![arr_unk.clone(), predicate.clone()], Type::Optional(Box::new(Type::Unknown)))); - // array_any([T], fn(T) -> Bool) -> Bool - env.register_fn("array_any", fn_type(vec![arr_unk.clone(), predicate.clone()], Type::Bool)); - // array_all([T], fn(T) -> Bool) -> Bool - env.register_fn("array_all", fn_type(vec![arr_unk.clone(), predicate], Type::Bool)); - // array_sort([Int]) -> [Int] - env.register_fn("array_sort", fn_type(vec![arr_int.clone()], arr_int.clone())); - // array_reverse([T]) -> [T] - env.register_fn("array_reverse", fn_type(vec![arr_unk.clone()], arr_unk.clone())); - // array_zip([T], [U]) -> [[T]] (simplified: returns array of unknown) - env.register_fn("array_zip", fn_type(vec![arr_unk.clone(), arr_unk.clone()], arr_unk.clone())); - // array_enumerate([T]) -> [[T]] (returns pairs as arrays) - env.register_fn("array_enumerate", fn_type(vec![arr_unk.clone()], arr_unk.clone())); - // array_join([String], String) -> String - env.register_fn("array_join", fn_type(vec![arr_str.clone(), Type::String], Type::String)); - // array_concat([T], [T]) -> [T] - env.register_fn("array_concat", fn_type(vec![arr_unk.clone(), arr_unk.clone()], arr_unk.clone())); - // array_slice([T], Int, Int) -> [T] - env.register_fn("array_slice", fn_type(vec![arr_unk.clone(), Type::Int, Type::Int], arr_unk.clone())); - // array_first([T]) -> T? - env.register_fn("array_first", fn_type(vec![arr_int.clone()], Type::Optional(Box::new(Type::Int)))); - // array_last([T]) -> T? - env.register_fn("array_last", fn_type(vec![arr_int.clone()], Type::Optional(Box::new(Type::Int)))); - // array_contains([String], String) -> Bool - env.register_fn("array_contains", fn_type(vec![arr_str, Type::String], Type::Bool)); - // New list/stack/queue builtins - env.register_fn("list_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone())); - env.register_fn("list_pop", fn_type(vec![arr_unk.clone()], arr_unk.clone())); - env.register_fn("list_pop_front", fn_type(vec![arr_unk.clone()], arr_unk.clone())); - env.register_fn("list_peek_last", fn_type(vec![arr_unk.clone()], Type::Unknown)); - env.register_fn("list_range", fn_type(vec![Type::Int, Type::Int], arr_unk.clone())); - env.register_fn("list_new", fn_type(vec![], arr_unk.clone())); - env.register_fn("list_empty", fn_type(vec![arr_unk.clone()], Type::Bool)); - env.register_fn("list_map", fn_type(vec![arr_unk.clone(), Type::String], arr_unk.clone())); - env.register_fn("list_filter", fn_type(vec![arr_unk.clone(), Type::String], arr_unk.clone())); - env.register_fn("list_reduce", fn_type(vec![arr_unk.clone(), Type::Unknown, Type::String], Type::Unknown)); - env.register_fn("fn_ref", fn_type(vec![Type::String], Type::String)); - // Stack - env.register_fn("stack_push", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone())); - env.register_fn("stack_pop", fn_type(vec![arr_unk.clone()], arr_unk.clone())); - env.register_fn("stack_peek", fn_type(vec![arr_unk.clone()], Type::Unknown)); - env.register_fn("stack_new", fn_type(vec![], arr_unk.clone())); - // Queue - env.register_fn("queue_enqueue", fn_type(vec![arr_unk.clone(), Type::Unknown], arr_unk.clone())); - env.register_fn("queue_dequeue", fn_type(vec![arr_unk.clone()], arr_unk.clone())); - env.register_fn("queue_peek", fn_type(vec![arr_unk.clone()], Type::Unknown)); - env.register_fn("queue_new", fn_type(vec![], arr_unk)); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - let mut e = TypeEnv::with_builtins(); - register(&mut e); - e - } - - #[test] - fn test_array_length_registered() { - assert!(env().lookup_fn("array_length").is_some()); - } - - #[test] - fn test_array_map_is_fn_type() { - let e = env(); - let ty = e.lookup_fn("array_map").unwrap(); - assert!(matches!(ty, Type::Fn { .. })); - } - - #[test] - fn test_array_filter_registered() { - assert!(env().lookup_fn("array_filter").is_some()); - } - - #[test] - fn test_array_push_registered() { - assert!(env().lookup_fn("array_push").is_some()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/engram.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/engram.rs deleted file mode 100644 index d21a975..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/engram.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Engram graph operations: activate, relate, forget, edge_between, neighbors. -//! -//! These are thin wrappers over the Engram HTTP API. They are registered as -//! built-in functions in the type environment so el programs can call them -//! directly without an import. - -use el_types::{Type, TypeEnv}; -use super::fn_type; - -pub fn register(env: &mut TypeEnv) { - let arr_unk = Type::Array(Box::new(Type::Unknown)); - - // engram_activate(type_name: String, query: String) -> [T] - env.register_fn("engram_activate", fn_type(vec![Type::String, Type::String], arr_unk.clone())); - - // engram_relate(from_id: Uuid, to_id: Uuid, relation: String, weight: Float) -> Void - env.register_fn("engram_relate", fn_type( - vec![Type::Uuid, Type::Uuid, Type::String, Type::Float], - Type::Void, - )); - - // engram_forget(node_id: Uuid) -> Void - env.register_fn("engram_forget", fn_type(vec![Type::Uuid], Type::Void)); - - // engram_edge_between(from_id: Uuid, to_id: Uuid) -> Bool - env.register_fn("engram_edge_between", fn_type(vec![Type::Uuid, Type::Uuid], Type::Bool)); - - // engram_neighbors(node_id: Uuid) -> [T] - env.register_fn("engram_neighbors", fn_type(vec![Type::Uuid], arr_unk.clone())); - - // engram_node_count() -> Int - env.register_fn("engram_node_count", fn_type(vec![], Type::Int)); - - // engram_search(query: String, limit: Int) -> [T] - env.register_fn("engram_search", fn_type(vec![Type::String, Type::Int], arr_unk)); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - let mut e = TypeEnv::with_builtins(); - register(&mut e); - e - } - - #[test] - fn test_engram_activate_registered() { - assert!(env().lookup_fn("engram_activate").is_some()); - } - - #[test] - fn test_engram_relate_registered() { - assert!(env().lookup_fn("engram_relate").is_some()); - } - - #[test] - fn test_engram_neighbors_registered() { - assert!(env().lookup_fn("engram_neighbors").is_some()); - } - - #[test] - fn test_engram_forget_returns_void() { - let e = env(); - let ty = e.lookup_fn("engram_forget").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Void))); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/lib.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/lib.rs deleted file mode 100644 index b038d83..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/lib.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! Engram language standard library. -//! -//! This crate defines function signatures for the built-in standard library -//! modules. Each module registers its functions into a [`TypeEnv`] so the -//! type checker can resolve calls to stdlib functions without an explicit -//! import. -//! -//! # Auto-imported modules -//! - `std::array` — array operations -//! - `std::string` — string operations -//! - `std::result` — Result operations -//! - `std::optional`— T? operations -//! - `std::math` — numeric operations -//! - `std::map` — Map operations -//! - `std::engram` — Engram graph operations - -pub mod array; -pub mod engram; -pub mod map; -pub mod math; -pub mod optional; -pub mod result; -pub mod string; - -use el_types::{Type, TypeEnv}; - -/// Register all automatically-imported stdlib modules into the given environment. -/// -/// Call this from `TypeEnv::with_builtins()` or at the start of type checking. -pub fn register_builtins(env: &mut TypeEnv) { - array::register(env); - string::register(env); - result::register(env); - optional::register(env); - math::register(env); - map::register(env); - engram::register(env); -} - -/// Helper: build a simple function type. -pub(crate) fn fn_type(params: Vec, ret: Type) -> Type { - Type::Fn { params, return_type: Box::new(ret) } -} - -#[cfg(test)] -mod tests { - use super::*; - use el_types::TypeEnv; - - fn stdlib_env() -> TypeEnv { - let mut env = TypeEnv::with_builtins(); - register_builtins(&mut env); - env - } - - #[test] - fn test_array_functions_registered() { - let env = stdlib_env(); - assert!(env.lookup_fn("array_map").is_some(), "array_map should be registered"); - assert!(env.lookup_fn("array_filter").is_some()); - assert!(env.lookup_fn("array_length").is_some()); - assert!(env.lookup_fn("array_push").is_some()); - } - - #[test] - fn test_string_functions_registered() { - let env = stdlib_env(); - assert!(env.lookup_fn("string_len").is_some()); - assert!(env.lookup_fn("string_trim").is_some()); - assert!(env.lookup_fn("string_split").is_some()); - assert!(env.lookup_fn("string_contains").is_some()); - } - - #[test] - fn test_math_functions_registered() { - let env = stdlib_env(); - assert!(env.lookup_fn("math_abs").is_some()); - assert!(env.lookup_fn("math_max").is_some()); - assert!(env.lookup_fn("math_min").is_some()); - assert!(env.lookup_fn("math_sqrt").is_some()); - } - - #[test] - fn test_result_functions_registered() { - let env = stdlib_env(); - assert!(env.lookup_fn("result_unwrap_or").is_some()); - assert!(env.lookup_fn("result_ok").is_some()); - } - - #[test] - fn test_optional_functions_registered() { - let env = stdlib_env(); - assert!(env.lookup_fn("optional_unwrap_or").is_some()); - assert!(env.lookup_fn("optional_is_some").is_some()); - assert!(env.lookup_fn("optional_is_none").is_some()); - } - - #[test] - fn test_map_functions_registered() { - let env = stdlib_env(); - assert!(env.lookup_fn("map_get").is_some()); - assert!(env.lookup_fn("map_set").is_some()); - assert!(env.lookup_fn("map_remove").is_some()); - } - - #[test] - fn test_engram_functions_registered() { - let env = stdlib_env(); - assert!(env.lookup_fn("engram_activate").is_some()); - assert!(env.lookup_fn("engram_relate").is_some()); - assert!(env.lookup_fn("engram_neighbors").is_some()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/map.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/map.rs deleted file mode 100644 index 28109be..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/map.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Map operations: get, set, remove, contains_key, keys, values, entries, merge. - -use el_types::{Type, TypeEnv}; -use super::fn_type; - -pub fn register(env: &mut TypeEnv) { - let map_unk = Type::Map { key: Box::new(Type::Unknown), value: Box::new(Type::Unknown) }; - let arr_unk = Type::Array(Box::new(Type::Unknown)); - - env.register_fn("map_get", fn_type(vec![map_unk.clone(), Type::Unknown], Type::Optional(Box::new(Type::Unknown)))); - env.register_fn("map_set", fn_type(vec![map_unk.clone(), Type::Unknown, Type::Unknown], map_unk.clone())); - env.register_fn("map_remove", fn_type(vec![map_unk.clone(), Type::Unknown], map_unk.clone())); - env.register_fn("map_contains_key", fn_type(vec![map_unk.clone(), Type::Unknown], Type::Bool)); - env.register_fn("map_keys", fn_type(vec![map_unk.clone()], arr_unk.clone())); - env.register_fn("map_values", fn_type(vec![map_unk.clone()], arr_unk.clone())); - env.register_fn("map_entries", fn_type(vec![map_unk.clone()], arr_unk.clone())); - env.register_fn("map_merge", fn_type(vec![map_unk.clone(), map_unk.clone()], map_unk.clone())); - env.register_fn("map_size", fn_type(vec![map_unk.clone()], Type::Int)); - env.register_fn("map_is_empty", fn_type(vec![map_unk.clone()], Type::Bool)); - env.register_fn("map_new", fn_type(vec![], map_unk)); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - let mut e = TypeEnv::with_builtins(); - register(&mut e); - e - } - - #[test] - fn test_map_get_registered() { - assert!(env().lookup_fn("map_get").is_some()); - } - - #[test] - fn test_map_get_returns_optional() { - let e = env(); - let ty = e.lookup_fn("map_get").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Optional(_)))); - } - - #[test] - fn test_map_contains_key_returns_bool() { - let e = env(); - let ty = e.lookup_fn("map_contains_key").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Bool))); - } - - #[test] - fn test_map_merge_registered() { - assert!(env().lookup_fn("map_merge").is_some()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/math.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/math.rs deleted file mode 100644 index faeb472..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/math.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Math operations: abs, max, min, floor, ceil, pow, sqrt, clamp. - -use el_types::{Type, TypeEnv}; -use super::fn_type; - -pub fn register(env: &mut TypeEnv) { - env.register_fn("math_abs", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_max", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("math_min", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("math_floor", fn_type(vec![Type::Float], Type::Int)); - env.register_fn("math_ceil", fn_type(vec![Type::Float], Type::Int)); - env.register_fn("math_round", fn_type(vec![Type::Float], Type::Int)); - env.register_fn("math_pow", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("math_sqrt", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_clamp", fn_type(vec![Type::Float, Type::Float, Type::Float], Type::Float)); - env.register_fn("math_abs_int", fn_type(vec![Type::Int], Type::Int)); - env.register_fn("math_max_int", fn_type(vec![Type::Int, Type::Int], Type::Int)); - env.register_fn("math_min_int", fn_type(vec![Type::Int, Type::Int], Type::Int)); - // Trig - env.register_fn("math_sin", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_cos", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_tan", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_asin", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_acos", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_atan2", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("math_exp", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_ln", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_log2", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_log10", fn_type(vec![Type::Float], Type::Float)); - env.register_fn("math_mod", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("math_pi", fn_type(vec![], Type::Float)); - env.register_fn("math_e", fn_type(vec![], Type::Float)); - // Conversion - env.register_fn("int_to_float", fn_type(vec![Type::Int], Type::Float)); - env.register_fn("float_to_int", fn_type(vec![Type::Float], Type::Int)); - env.register_fn("is_nil", fn_type(vec![Type::Unknown], Type::Bool)); - env.register_fn("unwrap_or", fn_type(vec![Type::Unknown, Type::Unknown], Type::Unknown)); - // Decimal - env.register_fn("decimal_add", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("decimal_sub", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("decimal_mul", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("decimal_div", fn_type(vec![Type::Float, Type::Float], Type::Float)); - env.register_fn("decimal_round", fn_type(vec![Type::Float, Type::Int], Type::Float)); - // Time - env.register_fn("time_now_utc", fn_type(vec![], Type::Int)); - env.register_fn("time_to_parts", fn_type(vec![Type::Int], Type::Unknown)); - env.register_fn("time_from_parts", fn_type(vec![Type::Unknown], Type::Int)); - env.register_fn("time_format", fn_type(vec![Type::Int, Type::String], Type::String)); - env.register_fn("time_parse", fn_type(vec![Type::String], Type::Int)); - env.register_fn("time_add", fn_type(vec![Type::Int, Type::Int, Type::String], Type::Int)); - env.register_fn("time_diff", fn_type(vec![Type::Int, Type::Int, Type::String], Type::Int)); - env.register_fn("time_start_of", fn_type(vec![Type::Int, Type::String], Type::Int)); - env.register_fn("time_tz_offset", fn_type(vec![Type::String], Type::Int)); - env.register_fn("time_to_tz", fn_type(vec![Type::Int, Type::String], Type::Unknown)); - // Observer - env.register_fn("observe", fn_type(vec![Type::String, Type::String], Type::Int)); - env.register_fn("unobserve", fn_type(vec![Type::Int], Type::Unknown)); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - let mut e = TypeEnv::with_builtins(); - register(&mut e); - e - } - - #[test] - fn test_math_abs_registered() { - assert!(env().lookup_fn("math_abs").is_some()); - } - - #[test] - fn test_math_sqrt_returns_float() { - let e = env(); - let ty = e.lookup_fn("math_sqrt").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Float))); - } - - #[test] - fn test_math_floor_returns_int() { - let e = env(); - let ty = e.lookup_fn("math_floor").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Int))); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/optional.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/optional.rs deleted file mode 100644 index c71f843..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/optional.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! T? operations: unwrap_or, map, flat_map, is_some, is_none. - -use el_types::{Type, TypeEnv}; -use super::fn_type; - -pub fn register(env: &mut TypeEnv) { - let opt_unk = Type::Optional(Box::new(Type::Unknown)); - let mapper = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) }; - - env.register_fn("optional_unwrap_or", fn_type(vec![opt_unk.clone(), Type::Unknown], Type::Unknown)); - env.register_fn("optional_unwrap_or_else", fn_type( - vec![opt_unk.clone(), Type::Fn { params: vec![], return_type: Box::new(Type::Unknown) }], - Type::Unknown, - )); - env.register_fn("optional_map", fn_type(vec![opt_unk.clone(), mapper.clone()], opt_unk.clone())); - env.register_fn("optional_flat_map", fn_type(vec![opt_unk.clone(), mapper.clone()], opt_unk.clone())); - env.register_fn("optional_is_some", fn_type(vec![opt_unk.clone()], Type::Bool)); - env.register_fn("optional_is_none", fn_type(vec![opt_unk.clone()], Type::Bool)); - env.register_fn("optional_filter", fn_type( - vec![opt_unk.clone(), Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Bool) }], - opt_unk.clone(), - )); - // some(T) -> T? - env.register_fn("some", fn_type(vec![Type::Unknown], opt_unk)); - // none() -> T? - env.register_fn("none", fn_type(vec![], Type::Optional(Box::new(Type::Unknown)))); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - let mut e = TypeEnv::with_builtins(); - register(&mut e); - e - } - - #[test] - fn test_optional_is_some_registered() { - assert!(env().lookup_fn("optional_is_some").is_some()); - } - - #[test] - fn test_optional_is_none_registered() { - assert!(env().lookup_fn("optional_is_none").is_some()); - } - - #[test] - fn test_optional_unwrap_or_registered() { - assert!(env().lookup_fn("optional_unwrap_or").is_some()); - } - - #[test] - fn test_some_and_none_registered() { - let e = env(); - assert!(e.lookup_fn("some").is_some()); - assert!(e.lookup_fn("none").is_some()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/result.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/result.rs deleted file mode 100644 index bcad825..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/result.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Result operations: map, map_err, unwrap_or, unwrap_or_else, and_then, ok. - -use el_types::{Type, TypeEnv}; -use super::fn_type; - -pub fn register(env: &mut TypeEnv) { - // result_unwrap_or(Result, T) -> T - let result_unk = Type::Result { - ok: Box::new(Type::Unknown), - err: Box::new(Type::Unknown), - }; - env.register_fn("result_unwrap_or", fn_type(vec![result_unk.clone(), Type::Unknown], Type::Unknown)); - env.register_fn("result_unwrap_or_else", fn_type( - vec![result_unk.clone(), Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) }], - Type::Unknown, - )); - // result_ok(Result) -> T? - env.register_fn("result_ok", fn_type(vec![result_unk.clone()], Type::Optional(Box::new(Type::Unknown)))); - // result_err(Result) -> E? - env.register_fn("result_err", fn_type(vec![result_unk.clone()], Type::Optional(Box::new(Type::Unknown)))); - // result_is_ok(Result) -> Bool - env.register_fn("result_is_ok", fn_type(vec![result_unk.clone()], Type::Bool)); - // result_is_err(Result) -> Bool - env.register_fn("result_is_err", fn_type(vec![result_unk.clone()], Type::Bool)); - // result_map(Result, fn(T) -> U) -> Result - let mapper = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(Type::Unknown) }; - env.register_fn("result_map", fn_type(vec![result_unk.clone(), mapper.clone()], result_unk.clone())); - // result_and_then(Result, fn(T) -> Result) -> Result - let chain_fn = Type::Fn { params: vec![Type::Unknown], return_type: Box::new(result_unk.clone()) }; - env.register_fn("result_and_then", fn_type(vec![result_unk.clone(), chain_fn], result_unk.clone())); - // result_ok_val(T) -> Result — wrap a value in Ok - env.register_fn("ok", fn_type(vec![Type::Unknown], result_unk.clone())); - // result_err_val(E) -> Result — wrap an error in Err - env.register_fn("err", fn_type(vec![Type::Unknown], result_unk)); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - let mut e = TypeEnv::with_builtins(); - register(&mut e); - e - } - - #[test] - fn test_result_unwrap_or_registered() { - assert!(env().lookup_fn("result_unwrap_or").is_some()); - } - - #[test] - fn test_result_ok_returns_optional() { - let e = env(); - let ty = e.lookup_fn("result_ok").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Optional(_)))); - } - - #[test] - fn test_ok_and_err_registered() { - let e = env(); - assert!(e.lookup_fn("ok").is_some()); - assert!(e.lookup_fn("err").is_some()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-stdlib/src/string.rs b/_archive/rust-bootstrap/engrams/el-stdlib/src/string.rs deleted file mode 100644 index 2d23061..0000000 --- a/_archive/rust-bootstrap/engrams/el-stdlib/src/string.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! String operations: trim, split, join, contains, starts_with, ends_with, -//! to_upper, to_lower, replace, len, chars. - -use el_types::{Type, TypeEnv}; -use super::fn_type; - -pub fn register(env: &mut TypeEnv) { - let arr_str = Type::Array(Box::new(Type::String)); - - env.register_fn("string_len", fn_type(vec![Type::String], Type::Int)); - env.register_fn("string_trim", fn_type(vec![Type::String], Type::String)); - env.register_fn("string_split", fn_type(vec![Type::String, Type::String], arr_str.clone())); - env.register_fn("string_join", fn_type(vec![arr_str.clone(), Type::String], Type::String)); - env.register_fn("string_contains", fn_type(vec![Type::String, Type::String], Type::Bool)); - env.register_fn("string_starts_with", fn_type(vec![Type::String, Type::String], Type::Bool)); - env.register_fn("string_ends_with", fn_type(vec![Type::String, Type::String], Type::Bool)); - env.register_fn("string_to_upper", fn_type(vec![Type::String], Type::String)); - env.register_fn("string_to_lower", fn_type(vec![Type::String], Type::String)); - env.register_fn("string_replace", fn_type(vec![Type::String, Type::String, Type::String], Type::String)); - env.register_fn("string_chars", fn_type(vec![Type::String], arr_str.clone())); - env.register_fn("string_slice", fn_type(vec![Type::String, Type::Int, Type::Int], Type::String)); - env.register_fn("string_repeat", fn_type(vec![Type::String, Type::Int], Type::String)); - env.register_fn("string_reverse", fn_type(vec![Type::String], Type::String)); - env.register_fn("string_parse_int", fn_type(vec![Type::String], Type::Optional(Box::new(Type::Int)))); - env.register_fn("string_parse_float", fn_type(vec![Type::String], Type::Optional(Box::new(Type::Float)))); - env.register_fn("string_from_int", fn_type(vec![Type::Int], Type::String)); - env.register_fn("string_from_float", fn_type(vec![Type::Float], Type::String)); - env.register_fn("string_is_empty", fn_type(vec![Type::String], Type::Bool)); - env.register_fn("string_concat", fn_type(vec![Type::String, Type::String], Type::String)); - // New string builtins - env.register_fn("str_char_at", fn_type(vec![Type::String, Type::Int], Type::String)); - env.register_fn("str_char_code", fn_type(vec![Type::String, Type::Int], Type::Int)); - env.register_fn("str_from_char_code", fn_type(vec![Type::Int], Type::String)); - env.register_fn("str_pad_left", fn_type(vec![Type::String, Type::Int, Type::String], Type::String)); - env.register_fn("str_pad_right", fn_type(vec![Type::String, Type::Int, Type::String], Type::String)); - env.register_fn("format_float", fn_type(vec![Type::Float, Type::Int], Type::String)); - env.register_fn("str_format", fn_type(vec![Type::String, Type::Unknown], Type::String)); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - let mut e = TypeEnv::with_builtins(); - register(&mut e); - e - } - - #[test] - fn test_string_len_returns_int() { - let e = env(); - let ty = e.lookup_fn("string_len").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Int))); - } - - #[test] - fn test_string_split_returns_array() { - let e = env(); - let ty = e.lookup_fn("string_split").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Array(_)))); - } - - #[test] - fn test_string_contains_returns_bool() { - let e = env(); - let ty = e.lookup_fn("string_contains").unwrap(); - assert!(matches!(ty, Type::Fn { return_type, .. } if matches!(return_type.as_ref(), Type::Bool))); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-test/Cargo.toml b/_archive/rust-bootstrap/engrams/el-test/Cargo.toml deleted file mode 100644 index d5705c8..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "el-test" -description = "Engram language unified testing framework — unit and e2e same syntax, seed-based graph testing" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-lexer = { workspace = true } -el-parser = { workspace = true } -el-types = { workspace = true } -el-compiler = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -thiserror = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-test/src/discovery.rs b/_archive/rust-bootstrap/engrams/el-test/src/discovery.rs deleted file mode 100644 index de89961..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/src/discovery.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Test discovery — finds all `test` blocks in an `.el` source file. - -use el_lexer::tokenize; -use el_parser::{parse, Stmt}; - -use crate::types::{TestCase, TestTarget}; - -/// Parse a source string and extract all `test` block definitions. -/// -/// Returns an error if the source cannot be lexed or parsed. -pub fn discover(source: &str) -> Result, String> { - let tokens = tokenize(source).map_err(|e| format!("lex error: {e}"))?; - let program = parse(tokens, source.to_string()).map_err(|e| format!("parse error: {e}"))?; - let mut cases = Vec::new(); - collect_tests(&program.stmts, &mut cases); - Ok(cases) -} - -fn collect_tests(stmts: &[Stmt], out: &mut Vec) { - for stmt in stmts { - if let Stmt::TestDef { name, target, body, .. } = stmt { - out.push(TestCase { - name: name.clone(), - target: TestTarget::from(target.clone()), - body: body.clone(), - }); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_discover_empty_source() { - let cases = discover("").unwrap(); - assert!(cases.is_empty()); - } - - #[test] - fn test_discover_no_tests() { - let src = r#"let x: Int = 42"#; - let cases = discover(src).unwrap(); - assert!(cases.is_empty()); - } - - #[test] - fn test_discover_single_test() { - let src = r#" -test "basic arithmetic" { - let x: Int = 6 - let y: Int = 7 -} -"#; - let cases = discover(src).unwrap(); - assert_eq!(cases.len(), 1); - assert_eq!(cases[0].name, "basic arithmetic"); - assert_eq!(cases[0].target, TestTarget::Unit); - } - - #[test] - fn test_discover_multiple_tests() { - let src = r#" -test "test one" { - let x: Int = 1 -} -test "test two" { - let y: Int = 2 -} -"#; - let cases = discover(src).unwrap(); - assert_eq!(cases.len(), 2); - assert_eq!(cases[0].name, "test one"); - assert_eq!(cases[1].name, "test two"); - } - - #[test] - fn test_discover_e2e_target() { - let src = r#" -test "production lookup" target: e2e { - let x: Int = 1 -} -"#; - let cases = discover(src).unwrap(); - assert_eq!(cases.len(), 1); - assert_eq!(cases[0].target, TestTarget::E2e); - } - - #[test] - fn test_discover_both_target() { - let src = r#" -test "dual target" target: both { - let x: Int = 1 -} -"#; - let cases = discover(src).unwrap(); - assert_eq!(cases[0].target, TestTarget::Both); - } - - #[test] - fn test_discover_default_target_is_unit() { - let src = r#"test "no target" { let x: Int = 1 }"#; - let cases = discover(src).unwrap(); - assert_eq!(cases[0].target, TestTarget::Unit); - } - - #[test] - fn test_discover_mixed_stmts() { - let src = r#" -let x: Int = 42 -test "my test" { - let y: Int = x -} -fn helper() -> Int { return 1 } -"#; - let cases = discover(src).unwrap(); - assert_eq!(cases.len(), 1); - } - - #[test] - fn test_discover_lex_error() { - let result = discover(r#""unterminated"#); - assert!(result.is_err()); - } - - #[test] - fn test_discover_body_preserved() { - let src = r#" -test "body check" { - let x: Int = 1 - let y: Int = 2 -} -"#; - let cases = discover(src).unwrap(); - assert_eq!(cases[0].body.len(), 2); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-test/src/eval.rs b/_archive/rust-bootstrap/engrams/el-test/src/eval.rs deleted file mode 100644 index 3b3bbbe..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/src/eval.rs +++ /dev/null @@ -1,598 +0,0 @@ -//! Test expression evaluator. -//! -//! Walks AST expressions inside a test block and produces runtime `EvalValue`s. -//! This is a lightweight direct evaluator (not the full bytecode VM) specifically -//! designed for the simple expression patterns that appear in test assertions. - -use std::collections::HashMap; - -use el_parser::{BinOp, Expr, Literal, Stmt}; - -use crate::graph::{ActivatedNode, ReasoningResult, TestGraph}; -use crate::types::AssertionResult; - -/// A runtime value in the test evaluator. -#[derive(Debug, Clone, PartialEq)] -pub enum EvalValue { - Int(i64), - Float(f64), - Str(String), - Bool(bool), - Nil, - /// A list of activated graph nodes (result of `activate`) - NodeList(Vec), - /// Result of a `reason()` call - Reasoning(ReasoningResultValue), -} - -/// Simplified reasoning result value (owns the verdict string for comparison). -#[derive(Debug, Clone, PartialEq)] -pub struct ReasoningResultValue { - pub verdict: String, - pub confidence: f32, -} - -impl EvalValue { - /// Try to get the length of a list/string value. - pub fn len(&self) -> Option { - match self { - EvalValue::NodeList(v) => Some(v.len() as i64), - EvalValue::Str(s) => Some(s.len() as i64), - _ => None, - } - } - - /// Try to index into a list. - pub fn index(&self, i: i64) -> Option { - match self { - EvalValue::NodeList(v) => { - let idx = if i < 0 { - let uidx = (-i) as usize; - v.len().checked_sub(uidx)? - } else { - i as usize - }; - let node = v.get(idx)?; - // Return the node as a struct-like value — we special-case field access - Some(EvalValue::NodeList(vec![node.clone()])) - } - _ => None, - } - } - - /// Check if this value contains a substring (for string `contains` keyword). - pub fn contains_str(&self, needle: &str) -> bool { - match self { - EvalValue::Str(s) => s.contains(needle), - EvalValue::NodeList(v) => v.iter().any(|n| n.content.contains(needle)), - _ => false, - } - } - - pub fn as_bool(&self) -> bool { - match self { - EvalValue::Bool(b) => *b, - EvalValue::Nil => false, - _ => true, - } - } -} - -impl std::fmt::Display for EvalValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - EvalValue::Int(n) => write!(f, "{n}"), - EvalValue::Float(n) => write!(f, "{n}"), - EvalValue::Str(s) => write!(f, "{s}"), - EvalValue::Bool(b) => write!(f, "{b}"), - EvalValue::Nil => write!(f, "nil"), - EvalValue::NodeList(v) => write!(f, "[{} node(s)]", v.len()), - EvalValue::Reasoning(r) => write!(f, "ReasoningResult {{ verdict: {} }}", r.verdict), - } - } -} - -/// Evaluator context — holds local bindings and the graph. -pub struct Evaluator<'g> { - locals: HashMap, - graph: &'g TestGraph, -} - -impl<'g> Evaluator<'g> { - pub fn new(graph: &'g TestGraph) -> Self { - Self { - locals: HashMap::new(), - graph, - } - } - - /// Execute a statement. Returns `Ok(())` or an error string. - pub fn exec_stmt(&mut self, stmt: &Stmt) -> Result<(), String> { - match stmt { - Stmt::Let { name, value, .. } => { - let v = self.eval_expr(value)?; - self.locals.insert(name.clone(), v); - } - Stmt::Expr(expr, _) => { - self.eval_expr(expr)?; - } - Stmt::Return(..) => { - // Return from test body — treat as no-op continuation - } - // Seed statements are handled before eval in the runner - Stmt::Seed(..) | Stmt::TestDef { .. } | Stmt::Assert(..) => {} - Stmt::FnDef { .. } | Stmt::TypeDef { .. } | Stmt::EnumDef { .. } => {} - // New statement kinds — skip - _ => {} - } - Ok(()) - } - - /// Evaluate an expression, returning its value. - pub fn eval_expr(&mut self, expr: &Expr) -> Result { - match expr { - Expr::Literal(lit) => Ok(match lit { - Literal::Int(n) => EvalValue::Int(*n), - Literal::Float(f) => EvalValue::Float(*f), - Literal::Str(s) => EvalValue::Str(s.clone()), - Literal::Bool(b) => EvalValue::Bool(*b), - }), - - Expr::Ident(name) => { - self.locals.get(name).cloned().ok_or_else(|| format!("undefined variable '{name}'")) - } - - Expr::BinOp { op, left, right } => { - // Special: handle ` contains ` — parsed as BinOp in the `contains` handler - // Actually `contains` is a keyword identifier parsed as Ident in the postfix context. - // We handle it as a Call pattern below. - let lv = self.eval_expr(left)?; - let rv = self.eval_expr(right)?; - self.eval_binop(op, lv, rv) - } - - Expr::Call { func, args } => { - match func.as_ref() { - // reason("hypothesis") — built-in - Expr::Ident(name) if name == "reason" => { - let arg = args.first().ok_or("reason() requires one argument")?; - let hypothesis = match self.eval_expr(arg)? { - EvalValue::Str(s) => s, - other => return Err(format!("reason() expects a string, got {other}")), - }; - let result: ReasoningResult = self.graph.reason(&hypothesis); - Ok(EvalValue::Reasoning(ReasoningResultValue { - verdict: result.verdict.to_string(), - confidence: result.confidence, - })) - } - - // results.len() — method call on a local - Expr::Field { object, field } if field == "len" => { - let obj = self.eval_expr(object)?; - match obj.len() { - Some(n) => Ok(EvalValue::Int(n)), - None => Err(format!("cannot call .len() on {obj}")), - } - } - - // results[0].content contains "needle" - // `contains` is parsed as an Ident called as a method - Expr::Field { object, field } if field == "contains" => { - let obj = self.eval_expr(object)?; - let needle_arg = args.first().ok_or("contains() requires one argument")?; - let needle = match self.eval_expr(needle_arg)? { - EvalValue::Str(s) => s, - other => return Err(format!("contains() expects a string, got {other}")), - }; - Ok(EvalValue::Bool(obj.contains_str(&needle))) - } - - _ => { - // Unknown call — return nil - Ok(EvalValue::Nil) - } - } - } - - Expr::Field { object, field } => { - let obj = self.eval_expr(object)?; - match &obj { - EvalValue::NodeList(nodes) if nodes.len() == 1 => { - let node = &nodes[0]; - match field.as_str() { - "content" => Ok(EvalValue::Str(node.content.clone())), - "node_type" => Ok(EvalValue::Str(node.node_type.clone())), - "importance" => Ok(EvalValue::Float(node.importance as f64)), - "id" => Ok(EvalValue::Str(node.id.to_string())), - other => Err(format!("no field '{other}' on ActivatedNode")), - } - } - EvalValue::Reasoning(r) => match field.as_str() { - "verdict" => Ok(EvalValue::Str(r.verdict.clone())), - "confidence" => Ok(EvalValue::Float(r.confidence as f64)), - other => Err(format!("no field '{other}' on ReasoningResult")), - }, - _ => Err(format!("cannot access field '{field}' on {obj}")), - } - } - - Expr::Index { object, index } => { - let obj = self.eval_expr(object)?; - let idx = self.eval_expr(index)?; - let i = match idx { - EvalValue::Int(n) => n, - other => return Err(format!("index must be Int, got {other}")), - }; - obj.index(i).ok_or_else(|| format!("index {i} out of bounds")) - } - - Expr::Activate { type_name, query } => { - let nodes = self.graph.activate(query, Some(type_name)); - Ok(EvalValue::NodeList(nodes)) - } - - Expr::UnaryNot(inner) => { - let v = self.eval_expr(inner)?; - Ok(EvalValue::Bool(!v.as_bool())) - } - - Expr::Block(stmts) => { - for s in stmts { - self.exec_stmt(s)?; - } - Ok(EvalValue::Nil) - } - - Expr::If { cond, then, else_ } => { - let cv = self.eval_expr(cond)?; - if cv.as_bool() { - self.eval_expr(then) - } else if let Some(e) = else_ { - self.eval_expr(e) - } else { - Ok(EvalValue::Nil) - } - } - - Expr::Array(elems) => { - // For simplicity — arrays of primitives in tests - let mut vals = Vec::new(); - for e in elems { - vals.push(self.eval_expr(e)?); - } - // Return first if all same, else nil - Ok(EvalValue::Nil) - } - - Expr::Path { segments } => { - // Enum variant reference — return as string (e.g. "Insufficient") - Ok(EvalValue::Str(segments.last().cloned().unwrap_or_default())) - } - - Expr::Match { subject, arms } => { - let sv = self.eval_expr(subject)?; - for arm in arms { - // Simplified: compare subject to pattern - let matches = match &arm.pattern { - el_parser::Pattern::Wildcard => true, - el_parser::Pattern::Literal(lit) => { - let lv = match lit { - Literal::Int(n) => EvalValue::Int(*n), - Literal::Float(f) => EvalValue::Float(*f), - Literal::Str(s) => EvalValue::Str(s.clone()), - Literal::Bool(b) => EvalValue::Bool(*b), - }; - sv == lv - } - el_parser::Pattern::Binding(name) => { - self.locals.insert(name.clone(), sv.clone()); - true - } - el_parser::Pattern::EnumVariant { variant, .. } => { - sv == EvalValue::Str(variant.clone()) - } - }; - if matches { - return self.eval_expr(&arm.body); - } - } - Ok(EvalValue::Nil) - } - - Expr::Sealed(stmts) => { - for s in stmts { - self.exec_stmt(s)?; - } - Ok(EvalValue::Nil) - } - - Expr::StructLit { type_name: _, fields, .. } => { - // Evaluate all fields but return Nil — struct construction in test - // eval context is not supported yet (tests use activate, not literals) - for (_, e) in fields { - self.eval_expr(e)?; - } - Ok(EvalValue::Nil) - } - // New expression kinds — return Nil - _ => { - Ok(EvalValue::Nil) - } - } - } - - fn eval_binop(&self, op: &BinOp, lv: EvalValue, rv: EvalValue) -> Result { - match op { - BinOp::Add => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a + b)), - (EvalValue::Float(a), EvalValue::Float(b)) => Ok(EvalValue::Float(a + b)), - (EvalValue::Str(a), EvalValue::Str(b)) => Ok(EvalValue::Str(a + &b)), - (a, b) => Err(format!("cannot add {a} and {b}")), - }, - BinOp::Sub => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a - b)), - (EvalValue::Float(a), EvalValue::Float(b)) => Ok(EvalValue::Float(a - b)), - (a, b) => Err(format!("cannot subtract {a} and {b}")), - }, - BinOp::Mul => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a * b)), - (EvalValue::Float(a), EvalValue::Float(b)) => Ok(EvalValue::Float(a * b)), - (a, b) => Err(format!("cannot multiply {a} and {b}")), - }, - BinOp::Div => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) if b != 0 => Ok(EvalValue::Int(a / b)), - (EvalValue::Float(a), EvalValue::Float(b)) => Ok(EvalValue::Float(a / b)), - (_, EvalValue::Int(0)) => Err("division by zero".into()), - (a, b) => Err(format!("cannot divide {a} and {b}")), - }, - BinOp::Eq => Ok(EvalValue::Bool(lv == rv)), - BinOp::NotEq => Ok(EvalValue::Bool(lv != rv)), - BinOp::Lt => self.compare_ord(lv, rv, |o| o == std::cmp::Ordering::Less), - BinOp::Gt => self.compare_ord(lv, rv, |o| o == std::cmp::Ordering::Greater), - BinOp::LtEq => self.compare_ord(lv, rv, |o| o != std::cmp::Ordering::Greater), - BinOp::GtEq => self.compare_ord(lv, rv, |o| o != std::cmp::Ordering::Less), - BinOp::And => Ok(EvalValue::Bool(lv.as_bool() && rv.as_bool())), - BinOp::Or => Ok(EvalValue::Bool(lv.as_bool() || rv.as_bool())), - BinOp::Mod => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) if b != 0 => Ok(EvalValue::Int(a % b)), - _ => Ok(EvalValue::Int(0)), - }, - BinOp::BitAnd => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a & b)), - _ => Ok(EvalValue::Int(0)), - }, - BinOp::BitOr => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a | b)), - _ => Ok(EvalValue::Int(0)), - }, - BinOp::BitXor => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a ^ b)), - _ => Ok(EvalValue::Int(0)), - }, - BinOp::Shl => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a << (b as u32))), - _ => Ok(EvalValue::Int(0)), - }, - BinOp::Shr => match (lv, rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => Ok(EvalValue::Int(a >> (b as u32))), - _ => Ok(EvalValue::Int(0)), - }, - BinOp::NullCoalesce => { - // `a ?? b` — use b if a is nil/empty/false - match &lv { - EvalValue::Nil => Ok(rv), - EvalValue::Str(s) if s.is_empty() => Ok(rv), - EvalValue::Bool(false) => Ok(rv), - _ => Ok(lv), - } - } - } - } - - fn compare_ord(&self, lv: EvalValue, rv: EvalValue, pred: F) -> Result - where - F: Fn(std::cmp::Ordering) -> bool, - { - let ord = match (&lv, &rv) { - (EvalValue::Int(a), EvalValue::Int(b)) => a.cmp(b), - (EvalValue::Float(a), EvalValue::Float(b)) => { - a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) - } - _ => return Err(format!("cannot compare {lv} and {rv}")), - }; - Ok(EvalValue::Bool(pred(ord))) - } -} - -/// Evaluate a single `assert` expression and return an `AssertionResult`. -/// -/// The `expr_text` is the source text representation of the assertion (for -/// diagnostic output). -pub fn evaluate_assert( - eval: &mut Evaluator, - expr: &Expr, - expr_text: &str, -) -> AssertionResult { - match eval.eval_expr(expr) { - Ok(val) => { - let passed = val.as_bool(); - AssertionResult { - expression: expr_text.to_string(), - passed, - actual: Some(val.to_string()), - expected: None, - } - } - Err(_e) => AssertionResult { - expression: expr_text.to_string(), - passed: false, - actual: None, - expected: None, - } - } -} - -/// Render an expression as a human-readable string (best-effort, for display). -pub fn expr_to_text(expr: &Expr) -> String { - match expr { - Expr::Literal(Literal::Int(n)) => n.to_string(), - Expr::Literal(Literal::Float(f)) => f.to_string(), - Expr::Literal(Literal::Str(s)) => format!("\"{s}\""), - Expr::Literal(Literal::Bool(b)) => b.to_string(), - Expr::Ident(name) => name.clone(), - Expr::BinOp { op, left, right } => { - let op_str = match op { - BinOp::Add => "+", BinOp::Sub => "-", BinOp::Mul => "*", BinOp::Div => "/", - BinOp::Eq => "==", BinOp::NotEq => "!=", - BinOp::Lt => "<", BinOp::Gt => ">", BinOp::LtEq => "<=", BinOp::GtEq => ">=", - BinOp::And => "&&", BinOp::Or => "||", - BinOp::Mod => "%", BinOp::BitAnd => "&", BinOp::BitOr => "|", - BinOp::BitXor => "^", BinOp::Shl => "<<", BinOp::Shr => ">>", - BinOp::NullCoalesce => "??", - }; - format!("{} {op_str} {}", expr_to_text(left), expr_to_text(right)) - } - Expr::Field { object, field } => format!("{}.{field}", expr_to_text(object)), - Expr::Call { func, args } => { - let args_str: Vec<_> = args.iter().map(expr_to_text).collect(); - format!("{}({})", expr_to_text(func), args_str.join(", ")) - } - Expr::Index { object, index } => format!("{}[{}]", expr_to_text(object), expr_to_text(index)), - Expr::Activate { type_name, query } => format!("activate {type_name} where \"{query}\""), - Expr::UnaryNot(inner) => format!("!{}", expr_to_text(inner)), - Expr::Path { segments } => segments.join("::"), - _ => "".to_string(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::graph::TestGraph; - - fn fresh_eval(g: &TestGraph) -> Evaluator { - Evaluator::new(g) - } - - #[test] - fn test_eval_int_literal() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::Literal(Literal::Int(42)); - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Int(42)); - } - - #[test] - fn test_eval_bool_literal() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::Literal(Literal::Bool(true)); - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Bool(true)); - } - - #[test] - fn test_eval_string_literal() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::Literal(Literal::Str("hello".into())); - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Str("hello".into())); - } - - #[test] - fn test_eval_addition() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::BinOp { - op: BinOp::Add, - left: Box::new(Expr::Literal(Literal::Int(3))), - right: Box::new(Expr::Literal(Literal::Int(4))), - }; - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Int(7)); - } - - #[test] - fn test_eval_multiplication() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::BinOp { - op: BinOp::Mul, - left: Box::new(Expr::Literal(Literal::Int(6))), - right: Box::new(Expr::Literal(Literal::Int(7))), - }; - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Int(42)); - } - - #[test] - fn test_eval_equality_true() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::BinOp { - op: BinOp::Eq, - left: Box::new(Expr::Literal(Literal::Int(42))), - right: Box::new(Expr::Literal(Literal::Int(42))), - }; - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Bool(true)); - } - - #[test] - fn test_eval_greater_than() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::BinOp { - op: BinOp::Gt, - left: Box::new(Expr::Literal(Literal::Int(5))), - right: Box::new(Expr::Literal(Literal::Int(3))), - }; - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Bool(true)); - } - - #[test] - fn test_eval_activate_empty_graph() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::Activate { - type_name: "Customer".into(), - query: "anything".into(), - }; - let result = e.eval_expr(&expr).unwrap(); - assert!(matches!(result, EvalValue::NodeList(ref v) if v.is_empty())); - } - - #[test] - fn test_eval_activate_with_seeds() { - let mut g = TestGraph::new(); - g.seed_node("Customer", "Will Anderson, founding member", 0.9, None); - let mut e = fresh_eval(&g); - let expr = Expr::Activate { - type_name: "Customer".into(), - query: "founding".into(), - }; - let result = e.eval_expr(&expr).unwrap(); - assert!(matches!(result, EvalValue::NodeList(ref v) if v.len() == 1)); - } - - #[test] - fn test_eval_let_and_ident() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - e.locals.insert("x".into(), EvalValue::Int(10)); - let expr = Expr::Ident("x".into()); - assert_eq!(e.eval_expr(&expr).unwrap(), EvalValue::Int(10)); - } - - #[test] - fn test_eval_reason_empty_graph() { - let g = TestGraph::new(); - let mut e = fresh_eval(&g); - let expr = Expr::Call { - func: Box::new(Expr::Ident("reason".into())), - args: vec![Expr::Literal(Literal::Str("Is there a customer?".into()))], - }; - let result = e.eval_expr(&expr).unwrap(); - match result { - EvalValue::Reasoning(r) => assert_eq!(r.verdict, "Insufficient"), - _ => panic!("expected Reasoning"), - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-test/src/graph.rs b/_archive/rust-bootstrap/engrams/el-test/src/graph.rs deleted file mode 100644 index 6243f0c..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/src/graph.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! In-memory graph for test seeding and activation. -//! -//! `TestGraph` provides the runtime behind `seed Node { ... }`, `seed Edge { ... }`, -//! `activate T where "query"`, and `reason("hypothesis")` in test blocks. -//! -//! For **unit tests** the graph is in-memory only — no disk, no external DB. -//! For **e2e tests** the graph would delegate to a real Engram database -//! (full implementation when Engram DB Rust client is available). - -use std::collections::HashMap; - -/// Unique node identifier (simplified UUID representation). -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct NodeId(pub String); - -impl NodeId { - pub fn new() -> Self { - // Simple deterministic ID for tests (real impl would use uuid crate) - use std::sync::atomic::{AtomicU64, Ordering}; - static COUNTER: AtomicU64 = AtomicU64::new(1); - let n = COUNTER.fetch_add(1, Ordering::Relaxed); - NodeId(format!("node-{n:08x}")) - } -} - -impl Default for NodeId { - fn default() -> Self { - Self::new() - } -} - -impl std::fmt::Display for NodeId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -/// A node in the test graph. -#[derive(Debug, Clone)] -pub struct GraphNode { - pub id: NodeId, - pub node_type: String, - pub content: String, - pub importance: f32, - #[allow(dead_code)] - pub tier: Option, -} - -/// A directed edge between two graph nodes. -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub struct GraphEdge { - pub from: NodeId, - pub to: NodeId, - pub relation: String, - pub weight: f32, -} - -/// A node returned by an `activate` query. -#[derive(Debug, Clone, PartialEq)] -pub struct ActivatedNode { - pub id: NodeId, - pub node_type: String, - pub content: String, - pub importance: f32, -} - -/// The verdict of a `reason()` call. -#[derive(Debug, Clone, PartialEq)] -pub enum ReasoningVerdict { - /// Evidence found; hypothesis is supported. - Supported, - /// Evidence found; hypothesis is contradicted. - Contradicted, - /// Insufficient evidence to evaluate hypothesis. - Insufficient, -} - -impl std::fmt::Display for ReasoningVerdict { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ReasoningVerdict::Supported => write!(f, "Supported"), - ReasoningVerdict::Contradicted => write!(f, "Contradicted"), - ReasoningVerdict::Insufficient => write!(f, "Insufficient"), - } - } -} - -/// Result of a `reason()` call. -#[derive(Debug, Clone)] -pub struct ReasoningResult { - pub verdict: ReasoningVerdict, - pub evidence: Vec, - pub confidence: f32, -} - -/// The in-memory graph used during unit tests. -/// -/// Seeds accumulate nodes and edges. Activation queries search over them -/// using simple substring/keyword matching (a proxy for real embedding search). -pub struct TestGraph { - nodes: HashMap, - edges: Vec, -} - -impl TestGraph { - /// Create an empty graph (unit test mode — pure in-memory). - pub fn new() -> Self { - Self { - nodes: HashMap::new(), - edges: Vec::new(), - } - } - - /// Seed a node into the graph. Returns the node's ID. - pub fn seed_node( - &mut self, - node_type: &str, - content: &str, - importance: f32, - tier: Option<&str>, - ) -> NodeId { - let id = NodeId::new(); - let node = GraphNode { - id: id.clone(), - node_type: node_type.to_string(), - content: content.to_string(), - importance, - tier: tier.map(String::from), - }; - self.nodes.insert(id.clone(), node); - id - } - - /// Seed a directed edge between two nodes. - pub fn seed_edge(&mut self, from: NodeId, to: NodeId, relation: &str, weight: f32) { - self.edges.push(GraphEdge { - from, - to, - relation: relation.to_string(), - weight, - }); - } - - /// Activate — query nodes by type and keyword relevance. - /// - /// Matches nodes whose `node_type` equals `node_type` (case-insensitive) - /// and whose content contains any keyword from the query. - /// Results are sorted by importance descending. - pub fn activate(&self, query: &str, node_type: Option<&str>) -> Vec { - let query_lower = query.to_lowercase(); - let keywords: Vec<&str> = query_lower.split_whitespace().collect(); - - let mut results: Vec = self - .nodes - .values() - .filter(|node| { - // Type filter - if let Some(ty) = node_type { - if !node.node_type.eq_ignore_ascii_case(ty) { - return false; - } - } - // If no query keywords, match all nodes of that type - if keywords.is_empty() { - return true; - } - // Keyword relevance: content must contain at least one keyword - let content_lower = node.content.to_lowercase(); - keywords.iter().any(|kw| content_lower.contains(kw)) - }) - .map(|node| ActivatedNode { - id: node.id.clone(), - node_type: node.node_type.clone(), - content: node.content.clone(), - importance: node.importance, - }) - .collect(); - - results.sort_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal)); - results - } - - /// Reason — evaluate a hypothesis against the seeded graph. - /// - /// If the graph is empty, returns `Insufficient`. - /// If matching nodes exist, returns `Supported` with those nodes. - pub fn reason(&self, hypothesis: &str) -> ReasoningResult { - if self.nodes.is_empty() { - return ReasoningResult { - verdict: ReasoningVerdict::Insufficient, - evidence: vec![], - confidence: 0.0, - }; - } - - let evidence = self.activate(hypothesis, None); - if evidence.is_empty() { - ReasoningResult { - verdict: ReasoningVerdict::Insufficient, - evidence: vec![], - confidence: 0.0, - } - } else { - let confidence = evidence.iter().map(|n| n.importance).sum::() - / evidence.len() as f32; - ReasoningResult { - verdict: ReasoningVerdict::Supported, - evidence, - confidence, - } - } - } - - /// Number of seeded nodes. - pub fn node_count(&self) -> usize { - self.nodes.len() - } - - /// Number of seeded edges. - pub fn edge_count(&self) -> usize { - self.edges.len() - } -} - -impl Default for TestGraph { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_seed_node_returns_id() { - let mut g = TestGraph::new(); - let id = g.seed_node("Customer", "Will Anderson", 0.9, Some("Semantic")); - assert!(!id.0.is_empty()); - } - - #[test] - fn test_activate_returns_matching_nodes() { - let mut g = TestGraph::new(); - g.seed_node("Customer", "Will Anderson, founding member", 0.9, None); - g.seed_node("Customer", "Jane Smith, standard client", 0.6, None); - let results = g.activate("founding", Some("Customer")); - assert_eq!(results.len(), 1); - assert!(results[0].content.contains("Will Anderson")); - } - - #[test] - fn test_activate_empty_query_matches_all_of_type() { - let mut g = TestGraph::new(); - g.seed_node("Customer", "Alice", 0.9, None); - g.seed_node("Customer", "Bob", 0.8, None); - g.seed_node("Order", "Order #1", 0.7, None); - let results = g.activate("", Some("Customer")); - assert_eq!(results.len(), 2); - } - - #[test] - fn test_activate_wrong_type_returns_empty() { - let mut g = TestGraph::new(); - g.seed_node("Customer", "Will Anderson", 0.9, None); - let results = g.activate("Will", Some("Order")); - assert!(results.is_empty()); - } - - #[test] - fn test_activate_sorts_by_importance() { - let mut g = TestGraph::new(); - g.seed_node("Item", "low importance apple", 0.3, None); - g.seed_node("Item", "high importance apple", 0.9, None); - g.seed_node("Item", "medium importance apple", 0.6, None); - let results = g.activate("apple", Some("Item")); - assert_eq!(results.len(), 3); - assert!(results[0].importance >= results[1].importance); - assert!(results[1].importance >= results[2].importance); - } - - #[test] - fn test_activate_no_type_filter() { - let mut g = TestGraph::new(); - g.seed_node("Customer", "Will Anderson", 0.9, None); - g.seed_node("Order", "Will's order", 0.7, None); - let results = g.activate("Will", None); - assert_eq!(results.len(), 2); - } - - #[test] - fn test_reason_empty_graph_returns_insufficient() { - let g = TestGraph::new(); - let result = g.reason("Is there a customer?"); - assert_eq!(result.verdict, ReasoningVerdict::Insufficient); - assert_eq!(result.confidence, 0.0); - } - - #[test] - fn test_reason_with_matching_nodes_returns_supported() { - let mut g = TestGraph::new(); - g.seed_node("Customer", "Will Anderson, founding member", 0.9, None); - let result = g.reason("founding member"); - assert_eq!(result.verdict, ReasoningVerdict::Supported); - assert!(!result.evidence.is_empty()); - } - - #[test] - fn test_reason_no_match_returns_insufficient() { - let mut g = TestGraph::new(); - g.seed_node("Customer", "Will Anderson", 0.9, None); - let result = g.reason("quantum teleportation"); - assert_eq!(result.verdict, ReasoningVerdict::Insufficient); - } - - #[test] - fn test_seed_edge() { - let mut g = TestGraph::new(); - let cust = g.seed_node("Customer", "Will", 0.9, None); - let order = g.seed_node("Order", "Order #1", 0.7, None); - g.seed_edge(cust, order, "Purchased", 0.9); - assert_eq!(g.edge_count(), 1); - } - - #[test] - fn test_node_count() { - let mut g = TestGraph::new(); - assert_eq!(g.node_count(), 0); - g.seed_node("X", "content", 0.5, None); - assert_eq!(g.node_count(), 1); - g.seed_node("Y", "more content", 0.5, None); - assert_eq!(g.node_count(), 2); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-test/src/lib.rs b/_archive/rust-bootstrap/engrams/el-test/src/lib.rs deleted file mode 100644 index c4724e3..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! el-test — Engram language unified testing framework. -//! -//! # Core insight -//! -//! All Engram state is graph nodes. A test seeds the graph and makes -//! assertions. Unit test = seed a few nodes in-memory. E2e test = point at -//! the real Engram database. **The test code is identical. Only the graph -//! differs.** No mocking framework. No dependency injection. One syntax. -//! -//! # Usage -//! -//! ```rust,ignore -//! use el_test::{TestRunner, TestReport}; -//! -//! let source = std::fs::read_to_string("tests.el").unwrap(); -//! let tests = el_test::discover(&source).unwrap(); -//! let runner = TestRunner::new(); -//! let report = runner.run_all(&tests, None); -//! report.print(); -//! ``` - -mod discovery; -mod eval; -mod graph; -mod report; -mod runner; -mod types; - -pub use discovery::discover; -pub use graph::TestGraph; -pub use report::TestReport; -pub use runner::TestRunner; -pub use types::{AssertionResult, TestCase, TestResult, TestStatus, TestTarget}; diff --git a/_archive/rust-bootstrap/engrams/el-test/src/report.rs b/_archive/rust-bootstrap/engrams/el-test/src/report.rs deleted file mode 100644 index 759b2a2..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/src/report.rs +++ /dev/null @@ -1,319 +0,0 @@ -//! Test report formatting — human-readable, JSON, and JUnit XML output. - -use crate::types::{TestResult, TestStatus}; - -/// Aggregated report for a set of test runs. -pub struct TestReport { - pub total: u32, - pub passed: u32, - pub failed: u32, - pub skipped: u32, - pub errors: u32, - pub duration_ms: u64, - pub results: Vec, -} - -impl TestReport { - /// Build a report from a slice of individual test results. - pub fn from_results(results: Vec) -> Self { - let total = results.len() as u32; - let passed = results.iter().filter(|r| r.status == TestStatus::Pass).count() as u32; - let failed = results.iter().filter(|r| r.status == TestStatus::Fail).count() as u32; - let skipped = results.iter().filter(|r| r.status == TestStatus::Skip).count() as u32; - let errors = results.iter().filter(|r| r.status == TestStatus::Error).count() as u32; - let duration_ms = results.iter().map(|r| r.duration_ms).sum(); - Self { total, passed, failed, skipped, errors, duration_ms, results } - } - - /// Print a human-readable summary to stdout. - pub fn print(&self) { - let target_label = format!("({}ms total)", self.duration_ms); - - println!("\nRunning {} tests...\n", self.total); - - for r in &self.results { - let icon = match r.status { - TestStatus::Pass => " ok ", - TestStatus::Fail => " FAIL ", - TestStatus::Skip => " SKIP ", - TestStatus::Error => "ERROR ", - }; - println!(" [{icon}] {} ({}ms)", r.name, r.duration_ms); - - // Show failing assertions - if r.status == TestStatus::Fail { - for (i, a) in r.assertions.iter().enumerate() { - if !a.passed { - println!(" assert {}", a.expression); - if let Some(actual) = &a.actual { - println!(" actual: {actual}"); - } - if let Some(expected) = &a.expected { - println!(" expected: {expected}"); - } - println!(" at assertion {}", i + 1); - } - } - } - if let Some(err) = &r.error { - println!(" error: {err}"); - } - } - - println!("\nResults: {} passed, {} failed, {} skipped {}", self.passed, self.failed, self.skipped, target_label); - if self.errors > 0 { - println!(" ({} error(s) — see above)", self.errors); - } - } - - /// Serialize to JSON. - pub fn to_json(&self) -> String { - let results: Vec = self - .results - .iter() - .map(|r| { - let assertions: Vec = r - .assertions - .iter() - .map(|a| { - serde_json::json!({ - "expression": a.expression, - "passed": a.passed, - "actual": a.actual, - "expected": a.expected, - }) - }) - .collect(); - serde_json::json!({ - "name": r.name, - "target": r.target.to_string(), - "status": r.status.to_string(), - "duration_ms": r.duration_ms, - "assertions": assertions, - "error": r.error, - }) - }) - .collect(); - - let report = serde_json::json!({ - "total": self.total, - "passed": self.passed, - "failed": self.failed, - "skipped": self.skipped, - "errors": self.errors, - "duration_ms": self.duration_ms, - "results": results, - }); - - serde_json::to_string_pretty(&report).unwrap_or_else(|_| "{}".to_string()) - } - - /// Serialize to JUnit XML (for CI integration). - pub fn to_junit_xml(&self) -> String { - let mut xml = String::new(); - xml.push_str("\n"); - xml.push_str(&format!( - "\n", - self.total, - self.failed, - self.errors, - self.skipped, - self.duration_ms as f64 / 1000.0, - )); - - for r in &self.results { - let classname = "el_test"; - let time = r.duration_ms as f64 / 1000.0; - let name_escaped = xml_escape(&r.name); - - match r.status { - TestStatus::Pass => { - xml.push_str(&format!( - " \n" - )); - } - TestStatus::Fail => { - xml.push_str(&format!( - " \n" - )); - for a in r.assertions.iter().filter(|a| !a.passed) { - let msg = xml_escape(&a.expression); - let details = match (&a.actual, &a.expected) { - (Some(act), Some(exp)) => format!("actual: {act}, expected: {exp}"), - (Some(act), None) => format!("actual: {act}"), - _ => "assertion failed".to_string(), - }; - let details_esc = xml_escape(&details); - xml.push_str(&format!( - " {details_esc}\n" - )); - } - xml.push_str(" \n"); - } - TestStatus::Skip => { - xml.push_str(&format!( - " \n \n \n" - )); - } - TestStatus::Error => { - let err_msg = xml_escape(r.error.as_deref().unwrap_or("unknown error")); - xml.push_str(&format!( - " \n \n \n" - )); - } - } - } - - xml.push_str("\n"); - xml - } - - /// Whether the overall test run passed (no failures or errors). - pub fn is_pass(&self) -> bool { - self.failed == 0 && self.errors == 0 - } -} - -fn xml_escape(s: &str) -> String { - s.replace('&', "&") - .replace('<', "<") - .replace('>', ">") - .replace('"', """) - .replace('\'', "'") -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::{AssertionResult, TestTarget}; - - fn make_pass(name: &str) -> TestResult { - TestResult { - name: name.to_string(), - target: TestTarget::Unit, - status: TestStatus::Pass, - duration_ms: 5, - assertions: vec![AssertionResult { - expression: "x == 42".into(), - passed: true, - actual: Some("true".into()), - expected: None, - }], - error: None, - } - } - - fn make_fail(name: &str) -> TestResult { - TestResult { - name: name.to_string(), - target: TestTarget::Unit, - status: TestStatus::Fail, - duration_ms: 3, - assertions: vec![AssertionResult { - expression: "x == 99".into(), - passed: false, - actual: Some("42".into()), - expected: Some("99".into()), - }], - error: None, - } - } - - fn make_skip(name: &str) -> TestResult { - TestResult { - name: name.to_string(), - target: TestTarget::E2e, - status: TestStatus::Skip, - duration_ms: 0, - assertions: vec![], - error: Some("ENGRAM_URL not set".into()), - } - } - - #[test] - fn test_report_counts() { - let report = TestReport::from_results(vec![ - make_pass("a"), - make_fail("b"), - make_skip("c"), - ]); - assert_eq!(report.total, 3); - assert_eq!(report.passed, 1); - assert_eq!(report.failed, 1); - assert_eq!(report.skipped, 1); - } - - #[test] - fn test_report_is_pass() { - let report = TestReport::from_results(vec![make_pass("a"), make_pass("b")]); - assert!(report.is_pass()); - } - - #[test] - fn test_report_is_not_pass_on_fail() { - let report = TestReport::from_results(vec![make_pass("a"), make_fail("b")]); - assert!(!report.is_pass()); - } - - #[test] - fn test_to_json_valid() { - let report = TestReport::from_results(vec![make_pass("test")]); - let json = report.to_json(); - let parsed: serde_json::Value = serde_json::from_str(&json).expect("valid JSON"); - assert_eq!(parsed["total"], 1); - assert_eq!(parsed["passed"], 1); - } - - #[test] - fn test_to_json_contains_results() { - let report = TestReport::from_results(vec![make_pass("hello")]); - let json = report.to_json(); - assert!(json.contains("hello")); - } - - #[test] - fn test_to_junit_xml_valid() { - let report = TestReport::from_results(vec![make_pass("a"), make_fail("b")]); - let xml = report.to_junit_xml(); - assert!(xml.starts_with("")); - } - - #[test] - fn test_to_junit_xml_pass_testcase() { - let report = TestReport::from_results(vec![make_pass("arithmetic")]); - let xml = report.to_junit_xml(); - assert!(xml.contains("arithmetic")); - assert!(!xml.contains(" d \"e\" 'f'"); - assert!(escaped.contains("<")); - assert!(escaped.contains("&")); - assert!(escaped.contains(">")); - assert!(escaped.contains(""")); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-test/src/runner.rs b/_archive/rust-bootstrap/engrams/el-test/src/runner.rs deleted file mode 100644 index b99d4d7..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/src/runner.rs +++ /dev/null @@ -1,382 +0,0 @@ -//! Test runner — executes test cases and produces results. - -use std::time::Instant; - -use el_parser::{SeedStmt, Stmt}; - -use crate::eval::{evaluate_assert, expr_to_text, Evaluator}; -use crate::graph::TestGraph; -use crate::types::{AssertionResult, TestCase, TestResult, TestStatus, TestTarget}; - -/// Runs test cases and collects results. -pub struct TestRunner; - -impl TestRunner { - pub fn new() -> Self { - Self - } - - /// Run all tests. E2e tests are skipped if `engram_url` is `None`. - pub fn run_all(&self, tests: &[TestCase], engram_url: Option<&str>) -> Vec { - let mut results = Vec::new(); - for test in tests { - match &test.target { - TestTarget::Unit => { - results.push(self.run_unit_test(test)); - } - TestTarget::E2e => { - if let Some(url) = engram_url { - results.push(self.run_e2e_test(test, url)); - } else { - results.push(TestResult { - name: test.name.clone(), - target: TestTarget::E2e, - status: TestStatus::Skip, - duration_ms: 0, - assertions: vec![], - error: Some("ENGRAM_URL not set; skipping e2e test".into()), - }); - } - } - TestTarget::Both => { - results.push(self.run_unit_test(test)); - if let Some(url) = engram_url { - results.push(self.run_e2e_test(test, url)); - } else { - results.push(TestResult { - name: format!("{} (e2e)", test.name), - target: TestTarget::E2e, - status: TestStatus::Skip, - duration_ms: 0, - assertions: vec![], - error: Some("ENGRAM_URL not set; skipping e2e test".into()), - }); - } - } - } - } - results - } - - /// Run only unit tests. - pub fn run_unit(&self, tests: &[TestCase]) -> Vec { - tests - .iter() - .filter(|t| matches!(t.target, TestTarget::Unit | TestTarget::Both)) - .map(|t| self.run_unit_test(t)) - .collect() - } - - /// Run only e2e tests. - pub fn run_e2e(&self, tests: &[TestCase], engram_url: &str) -> Vec { - tests - .iter() - .filter(|t| matches!(t.target, TestTarget::E2e | TestTarget::Both)) - .map(|t| self.run_e2e_test(t, engram_url)) - .collect() - } - - /// Run a single test against an in-memory graph. - pub fn run_one(&self, test: &TestCase, engram_url: Option<&str>) -> TestResult { - match (&test.target, engram_url) { - (TestTarget::E2e, Some(url)) => self.run_e2e_test(test, url), - (TestTarget::E2e, None) => TestResult { - name: test.name.clone(), - target: TestTarget::E2e, - status: TestStatus::Skip, - duration_ms: 0, - assertions: vec![], - error: Some("ENGRAM_URL not set; skipping e2e test".into()), - }, - _ => self.run_unit_test(test), - } - } - - // ── Private ─────────────────────────────────────────────────────────────── - - fn run_unit_test(&self, test: &TestCase) -> TestResult { - let start = Instant::now(); - let mut graph = TestGraph::new(); - - // Apply seed statements first - for stmt in &test.body { - if let Stmt::Seed(seed, _) = stmt { - apply_seed(&mut graph, seed); - } - } - - self.execute_test(test, &graph, TestTarget::Unit, start) - } - - fn run_e2e_test(&self, test: &TestCase, _engram_url: &str) -> TestResult { - let start = Instant::now(); - // For e2e, we still use an in-memory graph for now (real DB client TBD). - // The distinction is that e2e tests skip the seed step (they use real data). - let graph = TestGraph::new(); - self.execute_test(test, &graph, TestTarget::E2e, start) - } - - fn execute_test( - &self, - test: &TestCase, - graph: &TestGraph, - target: TestTarget, - start: Instant, - ) -> TestResult { - let mut eval = Evaluator::new(graph); - let mut assertions: Vec = Vec::new(); - let mut error: Option = None; - - for stmt in &test.body { - match stmt { - Stmt::Seed(..) => { - // Already processed before eval - } - Stmt::Assert(expr, _) => { - let text = expr_to_text(expr); - let result = evaluate_assert(&mut eval, expr, &text); - assertions.push(result); - } - other => { - if let Err(e) = eval.exec_stmt(other) { - error = Some(e); - break; - } - } - } - } - - let duration_ms = start.elapsed().as_millis() as u64; - let all_passed = assertions.iter().all(|a| a.passed); - let status = if error.is_some() { - TestStatus::Error - } else if all_passed { - TestStatus::Pass - } else { - TestStatus::Fail - }; - - TestResult { - name: test.name.clone(), - target, - status, - duration_ms, - assertions, - error, - } - } -} - -impl Default for TestRunner { - fn default() -> Self { - Self::new() - } -} - -fn apply_seed(graph: &mut TestGraph, seed: &SeedStmt) { - match seed { - SeedStmt::Node { node_type, content, importance, tier } => { - graph.seed_node(node_type, content, *importance, tier.as_deref()); - } - SeedStmt::Edge { from, to, relation, weight } => { - // For edges we use string IDs — in a real impl these would be looked - // up from the graph's node registry. We create placeholder node IDs. - use crate::graph::NodeId; - graph.seed_edge( - NodeId(from.clone()), - NodeId(to.clone()), - relation, - *weight, - ); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use el_parser::{Expr, Literal, BinOp, Stmt}; - use el_lexer::Span; - - fn dummy_span() -> Span { - Span::new(0, 0, 1, 1) - } - - fn make_assert(expr: Expr) -> Stmt { - Stmt::Assert(expr, dummy_span()) - } - - fn make_let(name: &str, expr: Expr) -> Stmt { - Stmt::Let { - name: name.to_string(), - type_ann: None, - value: expr, - span: dummy_span(), - } - } - - fn int_lit(n: i64) -> Expr { - Expr::Literal(Literal::Int(n)) - } - - fn str_lit(s: &str) -> Expr { - Expr::Literal(Literal::Str(s.to_string())) - } - - fn bool_lit(b: bool) -> Expr { - Expr::Literal(Literal::Bool(b)) - } - - fn binop(op: BinOp, l: Expr, r: Expr) -> Expr { - Expr::BinOp { op, left: Box::new(l), right: Box::new(r) } - } - - fn test_case(name: &str, body: Vec) -> TestCase { - TestCase { - name: name.to_string(), - target: TestTarget::Unit, - body, - } - } - - #[test] - fn test_runner_pass() { - let runner = TestRunner::new(); - let tc = test_case("arithmetic", vec![ - make_let("x", int_lit(6)), - make_let("y", int_lit(7)), - make_let("result", binop(BinOp::Mul, Expr::Ident("x".into()), Expr::Ident("y".into()))), - make_assert(binop(BinOp::Eq, Expr::Ident("result".into()), int_lit(42))), - ]); - let result = runner.run_one(&tc, None); - assert_eq!(result.status, TestStatus::Pass); - assert!(result.assertions[0].passed); - } - - #[test] - fn test_runner_fail() { - let runner = TestRunner::new(); - let tc = test_case("failing", vec![ - make_assert(binop(BinOp::Eq, int_lit(1), int_lit(2))), - ]); - let result = runner.run_one(&tc, None); - assert_eq!(result.status, TestStatus::Fail); - assert!(!result.assertions[0].passed); - } - - #[test] - fn test_runner_multiple_assertions_partial_fail() { - let runner = TestRunner::new(); - let tc = test_case("partial", vec![ - make_assert(bool_lit(true)), - make_assert(binop(BinOp::Eq, int_lit(1), int_lit(2))), // fails - make_assert(bool_lit(true)), - ]); - let result = runner.run_one(&tc, None); - assert_eq!(result.status, TestStatus::Fail); - assert!(result.assertions[0].passed); - assert!(!result.assertions[1].passed); - assert!(result.assertions[2].passed); - } - - #[test] - fn test_runner_e2e_skip_without_url() { - let runner = TestRunner::new(); - let tc = TestCase { - name: "e2e test".to_string(), - target: TestTarget::E2e, - body: vec![], - }; - let result = runner.run_one(&tc, None); - assert_eq!(result.status, TestStatus::Skip); - } - - #[test] - fn test_runner_run_all_skips_e2e() { - let runner = TestRunner::new(); - let tests = vec![ - TestCase { name: "unit".into(), target: TestTarget::Unit, body: vec![] }, - TestCase { name: "e2e".into(), target: TestTarget::E2e, body: vec![] }, - ]; - let results = runner.run_all(&tests, None); - assert_eq!(results.len(), 2); - assert_eq!(results[0].status, TestStatus::Pass); // empty = pass - assert_eq!(results[1].status, TestStatus::Skip); - } - - #[test] - fn test_runner_run_unit_filters_unit_only() { - let runner = TestRunner::new(); - let tests = vec![ - TestCase { name: "unit".into(), target: TestTarget::Unit, body: vec![] }, - TestCase { name: "e2e".into(), target: TestTarget::E2e, body: vec![] }, - ]; - let results = runner.run_unit(&tests); - assert_eq!(results.len(), 1); - assert_eq!(results[0].name, "unit"); - } - - #[test] - fn test_runner_empty_test_passes() { - let runner = TestRunner::new(); - let tc = test_case("empty", vec![]); - let result = runner.run_one(&tc, None); - assert_eq!(result.status, TestStatus::Pass); - } - - #[test] - fn test_runner_with_seed_and_activate() { - use el_parser::SeedStmt; - let runner = TestRunner::new(); - let seed = Stmt::Seed( - SeedStmt::Node { - node_type: "Customer".into(), - content: "Will Anderson, founding member".into(), - importance: 0.9, - tier: Some("Semantic".into()), - }, - dummy_span(), - ); - // activate Customer where "founding" - let activate = Expr::Activate { - type_name: "Customer".into(), - query: "founding".into(), - }; - let let_results = make_let("results", activate); - // assert results.len() > 0 => assert results.len() > 0 - // We'll call .len() via a Call to Field - let len_call = Expr::Call { - func: Box::new(Expr::Field { - object: Box::new(Expr::Ident("results".into())), - field: "len".into(), - }), - args: vec![], - }; - let assert_len = make_assert(binop(BinOp::Gt, len_call, int_lit(0))); - let tc = test_case("seed and activate", vec![seed, let_results, assert_len]); - let result = runner.run_one(&tc, None); - assert_eq!(result.status, TestStatus::Pass); - } - - #[test] - fn test_runner_empty_graph_activate_returns_empty() { - let runner = TestRunner::new(); - // No seeds — activate should return empty list - let activate = Expr::Activate { - type_name: "Customer".into(), - query: "anything".into(), - }; - let let_results = make_let("results", activate); - let len_call = Expr::Call { - func: Box::new(Expr::Field { - object: Box::new(Expr::Ident("results".into())), - field: "len".into(), - }), - args: vec![], - }; - let assert_zero = make_assert(binop(BinOp::Eq, len_call, int_lit(0))); - let tc = test_case("empty graph", vec![let_results, assert_zero]); - let result = runner.run_one(&tc, None); - assert_eq!(result.status, TestStatus::Pass); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-test/src/types.rs b/_archive/rust-bootstrap/engrams/el-test/src/types.rs deleted file mode 100644 index 73ae06f..0000000 --- a/_archive/rust-bootstrap/engrams/el-test/src/types.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Core data types for the testing framework. - -use el_parser::Stmt; - -/// Which graph a test should execute against. -#[derive(Debug, Clone, PartialEq)] -pub enum TestTarget { - /// In-memory graph — default, zero external dependencies. - Unit, - /// Real Engram database pointed at by `ENGRAM_URL` / `ENGRAM_DB_PATH`. - E2e, - /// Run against both unit (in-memory) and e2e (real DB). - Both, -} - -impl std::fmt::Display for TestTarget { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TestTarget::Unit => write!(f, "unit"), - TestTarget::E2e => write!(f, "e2e"), - TestTarget::Both => write!(f, "both"), - } - } -} - -impl From for TestTarget { - fn from(t: el_parser::TestTarget) -> Self { - match t { - el_parser::TestTarget::Unit => TestTarget::Unit, - el_parser::TestTarget::E2e => TestTarget::E2e, - el_parser::TestTarget::Both => TestTarget::Both, - } - } -} - -/// A single test case extracted from an `.el` source file. -pub struct TestCase { - pub name: String, - pub target: TestTarget, - /// The body statements of the test block. - pub body: Vec, -} - -/// Status of a single test run. -#[derive(Debug, Clone, PartialEq)] -pub enum TestStatus { - Pass, - Fail, - /// Skipped because the required target is not available. - Skip, - /// Unexpected runtime error (not an assertion failure). - Error, -} - -impl std::fmt::Display for TestStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TestStatus::Pass => write!(f, "pass"), - TestStatus::Fail => write!(f, "fail"), - TestStatus::Skip => write!(f, "skip"), - TestStatus::Error => write!(f, "error"), - } - } -} - -/// Result of evaluating a single `assert` statement. -#[derive(Debug, Clone)] -pub struct AssertionResult { - /// The `assert` expression as source text. - pub expression: String, - pub passed: bool, - pub actual: Option, - pub expected: Option, -} - -/// The full result of one test execution. -pub struct TestResult { - pub name: String, - pub target: TestTarget, - pub status: TestStatus, - pub duration_ms: u64, - pub assertions: Vec, - pub error: Option, -} diff --git a/_archive/rust-bootstrap/engrams/el-types/Cargo.toml b/_archive/rust-bootstrap/engrams/el-types/Cargo.toml deleted file mode 100644 index 0ab4745..0000000 --- a/_archive/rust-bootstrap/engrams/el-types/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "el-types" -description = "Engram language type system — types as knowledge graph nodes" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-lexer = { workspace = true } -el-parser = { workspace = true } -thiserror = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-types/src/checker.rs b/_archive/rust-bootstrap/engrams/el-types/src/checker.rs deleted file mode 100644 index 56cfa95..0000000 --- a/_archive/rust-bootstrap/engrams/el-types/src/checker.rs +++ /dev/null @@ -1,758 +0,0 @@ -//! Type checker — walks the AST and infers / verifies types. - -use el_parser::{BinOp, Expr, Literal, Program, Stmt}; - -use crate::error::{TypeError, TypeErrorKind}; -use crate::types::{EnumVariant, ProtocolMethodSig, Type, TypeDef, TypeEnv}; - -/// Diagnostics produced by the type checker. -#[derive(Debug, Clone)] -pub struct Diagnostic { - pub message: std::string::String, - pub is_error: bool, -} - -/// Entry point: type-check a parsed program. -pub struct TypeChecker { - pub env: TypeEnv, - pub diagnostics: Vec, -} - -impl TypeChecker { - pub fn new(env: TypeEnv) -> Self { - Self { env, diagnostics: Vec::new() } - } - - pub fn with_builtins() -> Self { - Self::new(TypeEnv::with_builtins()) - } - - // ── Public API ──────────────────────────────────────────────────────────── - - pub fn check(&mut self, program: &Program) -> &[Diagnostic] { - self.hoist_definitions(program); - for stmt in &program.stmts { - self.check_stmt(stmt); - } - &self.diagnostics - } - - pub fn ok(&self) -> bool { - !self.diagnostics.iter().any(|d| d.is_error) - } - - // ── Definition hoisting ─────────────────────────────────────────────────── - - fn hoist_definitions(&mut self, program: &Program) { - for stmt in &program.stmts { - self.hoist_stmt(stmt); - } - } - - fn hoist_stmt(&mut self, stmt: &Stmt) { - match stmt { - Stmt::TypeDef { name, fields, .. } => { - let resolved_fields: Vec<_> = fields.iter().filter_map(|f| { - match self.env.resolve_type_expr(&f.type_ann) { - Ok(ty) => Some((f.name.clone(), ty)), - Err(e) => { self.error(e); None } - } - }).collect(); - let def = TypeDef::Struct { name: name.clone(), fields: resolved_fields }; - self.env.register_type(name.clone(), def, ""); - } - Stmt::EnumDef { name, variants, .. } => { - let resolved_variants: Vec<_> = variants.iter().filter_map(|v| { - let payload = if let Some(pt) = &v.payload { - match self.env.resolve_type_expr(pt) { - Ok(ty) => Some(ty), - Err(e) => { self.error(e); return None; } - } - } else { None }; - Some(EnumVariant { name: v.name.clone(), payload }) - }).collect(); - let def = TypeDef::Enum { name: name.clone(), variants: resolved_variants }; - self.env.register_type(name.clone(), def, ""); - } - Stmt::FnDef { name, params, return_type, .. } => { - let param_types: Vec<_> = params.iter().filter_map(|p| { - self.env.resolve_type_expr(&p.type_ann).ok() - }).collect(); - if let Ok(ret) = self.env.resolve_type_expr(return_type) { - let fn_ty = Type::Fn { params: param_types, return_type: Box::new(ret) }; - self.env.register_fn(name.clone(), fn_ty); - } - } - Stmt::ProtocolDef { name, methods, .. } => { - let sigs: Vec<_> = methods.iter().filter_map(|m| { - let pt: Vec<_> = m.params.iter().filter_map(|p| { - self.env.resolve_type_expr(&p.type_ann).ok() - }).collect(); - if let Ok(ret) = self.env.resolve_type_expr(&m.return_type) { - Some(ProtocolMethodSig { name: m.name.clone(), params: pt, return_type: ret }) - } else { - None - } - }).collect(); - self.env.register_protocol(name.clone(), sigs); - } - Stmt::ImplDef { protocol_name, type_name, methods, .. } => { - for m in methods { - if let Stmt::FnDef { name, params, return_type, .. } = m { - let pt: Vec<_> = params.iter().filter_map(|p| { - self.env.resolve_type_expr(&p.type_ann).ok() - }).collect(); - if let Ok(ret) = self.env.resolve_type_expr(return_type) { - self.env.register_fn(name.clone(), Type::Fn { params: pt, return_type: Box::new(ret) }); - } - } - } - self.env.register_impl(protocol_name.clone(), type_name.clone()); - } - _ => {} - } - } - - // ── Statement checking ──────────────────────────────────────────────────── - - fn check_stmt(&mut self, stmt: &Stmt) { - match stmt { - Stmt::Let { name, type_ann, value, .. } => { - let inferred = self.infer_expr(value); - if let Some(ann) = type_ann { - match self.env.resolve_type_expr(ann) { - Ok(declared) => { - if !self.env.check_compatible(&inferred, &declared) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: declared.to_string(), - got: inferred.to_string(), - }); - } - self.env.bind(name.clone(), declared); - } - Err(e) => { - self.error(e); - self.env.bind(name.clone(), inferred); - } - } - } else { - self.env.bind(name.clone(), inferred); - } - } - Stmt::Return(expr, _) => { self.infer_expr(expr); } - Stmt::Expr(expr, _) => { self.infer_expr(expr); } - Stmt::FnDef { name, params, return_type, body, .. } => { - let mut inner_env = self.env.clone(); - for param in params { - if let Ok(ty) = inner_env.resolve_type_expr(¶m.type_ann) { - inner_env.bind(param.name.clone(), ty); - } - } - let mut inner_checker = TypeChecker::new(inner_env); - inner_checker.hoist_definitions_stmts(body); - for s in body { - inner_checker.check_stmt(s); - } - self.diagnostics.extend(inner_checker.diagnostics); - let param_types: Vec<_> = params.iter().filter_map(|p| { - self.env.resolve_type_expr(&p.type_ann).ok() - }).collect(); - if let Ok(ret) = self.env.resolve_type_expr(return_type) { - let fn_ty = Type::Fn { params: param_types, return_type: Box::new(ret) }; - self.env.register_fn(name.clone(), fn_ty); - } - } - Stmt::TypeDef { .. } | Stmt::EnumDef { .. } => {} - Stmt::ProtocolDef { .. } => {} - Stmt::ImplDef { protocol_name, type_name, methods, .. } => { - let method_names: Vec = methods.iter().filter_map(|m| { - if let Stmt::FnDef { name, .. } = m { Some(name.clone()) } else { None } - }).collect(); - let missing = self.env.check_impl_completeness(protocol_name, &method_names); - for m in &missing { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: format!("impl method '{m}' for protocol '{protocol_name}'"), - got: format!("missing in impl for '{type_name}'"), - }); - } - for m in methods { self.check_stmt(m); } - } - Stmt::Import { .. } => {} - Stmt::TestDef { body, .. } => { - for s in body { self.check_stmt(s); } - } - Stmt::Seed(_, _) => {} - Stmt::Assert(expr, _) => { - let ty = self.infer_expr(expr); - if !self.env.check_compatible(&ty, &Type::Bool) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: "Bool".into(), - got: ty.to_string(), - }); - } - } - Stmt::Retry { body, fallback, .. } => { - for s in body { self.check_stmt(s); } - if let Some(fb) = fallback { - for s in fb { self.check_stmt(s); } - } - } - Stmt::Deploy { .. } => {} - Stmt::While { condition, body, .. } => { - self.infer_expr(condition); - for s in body { self.check_stmt(s); } - } - Stmt::ComponentDef { props, state, methods, template, .. } => { - // Type-check component: props, state, methods, and template. - for field in props.iter().chain(state.iter()) { - if let Some(default) = &field.default { - self.infer_expr(default); - } - } - for m in methods { self.check_stmt(m); } - self.infer_expr(template); - } - } - } - - fn hoist_definitions_stmts(&mut self, stmts: &[Stmt]) { - for stmt in stmts { - match stmt { - Stmt::TypeDef { name, fields, .. } => { - let resolved: Vec<_> = fields.iter().filter_map(|f| { - self.env.resolve_type_expr(&f.type_ann).ok().map(|ty| (f.name.clone(), ty)) - }).collect(); - let def = TypeDef::Struct { name: name.clone(), fields: resolved }; - self.env.register_type(name.clone(), def, ""); - } - Stmt::FnDef { name, params, return_type, .. } => { - let pt: Vec<_> = params.iter().filter_map(|p| { - self.env.resolve_type_expr(&p.type_ann).ok() - }).collect(); - if let Ok(ret) = self.env.resolve_type_expr(return_type) { - self.env.register_fn(name.clone(), Type::Fn { params: pt, return_type: Box::new(ret) }); - } - } - _ => {} - } - } - } - - // ── Expression inference ────────────────────────────────────────────────── - - pub fn infer_expr(&mut self, expr: &Expr) -> Type { - match expr { - Expr::Literal(lit) => self.infer_literal(lit), - Expr::Ident(name) => { - if let Some(ty) = self.env.lookup(name) { - ty.clone() - } else if let Some(ty) = self.env.lookup_fn(name) { - ty.clone() - } else { - self.emit_error(TypeErrorKind::UndefinedVariable(name.clone())); - Type::Unknown - } - } - Expr::BinOp { op, left, right } => self.infer_binop(op, left, right), - Expr::UnaryNot(inner) => { - let ty = self.infer_expr(inner); - if !self.env.check_compatible(&ty, &Type::Bool) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: "Bool".into(), - got: ty.to_string(), - }); - } - Type::Bool - } - Expr::Call { func, args } => self.infer_call(func, args), - Expr::Block(stmts) => { - let mut last = Type::Void; - let mut inner = TypeChecker::new(self.env.clone()); - inner.hoist_definitions_stmts(stmts); - for (i, s) in stmts.iter().enumerate() { - if i == stmts.len() - 1 { - if let Stmt::Expr(e, _) = s { - last = inner.infer_expr(e); - continue; - } - } - inner.check_stmt(s); - } - self.diagnostics.extend(inner.diagnostics); - last - } - Expr::Match { subject, arms } => { - self.infer_expr(subject); - let mut result = Type::Unknown; - for arm in arms { - let arm_ty = self.infer_expr(&arm.body); - if matches!(result, Type::Unknown) { - result = arm_ty; - } - } - result - } - Expr::Activate { type_name, .. } => { - if self.env.get_type(type_name).is_none() { - self.emit_error(TypeErrorKind::ActivateUnknownType(type_name.clone())); - Type::Unknown - } else { - Type::Array(Box::new(Type::Named(type_name.clone()))) - } - } - Expr::Sealed(stmts) => { - let mut inner = TypeChecker::new(self.env.clone()); - for s in stmts { inner.check_stmt(s); } - self.diagnostics.extend(inner.diagnostics); - Type::Void - } - Expr::If { cond, then, else_ } => { - let cond_ty = self.infer_expr(cond); - if !self.env.check_compatible(&cond_ty, &Type::Bool) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: "Bool".into(), - got: cond_ty.to_string(), - }); - } - let then_ty = self.infer_expr(then); - if let Some(e) = else_ { - let else_ty = self.infer_expr(e); - if self.env.check_compatible(&then_ty, &else_ty) { then_ty } else { Type::Unknown } - } else { - Type::Void - } - } - Expr::Field { object, field } => { - let obj_ty = self.infer_expr(object); - match &obj_ty { - Type::Named(type_name) => { - match self.env.get_type(type_name) { - Some(TypeDef::Struct { fields, .. }) => { - if let Some((_, fty)) = fields.iter().find(|(n, _)| n == field) { - fty.clone() - } else { - self.emit_error(TypeErrorKind::UnknownField { - type_name: type_name.clone(), - field: field.clone(), - }); - Type::Unknown - } - } - _ => { - self.emit_error(TypeErrorKind::UnknownField { - type_name: obj_ty.to_string(), - field: field.clone(), - }); - Type::Unknown - } - } - } - _ => { - self.emit_error(TypeErrorKind::UnknownField { - type_name: obj_ty.to_string(), - field: field.clone(), - }); - Type::Unknown - } - } - } - Expr::Array(elems) => { - if elems.is_empty() { - Type::Array(Box::new(Type::Unknown)) - } else { - let elem_ty = self.infer_expr(&elems[0]); - for e in &elems[1..] { - let ty = self.infer_expr(e); - if !self.env.check_compatible(&ty, &elem_ty) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: elem_ty.to_string(), - got: ty.to_string(), - }); - } - } - Type::Array(Box::new(elem_ty)) - } - } - Expr::Path { segments } => { - if segments.len() >= 2 { - let enum_name = &segments[0]; - if self.env.get_type(enum_name).is_some() { - Type::Named(enum_name.clone()) - } else { - self.emit_error(TypeErrorKind::UndefinedType(enum_name.clone())); - Type::Unknown - } - } else { - Type::Unknown - } - } - Expr::Index { object, index } => { - let obj_ty = self.infer_expr(object); - let idx_ty = self.infer_expr(index); - if !self.env.check_compatible(&idx_ty, &Type::Int) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: "Int".into(), - got: idx_ty.to_string(), - }); - } - match obj_ty { - Type::Array(inner) => *inner, - other => { - self.emit_error(TypeErrorKind::NotIndexable(other.to_string())); - Type::Unknown - } - } - } - Expr::Closure { params, return_type, body, .. } => { - let param_types: Vec<_> = params.iter().filter_map(|p| { - self.env.resolve_type_expr(&p.type_ann).ok() - }).collect(); - let mut inner_env = self.env.clone(); - for p in params { - if let Ok(ty) = inner_env.resolve_type_expr(&p.type_ann) { - inner_env.bind(p.name.clone(), ty); - } - } - let mut inner = TypeChecker::new(inner_env); - let body_ty = inner.infer_expr(body); - self.diagnostics.extend(inner.diagnostics); - let ret_ty = if let Some(ann) = return_type { - self.env.resolve_type_expr(ann).unwrap_or(body_ty) - } else { - body_ty - }; - Type::Fn { params: param_types, return_type: Box::new(ret_ty) } - } - Expr::Try(inner) => { - let ty = self.infer_expr(inner); - match ty { - Type::Result { ok, .. } => *ok, - Type::Unknown => Type::Unknown, - other => { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: "Result".into(), - got: other.to_string(), - }); - Type::Unknown - } - } - } - Expr::MapLiteral(pairs) => { - if pairs.is_empty() { - Type::Map { key: Box::new(Type::Unknown), value: Box::new(Type::Unknown) } - } else { - let key_ty = self.infer_expr(&pairs[0].0); - let val_ty = self.infer_expr(&pairs[0].1); - for (k, v) in &pairs[1..] { - let kt = self.infer_expr(k); - let vt = self.infer_expr(v); - if !self.env.check_compatible(&kt, &key_ty) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: key_ty.to_string(), - got: kt.to_string(), - }); - } - if !self.env.check_compatible(&vt, &val_ty) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: val_ty.to_string(), - got: vt.to_string(), - }); - } - } - Type::Map { key: Box::new(key_ty), value: Box::new(val_ty) } - } - } - Expr::StructLit { type_name, fields, .. } => { - // Look up the type definition - match self.env.get_type(type_name).cloned() { - Some(TypeDef::Struct { fields: declared_fields, .. }) => { - // Check that all provided fields are valid and have compatible types - for (field_name, field_expr) in fields { - let got_ty = self.infer_expr(field_expr); - if let Some((_, expected_ty)) = declared_fields.iter().find(|(n, _)| n == field_name) { - if !self.env.check_compatible(&got_ty, expected_ty) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: expected_ty.to_string(), - got: got_ty.to_string(), - }); - } - } else { - self.emit_error(TypeErrorKind::UnknownField { - type_name: type_name.clone(), - field: field_name.clone(), - }); - } - } - Type::Named(type_name.clone()) - } - Some(_) => { - self.emit_error(TypeErrorKind::UndefinedType( - format!("{type_name} is not a struct type"), - )); - Type::Unknown - } - None => { - self.emit_error(TypeErrorKind::UndefinedType(type_name.clone())); - Type::Unknown - } - } - } - - // Engram-specific expressions - Expr::With { base, .. } => self.infer_expr(base), - Expr::Reason { .. } => Type::String, - Expr::Parallel { .. } => Type::Unknown, - Expr::Trace { .. } => Type::Unknown, - Expr::UnaryBitNot(inner) => { - self.infer_expr(inner); - Type::Int - } - - // JSX expressions — type as Unknown (JSX type-checking not yet implemented). - Expr::JsxElement { children, .. } => { - for child in children { self.infer_expr(child); } - Type::Unknown - } - Expr::JsxExpr(inner) => self.infer_expr(inner), - Expr::JsxText(_) => Type::String, - } - } - - fn infer_literal(&self, lit: &Literal) -> Type { - match lit { - Literal::Int(_) => Type::Int, - Literal::Float(_) => Type::Float, - Literal::Str(_) => Type::String, - Literal::Bool(_) => Type::Bool, - } - } - - fn infer_binop(&mut self, op: &BinOp, left: &Expr, right: &Expr) -> Type { - let lt = self.infer_expr(left); - let rt = self.infer_expr(right); - match op { - BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div => { - match (<, &rt) { - (Type::Float, _) | (_, Type::Float) => Type::Float, - (Type::Int, Type::Int) => Type::Int, - (Type::String, Type::String) if matches!(op, BinOp::Add) => Type::String, - _ => { - if self.env.check_compatible(<, &Type::Int) - && self.env.check_compatible(&rt, &Type::Int) { - Type::Int - } else { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: "numeric or String".into(), - got: format!("{lt} and {rt}"), - }); - Type::Unknown - } - } - } - } - BinOp::Eq | BinOp::NotEq => Type::Bool, - BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq => { - if !self.env.check_compatible(<, &rt) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: lt.to_string(), - got: rt.to_string(), - }); - } - Type::Bool - } - BinOp::And | BinOp::Or => { - for ty in [<, &rt] { - if !self.env.check_compatible(ty, &Type::Bool) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: "Bool".into(), - got: ty.to_string(), - }); - } - } - Type::Bool - } - BinOp::Mod | BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor - | BinOp::Shl | BinOp::Shr => Type::Int, - BinOp::NullCoalesce => { - // `a ?? b` — type is the type of `a` (or `b` if `a` is unknown) - let lt = self.infer_expr(left); - if matches!(lt, Type::Unknown) { self.infer_expr(right) } else { lt } - } - } - } - - fn infer_call(&mut self, func: &Expr, args: &[Expr]) -> Type { - let func_ty = self.infer_expr(func); - let arg_types: Vec<_> = args.iter().map(|a| self.infer_expr(a)).collect(); - - match func_ty { - Type::Fn { params, return_type } => { - if params.len() != arg_types.len() { - self.emit_error(TypeErrorKind::ArgCountMismatch { - expected: params.len(), - got: arg_types.len(), - }); - } else { - for (expected, got) in params.iter().zip(arg_types.iter()) { - if !self.env.check_compatible(got, expected) { - self.emit_error(TypeErrorKind::TypeMismatch { - expected: expected.to_string(), - got: got.to_string(), - }); - } - } - } - *return_type - } - Type::Unknown => Type::Unknown, - other => { - self.emit_error(TypeErrorKind::NotCallable(other.to_string())); - Type::Unknown - } - } - } - - // ── Diagnostic helpers ──────────────────────────────────────────────────── - - fn error(&mut self, e: TypeError) { - self.diagnostics.push(Diagnostic { message: e.to_string(), is_error: true }); - } - - fn emit_error(&mut self, kind: TypeErrorKind) { - self.diagnostics.push(Diagnostic { message: kind.to_string(), is_error: true }); - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use el_lexer::tokenize; - use el_parser::parse; - use super::*; - - fn check(src: &str) -> TypeChecker { - let tokens = tokenize(src).expect("lex"); - let prog = parse(tokens, src.to_string()).expect("parse"); - let mut checker = TypeChecker::with_builtins(); - checker.check(&prog); - checker - } - - fn assert_ok(src: &str) { - let c = check(src); - assert!(c.ok(), "Expected no errors, got: {:?}", c.diagnostics); - } - - fn assert_err(src: &str) { - let c = check(src); - assert!(!c.ok(), "Expected errors but got none"); - } - - #[test] - fn test_let_int() { assert_ok("let x: Int = 42"); } - - #[test] - fn test_let_string() { assert_ok(r#"let s: String = "hello""#); } - - #[test] - fn test_type_mismatch() { assert_err(r#"let x: Int = "not an int""#); } - - #[test] - fn test_fn_def_and_call() { - assert_ok(r#" -fn double(n: Int) -> Int { return n + n } -let result: Int = double(5) -"#); - } - - #[test] - fn test_fn_arg_count_mismatch() { - assert_err(r#" -fn add(a: Int, b: Int) -> Int { return a + b } -add(1) -"#); - } - - #[test] - fn test_type_def_and_field_access() { - assert_ok(r#" -type User { name: String age: Int } -fn make_user() -> User { return make_user() } -"#); - } - - #[test] - fn test_activate_known_type_ok() { - assert_ok(r#" -type User { id: Uuid name: String } -activate User where "recent customers" -"#); - } - - #[test] - fn test_activate_unknown_type_err() { assert_err(r#"activate Phantom where "ghosts""#); } - - #[test] - fn test_bool_ops() { - assert_ok("let a: Bool = true && false"); - assert_ok("let b: Bool = true || false"); - } - - #[test] - fn test_int_arithmetic() { assert_ok("let x: Int = 1 + 2 * 3 - 4 / 2"); } - - #[test] - fn test_string_concat() { assert_ok(r#"let s: String = "hello" + " world""#); } - - #[test] - fn test_closure_type_inferred() { - assert_ok("let double = |x: Int| x"); - } - - #[test] - fn test_closure_with_return_type() { - assert_ok("let add = |x: Int, y: Int| -> Int { x }"); - } - - #[test] - fn test_protocol_def_ok() { - assert_ok(r#" -protocol Printable { fn print(msg: String) -> Void } -"#); - } - - #[test] - fn test_impl_def_ok() { - assert_ok(r#" -protocol Printable { fn print(msg: String) -> Void } -type User { name: String } -impl Printable for User { fn print(msg: String) -> Void { } } -"#); - } - - #[test] - fn test_import_does_not_fail() { - assert_ok("import std::array"); - } - - #[test] - fn test_result_type_annotation() { - assert_ok(r#"fn fetch() -> Result { return fetch() }"#); - } - - #[test] - fn test_map_type_annotation() { - // Just test that Map type annotation parses and resolves without crashing - // Use a function body where a self-reference is valid - assert_ok(r#"fn get_map() -> Map { return get_map() }"#); - } - - #[test] - fn test_decorator_does_not_break_fn() { - assert_ok(r#" -@public -fn greet(name: String) -> String { return name } -"#); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-types/src/error.rs b/_archive/rust-bootstrap/engrams/el-types/src/error.rs deleted file mode 100644 index 27cc592..0000000 --- a/_archive/rust-bootstrap/engrams/el-types/src/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Type system errors. - -use thiserror::Error; - -#[derive(Debug, Clone, Error)] -#[error("{kind}")] -pub struct TypeError { - pub kind: TypeErrorKind, -} - -impl TypeError { - pub fn new(kind: TypeErrorKind) -> Self { - Self { kind } - } -} - -#[derive(Debug, Clone, Error)] -pub enum TypeErrorKind { - #[error("type mismatch: expected {expected}, got {got}")] - TypeMismatch { expected: String, got: String }, - - #[error("undefined variable '{0}'")] - UndefinedVariable(String), - - #[error("undefined type '{0}'")] - UndefinedType(String), - - #[error("undefined function '{0}'")] - UndefinedFunction(String), - - #[error("wrong number of arguments: expected {expected}, got {got}")] - ArgCountMismatch { expected: usize, got: usize }, - - #[error("field '{field}' not found on type '{type_name}'")] - UnknownField { type_name: String, field: String }, - - #[error("cannot call non-function type {0}")] - NotCallable(String), - - #[error("activate expression requires a registered type name, got '{0}'")] - ActivateUnknownType(String), - - #[error("index operator requires Array type, got {0}")] - NotIndexable(String), -} diff --git a/_archive/rust-bootstrap/engrams/el-types/src/lib.rs b/_archive/rust-bootstrap/engrams/el-types/src/lib.rs deleted file mode 100644 index 573fe77..0000000 --- a/_archive/rust-bootstrap/engrams/el-types/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! el-types — Engram language type system. -//! -//! Types in the Engram language are more than structural contracts — every -//! named type is a node in a knowledge graph. Compatibility checking is -//! therefore two-dimensional: -//! -//! 1. **Structural compatibility** — the traditional "does this type's layout -//! match?" check. -//! 2. **Semantic compatibility** — are the Engram node embeddings for these -//! two types close enough in meaning-space? This enables the `activate` -//! construct to return a statically-typed result even though the query is -//! a free-form natural language string. -//! -//! In the current implementation, semantic compatibility falls back to a -//! symbolic check (are the Engram node type strings the same?). When an -//! actual Engram database is connected via `CompilerOptions::engram_db_path`, -//! the checker can delegate to real cosine-similarity over embeddings. - -mod error; -mod types; -mod checker; - -pub use error::{TypeError, TypeErrorKind}; -pub use types::{Type, TypeDef, TypeEnv}; -pub use checker::TypeChecker; diff --git a/_archive/rust-bootstrap/engrams/el-types/src/types.rs b/_archive/rust-bootstrap/engrams/el-types/src/types.rs deleted file mode 100644 index d820092..0000000 --- a/_archive/rust-bootstrap/engrams/el-types/src/types.rs +++ /dev/null @@ -1,596 +0,0 @@ -//! Core type definitions and the type environment. - -use std::collections::HashMap; - -/// The semantic type of a value in Engram source. -#[derive(Debug, Clone, PartialEq)] -pub enum Type { - // ── Primitives ──────────────────────────────────────────────────────────── - Int, - Float, - String, - Bool, - Uuid, - Void, - - // ── Composite ───────────────────────────────────────────────────────────── - Named(std::string::String), - Array(Box), - Optional(Box), - Result { ok: Box, err: Box }, - Map { key: Box, value: Box }, - - // ── Function ────────────────────────────────────────────────────────────── - Fn { params: Vec, return_type: Box }, - - // ── Internal ────────────────────────────────────────────────────────────── - Unknown, - Never, -} - -impl std::fmt::Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Type::Int => write!(f, "Int"), - Type::Float => write!(f, "Float"), - Type::String => write!(f, "String"), - Type::Bool => write!(f, "Bool"), - Type::Uuid => write!(f, "Uuid"), - Type::Void => write!(f, "Void"), - Type::Named(n) => write!(f, "{n}"), - Type::Array(t) => write!(f, "[{t}]"), - Type::Optional(t) => write!(f, "{t}?"), - Type::Result { ok, err } => write!(f, "Result<{ok}, {err}>"), - Type::Map { key, value } => write!(f, "Map<{key}, {value}>"), - Type::Fn { params, return_type } => { - let ps: Vec<_> = params.iter().map(|p| p.to_string()).collect(); - write!(f, "fn({}) -> {return_type}", ps.join(", ")) - } - Type::Unknown => write!(f, ""), - Type::Never => write!(f, "!"), - } - } -} - -// ── TypeDef ─────────────────────────────────────────────────────────────────── - -#[derive(Debug, Clone)] -pub enum TypeDef { - Struct { - name: std::string::String, - fields: Vec<(std::string::String, Type)>, - }, - Enum { - name: std::string::String, - variants: Vec, - }, - Primitive(Type), - Protocol { - name: std::string::String, - methods: Vec, - }, -} - -#[derive(Debug, Clone)] -pub struct EnumVariant { - pub name: std::string::String, - pub payload: Option, -} - -#[derive(Debug, Clone)] -pub struct ProtocolMethodSig { - pub name: std::string::String, - pub params: Vec, - pub return_type: Type, -} - -// ── TypeEnv ─────────────────────────────────────────────────────────────────── - -#[derive(Debug, Clone, Default)] -pub struct TypeEnv { - bindings: HashMap, - pub types: HashMap, - pub engram_mappings: HashMap, - pub functions: HashMap, - /// Tracks explicit `impl Protocol for Type` registrations. - pub impls: HashMap<(std::string::String, std::string::String), bool>, -} - -impl TypeEnv { - /// Create a fresh environment pre-populated with built-in types and functions. - pub fn with_builtins() -> Self { - let mut env = Self::default(); - env.types.insert("Int".into(), TypeDef::Primitive(Type::Int)); - env.types.insert("Float".into(), TypeDef::Primitive(Type::Float)); - env.types.insert("String".into(), TypeDef::Primitive(Type::String)); - env.types.insert("Bool".into(), TypeDef::Primitive(Type::Bool)); - env.types.insert("Uuid".into(), TypeDef::Primitive(Type::Uuid)); - env.types.insert("Void".into(), TypeDef::Primitive(Type::Void)); - - // Register built-in output functions — accept any value (Unknown = polymorphic) - let void_fn_any = Type::Fn { - params: vec![Type::Unknown], - return_type: Box::new(Type::Void), - }; - env.functions.insert("print".into(), void_fn_any.clone()); - env.functions.insert("println".into(), void_fn_any.clone()); - env.functions.insert("log".into(), void_fn_any.clone()); - env.functions.insert("print_err".into(), void_fn_any); - - // ── String builtins ─────────────────────────────────────────────────── - let str_fn = |params: Vec, ret: Type| Type::Fn { params, return_type: Box::new(ret) }; - let s = Type::String; - let i = Type::Int; - let b = Type::Bool; - let u = Type::Unknown; - - // String operations - for name in &["str_contains","str_starts_with","string_starts_with","string_contains","str_ends_with","str_eq","string_ends_with","string_index_of","str_index_of","str_last_index_of"] { - env.functions.insert(name.to_string(), str_fn(vec![s.clone(), s.clone()], b.clone())); - } - for name in &["str_to_lowercase","str_trim","string_trim","string_to_upper","str_upper","string_to_lower","str_lower","to_string","int_to_str","bool_to_str"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone()], s.clone())); - } - for name in &["str_len","string_len","list_len","array_length","array_len","map_len","json_array_len"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone()], i.clone())); - } - for name in &["str_replace","string_replace","string_concat","string_substring"] { - env.functions.insert(name.to_string(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone())); - } - // str_slice(s: String, start: Int, end: Int) -> String - env.functions.insert("str_slice".into(), str_fn(vec![s.clone(), i.clone(), i.clone()], s.clone())); - env.functions.insert("str_split".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown)); - env.functions.insert("string_split".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown)); - env.functions.insert("string_split_last".into(), str_fn(vec![s.clone(), s.clone()], Type::Unknown)); - env.functions.insert("array_join".into(), str_fn(vec![u.clone(), s.clone()], s.clone())); - env.functions.insert("list_join".into(), str_fn(vec![u.clone(), s.clone()], s.clone())); - - // Parse / convert - for name in &["str_to_int","parse_int","int_parse"] { - env.functions.insert(name.to_string(), str_fn(vec![s.clone()], i.clone())); - } - for name in &["str_to_float","parse_float"] { - env.functions.insert(name.to_string(), str_fn(vec![s.clone()], Type::Float)); - } - - // JSON - env.functions.insert("json_get".into(), str_fn(vec![s.clone(), s.clone()], u.clone())); - env.functions.insert("json_set".into(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone())); - env.functions.insert("json_keys".into(), str_fn(vec![s.clone()], Type::Unknown)); - env.functions.insert("json_stringify".into(), str_fn(vec![u.clone()], s.clone())); - env.functions.insert("json_parse".into(), str_fn(vec![s.clone()], u.clone())); - env.functions.insert("json_encode".into(), str_fn(vec![u.clone()], s.clone())); - env.functions.insert("json_decode".into(), str_fn(vec![s.clone()], u.clone())); - env.functions.insert("json_get_string".into(), str_fn(vec![u.clone(), s.clone()], s.clone())); - env.functions.insert("json_get_int".into(), str_fn(vec![u.clone(), s.clone()], i.clone())); - env.functions.insert("json_get_array".into(), str_fn(vec![u.clone(), s.clone()], Type::Unknown)); - env.functions.insert("json_array_get".into(), str_fn(vec![s.clone(), i.clone()], u.clone())); - env.functions.insert("json_array_push".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("json_array_len".into(), str_fn(vec![s.clone()], i.clone())); - - // Array - for name in &["array_push","array_pop","array_reverse","array_sort","array_first","array_last"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone()], u.clone())); - } - env.functions.insert("array_get".into(), str_fn(vec![u.clone(), i.clone()], u.clone())); - env.functions.insert("list_get".into(), str_fn(vec![u.clone(), i.clone()], u.clone())); - env.functions.insert("array_concat".into(), str_fn(vec![u.clone(), u.clone()], u.clone())); - env.functions.insert("array_contains".into(), str_fn(vec![u.clone(), u.clone()], b.clone())); - env.functions.insert("array_slice".into(), str_fn(vec![u.clone(), i.clone(), i.clone()], u.clone())); - env.functions.insert("array_zip".into(), str_fn(vec![u.clone(), u.clone()], u.clone())); - env.functions.insert("array_enumerate".into(), str_fn(vec![u.clone()], u.clone())); - - // Map - env.functions.insert("map_new".into(), str_fn(vec![], u.clone())); - for name in &["map_get","map_remove","map_contains","map_keys","map_values"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone(), s.clone()], u.clone())); - } - env.functions.insert("map_set".into(), str_fn(vec![u.clone(), s.clone(), u.clone()], u.clone())); - env.functions.insert("map_len".into(), str_fn(vec![u.clone()], i.clone())); - - // Filesystem - for name in &["fs_read"] { - env.functions.insert(name.to_string(), str_fn(vec![s.clone()], s.clone())); - } - for name in &["fs_exists","fs_mkdir","fs_remove","fs_is_dir"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone()], b.clone())); - } - // fs_write and fs_append take (path, content) — two arguments - for name in &["fs_write","fs_append"] { - env.functions.insert(name.to_string(), str_fn(vec![s.clone(), s.clone()], b.clone())); - } - env.functions.insert("fs_list".into(), str_fn(vec![s.clone()], Type::Unknown)); - env.functions.insert("fs_list_recursive".into(), str_fn(vec![s.clone()], Type::Unknown)); - env.functions.insert("path_join".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("path_parent".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("cwd".into(), str_fn(vec![], s.clone())); - - // Crypto / UUID - env.functions.insert("blake3_hash".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("uuid_new".into(), str_fn(vec![], s.clone())); - env.functions.insert("uuid_v4".into(), str_fn(vec![], s.clone())); - env.functions.insert("hmac_sha256".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("base64_url_encode".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("base64_url_decode".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("unix_timestamp".into(), str_fn(vec![], i.clone())); - env.functions.insert("now_millis".into(), str_fn(vec![], i.clone())); - - // HTTP - env.functions.insert("http_get".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("http_post".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("http_put".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("http_delete".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("http_patch".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("http_get_auth".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("http_post_auth".into(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone())); - env.functions.insert("http_put_auth".into(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone())); - env.functions.insert("http_delete_auth".into(), str_fn(vec![s.clone(), s.clone()], s.clone())); - env.functions.insert("http_serve".into(), str_fn(vec![u.clone()], Type::Void)); - - // State - env.functions.insert("state_get".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("state_set".into(), str_fn(vec![s.clone(), s.clone()], b.clone())); - env.functions.insert("state_del".into(), str_fn(vec![s.clone()], b.clone())); - env.functions.insert("state_keys".into(), str_fn(vec![], Type::Unknown)); - - // System - env.functions.insert("env".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("args".into(), str_fn(vec![], Type::Unknown)); - env.functions.insert("exit".into(), str_fn(vec![i.clone()], Type::Void)); - env.functions.insert("sleep_ms".into(), str_fn(vec![i.clone()], Type::Void)); - env.functions.insert("sleep_secs".into(), str_fn(vec![i.clone()], Type::Void)); - env.functions.insert("timestamp".into(), str_fn(vec![], s.clone())); - env.functions.insert("readline".into(), str_fn(vec![s.clone()], s.clone())); - env.functions.insert("getpid".into(), str_fn(vec![], i.clone())); - env.functions.insert("exec_bg".into(), str_fn(vec![s.clone()], i.clone())); - env.functions.insert("spawn_thread".into(), str_fn(vec![s.clone()], Type::Void)); - - // ANSI color builtins - for name in &["color_cyan","color_green","color_red","color_yellow","color_bold","color_dim"] { - env.functions.insert(name.to_string(), str_fn(vec![s.clone()], s.clone())); - } - - // Terminal control builtins - for name in &["term_clear", "term_save_cursor", "term_restore_cursor", "term_clear_line"] { - env.functions.insert(name.to_string(), str_fn(vec![], Type::Void)); - } - env.functions.insert("print_inline".into(), str_fn(vec![u.clone()], Type::Void)); - env.functions.insert("term_size".into(), str_fn(vec![], Type::Unknown)); - env.functions.insert("cursor_to".into(), str_fn(vec![i.clone(), i.clone()], Type::Void)); - env.functions.insert("cursor_up".into(), str_fn(vec![i.clone()], Type::Void)); - env.functions.insert("cursor_down".into(), str_fn(vec![i.clone()], Type::Void)); - env.functions.insert("cursor_col".into(), str_fn(vec![i.clone()], Type::Void)); - env.functions.insert("http_sse_post".into(), str_fn(vec![s.clone(), s.clone(), s.clone()], s.clone())); - - // Canvas / native window builtins - env.functions.insert("canvas_open".into(), str_fn(vec![s.clone(), i.clone(), i.clone()], Type::Void)); - env.functions.insert("canvas_clear".into(), str_fn(vec![s.clone()], Type::Void)); - env.functions.insert("canvas_fill_rect".into(), str_fn(vec![i.clone(), i.clone(), i.clone(), i.clone(), s.clone(), i.clone()], Type::Void)); - env.functions.insert("canvas_stroke_rect".into(), str_fn(vec![i.clone(), i.clone(), i.clone(), i.clone(), s.clone(), i.clone(), i.clone()], Type::Void)); - env.functions.insert("canvas_line".into(), str_fn(vec![i.clone(), i.clone(), i.clone(), i.clone(), s.clone(), i.clone()], Type::Void)); - env.functions.insert("canvas_text".into(), str_fn(vec![i.clone(), i.clone(), s.clone(), i.clone(), s.clone()], Type::Void)); - env.functions.insert("canvas_text_width".into(), str_fn(vec![s.clone(), i.clone()], i.clone())); - env.functions.insert("canvas_text_height".into(), str_fn(vec![i.clone()], i.clone())); - env.functions.insert("canvas_clip".into(), str_fn(vec![i.clone(), i.clone(), i.clone(), i.clone()], Type::Void)); - env.functions.insert("canvas_unclip".into(), str_fn(vec![], Type::Void)); - env.functions.insert("canvas_size".into(), str_fn(vec![], Type::Unknown)); - env.functions.insert("canvas_mouse_pos".into(), str_fn(vec![], Type::Unknown)); - env.functions.insert("canvas_events".into(), str_fn(vec![], s.clone())); - env.functions.insert("canvas_swap".into(), str_fn(vec![], Type::Void)); - env.functions.insert("canvas_run_loop".into(), str_fn(vec![s.clone()], Type::Void)); - env.functions.insert("canvas_image".into(), str_fn(vec![s.clone(), i.clone(), i.clone(), i.clone(), i.clone()], Type::Void)); - env.functions.insert("state_set".into(), str_fn(vec![s.clone(), s.clone()], Type::Void)); - env.functions.insert("state_get".into(), str_fn(vec![s.clone()], s.clone())); - - // Math - for name in &["math_abs","math_floor","math_ceil","math_round","math_sqrt"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone()], u.clone())); - } - env.functions.insert("math_max".into(), str_fn(vec![u.clone(), u.clone()], u.clone())); - env.functions.insert("math_min".into(), str_fn(vec![u.clone(), u.clone()], u.clone())); - env.functions.insert("math_pow".into(), str_fn(vec![u.clone(), u.clone()], Type::Float)); - - // Result / Optional - for name in &["result_ok","result_err","result_unwrap","result_unwrap_or","optional_some","optional_unwrap","optional_unwrap_or"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone()], u.clone())); - } - for name in &["result_is_ok","result_is_err","optional_is_some","optional_is_none"] { - env.functions.insert(name.to_string(), str_fn(vec![u.clone()], b.clone())); - } - env.functions.insert("optional_none".into(), str_fn(vec![], Type::Void)); - - env - } - - // ── Bindings ────────────────────────────────────────────────────────────── - - pub fn bind(&mut self, name: impl Into, ty: Type) { - self.bindings.insert(name.into(), ty); - } - - pub fn lookup(&self, name: &str) -> Option<&Type> { - self.bindings.get(name) - } - - // ── Type registration ───────────────────────────────────────────────────── - - pub fn register_type( - &mut self, - name: impl Into, - def: TypeDef, - engram_node_type: impl Into, - ) { - let name = name.into(); - let engram = engram_node_type.into(); - if !engram.is_empty() { - self.engram_mappings.insert(name.clone(), engram); - } - self.types.insert(name, def); - } - - pub fn get_type(&self, name: &str) -> Option<&TypeDef> { - self.types.get(name) - } - - pub fn register_fn(&mut self, name: impl Into, ty: Type) { - self.functions.insert(name.into(), ty); - } - - pub fn lookup_fn(&self, name: &str) -> Option<&Type> { - self.functions.get(name) - } - - // ── Protocol support ────────────────────────────────────────────────────── - - pub fn register_protocol( - &mut self, - name: impl Into, - methods: Vec, - ) { - let name = name.into(); - let def = TypeDef::Protocol { name: name.clone(), methods }; - self.types.insert(name, def); - } - - pub fn register_impl( - &mut self, - protocol_name: impl Into, - type_name: impl Into, - ) { - self.impls.insert((protocol_name.into(), type_name.into()), true); - } - - pub fn implements(&self, type_name: &str, protocol_name: &str) -> bool { - self.impls.contains_key(&(protocol_name.to_string(), type_name.to_string())) - } - - pub fn check_impl_completeness( - &self, - protocol_name: &str, - impl_method_names: &[String], - ) -> Vec { - match self.types.get(protocol_name) { - Some(TypeDef::Protocol { methods, .. }) => { - methods.iter() - .filter(|m| !impl_method_names.contains(&m.name)) - .map(|m| m.name.clone()) - .collect() - } - _ => vec![], - } - } - - // ── Compatibility ───────────────────────────────────────────────────────── - - pub fn check_compatible(&self, a: &Type, b: &Type) -> bool { - match (a, b) { - (Type::Unknown, _) | (_, Type::Unknown) => true, - (Type::Never, _) => true, - (Type::Int, Type::Int) => true, - (Type::Float, Type::Float) => true, - (Type::String, Type::String) => true, - (Type::Bool, Type::Bool) => true, - (Type::Uuid, Type::Uuid) => true, - (Type::Void, Type::Void) => true, - (Type::Int, Type::Float) => true, - (Type::Named(a_name), Type::Named(b_name)) => { - if a_name == b_name { - return true; - } - let a_node = self.engram_mappings.get(a_name); - let b_node = self.engram_mappings.get(b_name); - match (a_node, b_node) { - (Some(a_n), Some(b_n)) => a_n == b_n, - _ => false, - } - } - (Type::Array(a_inner), Type::Array(b_inner)) => { - self.check_compatible(a_inner, b_inner) - } - (Type::Optional(a_inner), Type::Optional(b_inner)) => { - self.check_compatible(a_inner, b_inner) - } - (t, Type::Optional(inner)) => self.check_compatible(t, inner), - (Type::Result { ok: a_ok, err: a_err }, Type::Result { ok: b_ok, err: b_err }) => { - self.check_compatible(a_ok, b_ok) && self.check_compatible(a_err, b_err) - } - (Type::Map { key: ak, value: av }, Type::Map { key: bk, value: bv }) => { - self.check_compatible(ak, bk) && self.check_compatible(av, bv) - } - (Type::Fn { params: ap, return_type: ar }, Type::Fn { params: bp, return_type: br }) => { - ap.len() == bp.len() - && ap.iter().zip(bp.iter()).all(|(a, b)| self.check_compatible(a, b)) - && self.check_compatible(ar, br) - } - _ => false, - } - } - - pub fn resolve_type_expr(&self, te: &el_parser::TypeExpr) -> Result { - match te { - el_parser::TypeExpr::Named(n) => { - Ok(match n.as_str() { - "Int" => Type::Int, - "Float" => Type::Float, - "String" => Type::String, - "Bool" => Type::Bool, - "Uuid" => Type::Uuid, - "Void" => Type::Void, - other => { - if self.types.contains_key(other) { - Type::Named(other.to_string()) - } else { - return Err(crate::TypeError::new( - crate::TypeErrorKind::UndefinedType(other.to_string()), - )); - } - } - }) - } - el_parser::TypeExpr::Array(inner) => { - Ok(Type::Array(Box::new(self.resolve_type_expr(inner)?))) - } - el_parser::TypeExpr::Optional(inner) => { - Ok(Type::Optional(Box::new(self.resolve_type_expr(inner)?))) - } - el_parser::TypeExpr::Fn { params, return_type } => { - let ps = params.iter().map(|p| self.resolve_type_expr(p)).collect::, _>>()?; - let ret = self.resolve_type_expr(return_type)?; - Ok(Type::Fn { params: ps, return_type: Box::new(ret) }) - } - el_parser::TypeExpr::Result { ok, err } => { - let ok_ty = self.resolve_type_expr(ok)?; - let err_ty = self.resolve_type_expr(err)?; - Ok(Type::Result { ok: Box::new(ok_ty), err: Box::new(err_ty) }) - } - el_parser::TypeExpr::Map { key, value } => { - let key_ty = self.resolve_type_expr(key)?; - let val_ty = self.resolve_type_expr(value)?; - Ok(Type::Map { key: Box::new(key_ty), value: Box::new(val_ty) }) - } - el_parser::TypeExpr::TypeParam(_) => { - // Generic type parameters resolve to Unknown at the call site — - // the actual type is inferred from arguments during call checking. - Ok(Type::Unknown) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn env() -> TypeEnv { - TypeEnv::with_builtins() - } - - #[test] - fn test_primitive_compatibility() { - let e = env(); - assert!(e.check_compatible(&Type::Int, &Type::Int)); - assert!(e.check_compatible(&Type::String, &Type::String)); - assert!(!e.check_compatible(&Type::Int, &Type::String)); - } - - #[test] - fn test_int_promotes_to_float() { - let e = env(); - assert!(e.check_compatible(&Type::Int, &Type::Float)); - } - - #[test] - fn test_named_same_is_compatible() { - let e = env(); - assert!(e.check_compatible(&Type::Named("User".into()), &Type::Named("User".into()))); - assert!(!e.check_compatible(&Type::Named("User".into()), &Type::Named("Order".into()))); - } - - #[test] - fn test_semantic_compatibility_via_engram_mapping() { - let mut e = env(); - e.engram_mappings.insert("User".into(), "Entity".into()); - e.engram_mappings.insert("Customer".into(), "Entity".into()); - assert!(e.check_compatible(&Type::Named("User".into()), &Type::Named("Customer".into()))); - } - - #[test] - fn test_optional_compatibility() { - let e = env(); - assert!(e.check_compatible(&Type::Int, &Type::Optional(Box::new(Type::Int)))); - } - - #[test] - fn test_array_compatibility() { - let e = env(); - assert!(e.check_compatible( - &Type::Array(Box::new(Type::Int)), - &Type::Array(Box::new(Type::Int)), - )); - assert!(!e.check_compatible( - &Type::Array(Box::new(Type::Int)), - &Type::Array(Box::new(Type::String)), - )); - } - - #[test] - fn test_result_type_compatibility() { - let e = env(); - let r1 = Type::Result { ok: Box::new(Type::String), err: Box::new(Type::String) }; - let r2 = Type::Result { ok: Box::new(Type::String), err: Box::new(Type::String) }; - assert!(e.check_compatible(&r1, &r2)); - let r3 = Type::Result { ok: Box::new(Type::Int), err: Box::new(Type::String) }; - assert!(!e.check_compatible(&r1, &r3)); - } - - #[test] - fn test_map_type_compatibility() { - let e = env(); - let m1 = Type::Map { key: Box::new(Type::String), value: Box::new(Type::Int) }; - let m2 = Type::Map { key: Box::new(Type::String), value: Box::new(Type::Int) }; - assert!(e.check_compatible(&m1, &m2)); - let m3 = Type::Map { key: Box::new(Type::Int), value: Box::new(Type::Int) }; - assert!(!e.check_compatible(&m1, &m3)); - } - - #[test] - fn test_register_and_lookup_protocol() { - let mut e = env(); - e.register_protocol("Printable", vec![ - ProtocolMethodSig { name: "print".into(), params: vec![], return_type: Type::Void }, - ]); - assert!(matches!(e.get_type("Printable"), Some(TypeDef::Protocol { .. }))); - } - - #[test] - fn test_register_impl_and_check() { - let mut e = env(); - e.register_protocol("Printable", vec![ - ProtocolMethodSig { name: "print".into(), params: vec![], return_type: Type::Void }, - ]); - e.register_impl("Printable", "User"); - assert!(e.implements("User", "Printable")); - assert!(!e.implements("Order", "Printable")); - } - - #[test] - fn test_check_impl_completeness_missing_methods() { - let mut e = env(); - e.register_protocol("Comparable", vec![ - ProtocolMethodSig { name: "compare".into(), params: vec![], return_type: Type::Int }, - ProtocolMethodSig { name: "equals".into(), params: vec![], return_type: Type::Bool }, - ]); - let missing = e.check_impl_completeness("Comparable", &["compare".to_string()]); - assert_eq!(missing, vec!["equals"]); - } - - #[test] - fn test_check_impl_completeness_all_present() { - let mut e = env(); - e.register_protocol("Comparable", vec![ - ProtocolMethodSig { name: "compare".into(), params: vec![], return_type: Type::Int }, - ]); - let missing = e.check_impl_completeness("Comparable", &["compare".to_string()]); - assert!(missing.is_empty()); - } -} diff --git a/_archive/rust-bootstrap/engrams/el-vm/Cargo.toml b/_archive/rust-bootstrap/engrams/el-vm/Cargo.toml deleted file mode 100644 index 6057774..0000000 --- a/_archive/rust-bootstrap/engrams/el-vm/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "el-vm" -description = "El Virtual Machine — core bytecode executor used by the elvm binary and el toolchain" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -el-compiler = { workspace = true } -serde_json = { workspace = true } -reqwest = { workspace = true } -uuid = { workspace = true } -walkdir = { workspace = true } -blake3 = { workspace = true } -hmac = { workspace = true } -sha2 = { workspace = true } -hex = { workspace = true } -base64 = { workspace = true } -aes-gcm = { workspace = true } -rand = { workspace = true } -crossbeam-channel = { workspace = true } diff --git a/_archive/rust-bootstrap/engrams/el-vm/src/builtins.rs b/_archive/rust-bootstrap/engrams/el-vm/src/builtins.rs deleted file mode 100644 index ffac63e..0000000 --- a/_archive/rust-bootstrap/engrams/el-vm/src/builtins.rs +++ /dev/null @@ -1,725 +0,0 @@ -//! Built-in function dispatch for the El VM. -//! -//! Provides the set of built-in functions accessible to El programs at runtime. -//! This is the core portable set that `elvm` supports on all platforms. - -use el_compiler::Value; -use std::collections::HashMap; - -/// Result of a builtin dispatch attempt. -pub enum BuiltinResult { - /// The builtin was handled; return value (if any) is on the stack. - Handled, - /// The `http_serve` builtin was invoked; the server loop is running. - HttpServe, - /// The program called `exit(code)`. - Exit(i32), - /// The name is not a known builtin — try user-defined functions. - NotBuiltin, -} - -/// Dispatch a built-in call. -/// -/// # Arguments -/// * `name` — function name -/// * `arity` — number of arguments already on the stack -/// * `stack` — the VM value stack (may be modified) -/// * `args` — program arguments (for the `args()` builtin) -/// * `state` — global mutable string state -pub fn dispatch( - name: &str, - _arity: u32, - stack: &mut Vec, - program_args: &[String], - state: &mut HashMap, -) -> BuiltinResult { - match name { - // ── I/O ────────────────────────────────────────────────────────────── - - "print" => { - let v = stack.pop().unwrap_or(Value::Nil); - print!("{v}"); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "println" | "log" => { - let v = stack.pop().unwrap_or(Value::Nil); - println!("{v}"); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "print_err" | "log_debug" | "log_info" | "log_warn" | "log_error" => { - let v = stack.pop().unwrap_or(Value::Nil); - eprintln!("{v}"); - stack.push(Value::Nil); - BuiltinResult::Handled - } - - // ── Process ─────────────────────────────────────────────────────────── - - "args" => { - let list = program_args.iter().map(|s| Value::Str(s.clone())).collect(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "exit" => { - let code = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as i32, - _ => 0, - }; - BuiltinResult::Exit(code) - } - "env" => { - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let val = std::env::var(&key).unwrap_or_default(); - stack.push(Value::Str(val)); - 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 - } - "getpid" => { - stack.push(Value::Int(std::process::id() as i64)); - BuiltinResult::Handled - } - "sleep_ms" => { - let ms = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u64, - _ => 0, - }; - std::thread::sleep(std::time::Duration::from_millis(ms)); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "sleep_secs" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(n) => n as u64, - Value::Float(f) => f as u64, - _ => 0, - }; - std::thread::sleep(std::time::Duration::from_secs(s)); - stack.push(Value::Nil); - BuiltinResult::Handled - } - "shell_exec" | "sh" => { - let cmd = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - _ => String::new(), - }; - let output = std::process::Command::new("sh") - .arg("-c") - .arg(&cmd) - .output() - .map(|o| String::from_utf8_lossy(&o.stdout).to_string()) - .unwrap_or_default(); - stack.push(Value::Str(output)); - BuiltinResult::Handled - } - - // ── String operations ───────────────────────────────────────────────── - - "str_len" | "string_len" | "native_string_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_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 - } - "str_contains" | "native_string_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_ends_with" => { - let suffix = 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.ends_with(suffix.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 = s.split(delim.as_str()).map(|p| Value::Str(p.to_string())).collect(); - stack.push(Value::List(parts)); - BuiltinResult::Handled - } - "str_replace" => { - let replacement = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let pattern = 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::Str(s.replace(pattern.as_str(), &replacement))); - BuiltinResult::Handled - } - "str_trim" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(s.trim().to_string())); - BuiltinResult::Handled - } - "str_upper" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(s.to_uppercase())); - BuiltinResult::Handled - } - "str_lower" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(s.to_lowercase())); - BuiltinResult::Handled - } - "str_slice" => { - let end = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 }; - let start = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 }; - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let chars: Vec = s.chars().collect(); - let slice: String = chars[start.min(chars.len())..end.min(chars.len())].iter().collect(); - stack.push(Value::Str(slice)); - BuiltinResult::Handled - } - "str_index_of" => { - 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() }; - let idx = haystack.find(needle.as_str()).map(|i| i as i64).unwrap_or(-1); - stack.push(Value::Int(idx)); - BuiltinResult::Handled - } - "str_char_at" => { - let idx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 }; - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let c = s.chars().nth(idx).map(|c| Value::Str(c.to_string())).unwrap_or(Value::Nil); - stack.push(c); - BuiltinResult::Handled - } - - // ── Type conversion ─────────────────────────────────────────────────── - - "int_to_str" | "str_format" | "native_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 - } - "float_to_str" => { - let f = match stack.pop().unwrap_or(Value::Nil) { - Value::Float(f) => f, - Value::Int(n) => n as f64, - _ => 0.0, - }; - stack.push(Value::Str(f.to_string())); - BuiltinResult::Handled - } - "bool_to_str" => { - let b = match stack.pop().unwrap_or(Value::Nil) { Value::Bool(b) => b, _ => false }; - stack.push(Value::Str(b.to_string())); - BuiltinResult::Handled - } - "str_to_int" | "parse_int" | "native_str_to_int" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let v = s.trim().parse::().map(Value::Int).unwrap_or(Value::Nil); - stack.push(v); - BuiltinResult::Handled - } - "str_to_float" | "parse_float" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let v = s.trim().parse::().map(Value::Float).unwrap_or(Value::Nil); - stack.push(v); - BuiltinResult::Handled - } - "int_to_float" => { - let n = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => 0 }; - stack.push(Value::Float(n as f64)); - BuiltinResult::Handled - } - "float_to_int" => { - let f = match stack.pop().unwrap_or(Value::Nil) { Value::Float(f) => f, Value::Int(n) => n as f64, _ => 0.0 }; - stack.push(Value::Int(f as i64)); - BuiltinResult::Handled - } - "is_nil" => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(matches!(v, Value::Nil))); - BuiltinResult::Handled - } - "unwrap_or" => { - let default = stack.pop().unwrap_or(Value::Nil); - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(if matches!(v, Value::Nil) { default } else { v }); - BuiltinResult::Handled - } - - // ── List operations ─────────────────────────────────────────────────── - - "list_len" | "native_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" | "native_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 - } - "native_string_chars" => { - // Split a string into a list of single-character strings. - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let chars: Vec = s.chars().map(|c| Value::Str(c.to_string())).collect(); - stack.push(Value::List(chars)); - BuiltinResult::Handled - } - "list_push" | "native_list_append" => { - let item = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(mut l) => { l.push(item); l }, _ => vec![item] }; - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "list_pop" => { - let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(mut l) => { l.pop(); l }, _ => vec![] }; - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "list_join" => { - let sep = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] }; - let s: String = list.iter().map(|v| v.to_string()).collect::>().join(&sep); - stack.push(Value::Str(s)); - BuiltinResult::Handled - } - "list_contains" => { - let item = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] }; - stack.push(Value::Bool(list.contains(&item))); - BuiltinResult::Handled - } - "list_concat" => { - let b = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] }; - let a = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] }; - let mut result = a; - result.extend(b); - stack.push(Value::List(result)); - BuiltinResult::Handled - } - "list_reverse" => { - let mut list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] }; - list.reverse(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "list_range" => { - let end = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => 0 }; - let start = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n, _ => 0 }; - let list: Vec = (start..end).map(Value::Int).collect(); - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "list_set" => { - let val = stack.pop().unwrap_or(Value::Nil); - let idx = match stack.pop().unwrap_or(Value::Nil) { Value::Int(n) => n as usize, _ => 0 }; - let mut list = match stack.pop().unwrap_or(Value::Nil) { Value::List(l) => l, _ => vec![] }; - if idx < list.len() { list[idx] = val; } - stack.push(Value::List(list)); - BuiltinResult::Handled - } - "list_new" | "list_empty" | "native_list_empty" => { - stack.push(Value::List(vec![])); - BuiltinResult::Handled - } - "__build_list__" => { - 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 - } - - // ── JSON ────────────────────────────────────────────────────────────── - - "json_get" | "json_get_string" => { - 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::(&json_str) - .ok() - .and_then(|v| v.get(&key).cloned()) - .map(|v| crate::json_to_value(&v)) - .unwrap_or(Value::Nil); - stack.push(val); - BuiltinResult::Handled - } - "json_stringify" | "json_encode" => { - let v = stack.pop().unwrap_or(Value::Nil); - let json = value_to_json(&v); - stack.push(Value::Str(json.to_string())); - BuiltinResult::Handled - } - "json_parse" | "json_decode" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let val = serde_json::from_str::(&s) - .map(|v| crate::json_to_value(&v)) - .unwrap_or(Value::Nil); - stack.push(val); - BuiltinResult::Handled - } - "json_keys" => { - let json_str = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let keys = serde_json::from_str::(&json_str) - .ok() - .and_then(|v| v.as_object().cloned()) - .map(|o| o.keys().map(|k| Value::Str(k.clone())).collect::>()) - .unwrap_or_default(); - stack.push(Value::List(keys)); - BuiltinResult::Handled - } - "json_set" => { - let val = stack.pop().unwrap_or(Value::Nil); - 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, _ => "{}".to_string() }; - let mut obj = serde_json::from_str::>(&json_str) - .unwrap_or_default(); - obj.insert(key, value_to_json(&val)); - stack.push(Value::Str(serde_json::Value::Object(obj).to_string())); - BuiltinResult::Handled - } - - // ── Time ────────────────────────────────────────────────────────────── - - "now_millis" | "time_now_ms" | "unix_timestamp" | "timestamp" => { - let ms = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis() as i64) - .unwrap_or(0); - stack.push(Value::Int(ms)); - BuiltinResult::Handled - } - "time_now_s" => { - let s = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_secs() as i64) - .unwrap_or(0); - stack.push(Value::Int(s)); - BuiltinResult::Handled - } - "time_now_utc" => { - let ms = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis() as i64) - .unwrap_or(0); - stack.push(Value::Int(ms)); - BuiltinResult::Handled - } - - // ── Math ────────────────────────────────────────────────────────────── - - "math_sin" => { let x = pop_f64(stack); stack.push(Value::Float(x.sin())); BuiltinResult::Handled } - "math_cos" => { let x = pop_f64(stack); stack.push(Value::Float(x.cos())); BuiltinResult::Handled } - "math_tan" => { let x = pop_f64(stack); stack.push(Value::Float(x.tan())); BuiltinResult::Handled } - "math_asin" => { let x = pop_f64(stack); stack.push(Value::Float(x.asin())); BuiltinResult::Handled } - "math_acos" => { let x = pop_f64(stack); stack.push(Value::Float(x.acos())); BuiltinResult::Handled } - "math_exp" => { let x = pop_f64(stack); stack.push(Value::Float(x.exp())); BuiltinResult::Handled } - "math_ln" => { let x = pop_f64(stack); stack.push(Value::Float(x.ln())); BuiltinResult::Handled } - "math_log2" => { let x = pop_f64(stack); stack.push(Value::Float(x.log2())); BuiltinResult::Handled } - "math_log10"=> { let x = pop_f64(stack); stack.push(Value::Float(x.log10()));BuiltinResult::Handled } - "math_pi" => { stack.push(Value::Float(std::f64::consts::PI)); BuiltinResult::Handled } - "math_e" => { stack.push(Value::Float(std::f64::consts::E)); BuiltinResult::Handled } - "math_atan2"=> { - let y = pop_f64(stack); - let x = pop_f64(stack); - stack.push(Value::Float(x.atan2(y))); - BuiltinResult::Handled - } - "math_mod" => { - let b = pop_f64(stack); - let a = pop_f64(stack); - stack.push(Value::Float(a % b)); - BuiltinResult::Handled - } - - // ── Crypto / ID ─────────────────────────────────────────────────────── - - "uuid_new" | "uuid_v4" | "crypto_uuid" => { - let id = uuid::Uuid::new_v4().to_string(); - stack.push(Value::Str(id)); - BuiltinResult::Handled - } - "blake3_hash" | "crypto_hash_blake3" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let hash = blake3::hash(s.as_bytes()); - stack.push(Value::Str(hash.to_hex().to_string())); - BuiltinResult::Handled - } - "hash_sha256" | "crypto_hash_sha256" => { - // Use blake3 as a stable hash (sha2 not linked in elvm). - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let hash = blake3::hash(s.as_bytes()); - stack.push(Value::Str(hash.to_hex().to_string())); - BuiltinResult::Handled - } - "base64_encode" => { - use base64::Engine; - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(base64::engine::general_purpose::STANDARD.encode(s.as_bytes()))); - BuiltinResult::Handled - } - "base64_decode" => { - use base64::Engine; - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let decoded = base64::engine::general_purpose::STANDARD - .decode(s.as_bytes()) - .map(|b| String::from_utf8_lossy(&b).to_string()) - .unwrap_or_default(); - stack.push(Value::Str(decoded)); - BuiltinResult::Handled - } - - // ── Filesystem ──────────────────────────────────────────────────────── - - "fs_read" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => { stack.push(Value::Nil); return BuiltinResult::Handled; } }; - let val = std::fs::read_to_string(&path).map(Value::Str).unwrap_or(Value::Nil); - stack.push(val); - BuiltinResult::Handled - } - "fs_write" => { - let content = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let ok = std::fs::write(&path, &content).is_ok(); - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "fs_exists" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Bool(std::path::Path::new(&path).exists())); - BuiltinResult::Handled - } - "fs_mkdir" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let ok = std::fs::create_dir_all(&path).is_ok(); - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "fs_list" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let entries = std::fs::read_dir(&path) - .map(|rd| rd.filter_map(|e| e.ok().and_then(|e| e.file_name().into_string().ok().map(Value::Str))).collect::>()) - .unwrap_or_default(); - stack.push(Value::List(entries)); - BuiltinResult::Handled - } - "fs_append" => { - use std::io::Write; - let content = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let ok = std::fs::OpenOptions::new().create(true).append(true).open(&path) - .and_then(|mut f| f.write_all(content.as_bytes())) - .is_ok(); - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "fs_remove" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let p = std::path::Path::new(&path); - let ok = if p.is_dir() { std::fs::remove_dir(p).is_ok() } else { std::fs::remove_file(p).is_ok() }; - stack.push(Value::Bool(ok)); - BuiltinResult::Handled - } - "fs_is_dir" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Bool(std::path::Path::new(&path).is_dir())); - BuiltinResult::Handled - } - "path_join" => { - let name = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let base = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let joined = std::path::Path::new(&base).join(&name); - stack.push(Value::Str(joined.to_string_lossy().to_string())); - BuiltinResult::Handled - } - "path_parent" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let parent = std::path::Path::new(&path).parent() - .map(|p| Value::Str(p.to_string_lossy().to_string())) - .unwrap_or(Value::Nil); - stack.push(parent); - BuiltinResult::Handled - } - "fs_list_recursive" => { - let path = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let skip_dirs = ["node_modules", ".git", ".nc", "target", "__pycache__", ".el"]; - let mut files = Vec::new(); - if let Ok(walker) = walkdir::WalkDir::new(&path).into_iter().collect::, _>>() { - for entry in walker { - let should_skip = entry.path().components().any(|c| { - let s = c.as_os_str().to_string_lossy(); - skip_dirs.iter().any(|d| s == *d) - }); - if should_skip && entry.path() != std::path::Path::new(&path) { continue; } - if entry.file_type().is_file() { - files.push(Value::Str(entry.path().to_string_lossy().to_string())); - } - } - } - stack.push(Value::List(files)); - BuiltinResult::Handled - } - - // ── Global state ────────────────────────────────────────────────────── - - "state_set" => { - let value = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - state.insert(key, value); - stack.push(Value::Bool(true)); - BuiltinResult::Handled - } - "state_get" => { - let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let val = state.get(&key).cloned().map(Value::Str).unwrap_or(Value::Nil); - stack.push(val); - BuiltinResult::Handled - } - "state_del" => { - let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - state.remove(&key); - stack.push(Value::Bool(true)); - BuiltinResult::Handled - } - "state_keys" => { - let keys: Vec = state.keys().cloned().map(Value::Str).collect(); - stack.push(Value::List(keys)); - BuiltinResult::Handled - } - - // ── HTTP client ─────────────────────────────────────────────────────── - - "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 - } - "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_post_form_auth" => { - // args: url (bottom), auth_value, body (top) - let body = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - let auth = 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/x-www-form-urlencoded") - .header("Authorization", format!("Bearer {auth}")) - .body(body) - .send() - .and_then(|r| r.text()) - .unwrap_or_else(|e| format!("{{\"error\":\"{e}\"}}")); - stack.push(Value::Str(result)); - BuiltinResult::Handled - } - - // ── App context (no-op stubs — app block not parsed by elvm) ───────── - - "env_get" => { - let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(std::env::var(&key).unwrap_or_default())); - BuiltinResult::Handled - } - "ctx_env" => { stack.push(Value::Str(std::env::var("NEURON_ENV").unwrap_or_else(|_| "dev".to_string()))); BuiltinResult::Handled } - "ctx_service" => { stack.push(Value::Str(String::new())); BuiltinResult::Handled } - "ctx_version" => { stack.push(Value::Str(String::new())); BuiltinResult::Handled } - "ctx_instance"=> { stack.push(Value::Str(uuid::Uuid::new_v4().to_string())); BuiltinResult::Handled } - "config" => { let _ = stack.pop(); stack.push(Value::Str(String::new())); BuiltinResult::Handled } - "secret" => { let _ = stack.pop(); stack.push(Value::Str(String::new())); BuiltinResult::Handled } - "flag" => { let _ = stack.pop(); stack.push(Value::Bool(false)); BuiltinResult::Handled } - - // ── Color / formatting (terminal output) ───────────────────────────── - - "color_bold" => { - let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(format!("\x1b[1m{s}\x1b[0m"))); - BuiltinResult::Handled - } - - // ── http_serve (stub — available but note elvm doesn't have the tiny_http dep) ── - - "http_serve" => { - // elvm does not bundle the HTTP server. Emit a helpful message. - let _ = stack.pop(); - eprintln!("elvm: http_serve is not available in the standalone elvm binary. Use 'el run' for HTTP server programs."); - stack.push(Value::Nil); - BuiltinResult::HttpServe - } - - // ── fn_ref helper ───────────────────────────────────────────────────── - - "fn_ref" => { - let name_val = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; - stack.push(Value::Str(name_val)); - BuiltinResult::Handled - } - - _ => BuiltinResult::NotBuiltin, - } -} - -// ── Internal helpers ────────────────────────────────────────────────────────── - -fn pop_f64(stack: &mut Vec) -> f64 { - match stack.pop().unwrap_or(Value::Nil) { - Value::Float(f) => f, - Value::Int(n) => n as f64, - _ => 0.0, - } -} - -fn value_to_json(v: &Value) -> serde_json::Value { - match v { - Value::Int(n) => serde_json::Value::Number((*n).into()), - Value::Float(f) => serde_json::Number::from_f64(*f).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null), - Value::Str(s) => serde_json::Value::String(s.clone()), - Value::Bool(b) => serde_json::Value::Bool(*b), - Value::Nil => serde_json::Value::Null, - Value::List(items) => serde_json::Value::Array(items.iter().map(value_to_json).collect()), - Value::Map(pairs) => { - let mut map = serde_json::Map::new(); - for (k, v) in pairs { map.insert(k.clone(), value_to_json(v)); } - serde_json::Value::Object(map) - } - Value::ResultOk(inner) => serde_json::json!({ "ok": value_to_json(inner) }), - Value::ResultErr(inner) => serde_json::json!({ "err": value_to_json(inner) }), - Value::Struct { fields, .. } => { - let mut map = serde_json::Map::new(); - for (k, v) in fields { map.insert(k.clone(), value_to_json(v)); } - serde_json::Value::Object(map) - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-vm/src/lib.rs b/_archive/rust-bootstrap/engrams/el-vm/src/lib.rs deleted file mode 100644 index f1fccc7..0000000 --- a/_archive/rust-bootstrap/engrams/el-vm/src/lib.rs +++ /dev/null @@ -1,1161 +0,0 @@ -//! el-vm — El Virtual Machine executor. -//! -//! This crate provides the bytecode execution engine used by both the `el` -//! toolchain (`el exec`) and the standalone `elvm` binary. -//! -//! # Usage -//! -//! ```no_run -//! use el_vm::ElVm; -//! -//! let bytes = std::fs::read("program.elc").unwrap(); -//! let instructions = el_compiler::Bytecode::deserialize_all(&bytes).unwrap(); -//! ElVm::new().run(&instructions, &[]); -//! ``` - -use el_compiler::{Bytecode, Value}; -use std::collections::HashMap; - -mod builtins; -pub use builtins::BuiltinResult; - -// ── Call frame ──────────────────────────────────────────────────────────────── - -struct CallFrame { - /// Bytecode offset to return to when this frame pops. - return_ip: usize, - /// Saved locals from the caller's frame. - saved_locals: HashMap, -} - -// ── El VM ───────────────────────────────────────────────────────────────────── - -/// The El Virtual Machine. -/// -/// Executes a flat array of [`Bytecode`] instructions in a stack-based -/// execution model. User-defined functions are resolved via a pre-scanned -/// function table (the codegen emits `Push(Int(entry)) StoreLocal(__fn_name)` -/// stanzas that the VM recognises on its initial scan pass). -pub struct ElVm { - /// Global mutable key-value state (shared across the program lifetime). - global_state: HashMap, - /// Program arguments forwarded to the `args()` builtin. - program_args: Vec, - /// Global instruction buffer — used by the El self-hosting compiler to - /// accumulate codegen output without expensive per-call list cloning. - /// Accessible via the `native_instr_*` builtin family. - instr_buf: Vec, - /// Global token buffer — used by the El self-hosting parser to avoid - /// cloning the token list on every recursive call. - /// Accessible via `native_tokens_init` / `native_token_at` / `native_token_len`. - token_buf: Vec, - /// Global char buffer — used by the El self-hosting lexer to avoid - /// O(n²) cloning of the chars list on each character access. - /// Accessible via `native_chars_init` / `native_char_at` / `native_char_len`. - char_buf: Vec, -} - -impl Default for ElVm { - fn default() -> Self { Self::new() } -} - -impl ElVm { - /// Create a new VM instance with empty global state. - pub fn new() -> Self { - Self { - global_state: HashMap::new(), - program_args: Vec::new(), - instr_buf: Vec::new(), - token_buf: Vec::new(), - char_buf: Vec::new(), - } - } - - /// Set program arguments (available via the `args()` builtin). - pub fn with_args(mut self, args: Vec) -> Self { - self.program_args = args; - self - } - - /// Execute a compiled program. - /// - /// Scans for function entry points on first pass, then runs from IP 0. - /// If the program defines a `main` function, it is called automatically - /// after the top-level initialisation pass completes. - pub fn run(&mut self, instructions: &[Bytecode], args: &[String]) { - self.program_args = args.to_vec(); - self.execute(instructions, &[]); - - // Auto-call `main()` if the program registered one. - let fn_table = Self::scan_fn_table(instructions); - if let Some(&entry) = fn_table.get("main") { - self.call_fn(instructions, &fn_table, entry, vec![]); - } - } - - /// Internal execution — also used for sub-interpreter calls (e.g. http_serve). - pub fn execute(&mut self, instructions: &[Bytecode], initial_stack: &[Value]) -> Value { - // ── Pass 1: build function table ───────────────────────────────────── - let fn_table = Self::scan_fn_table(instructions); - - // ── Pass 2: run ─────────────────────────────────────────────────────── - let mut stack: Vec = initial_stack.to_vec(); - let mut locals: HashMap = HashMap::new(); - let mut call_stack: Vec = Vec::new(); - let mut ip = 0usize; - - while ip < instructions.len() { - match &instructions[ip] { - // ── Stack ───────────────────────────────────────────────────── - Bytecode::Push(v) => stack.push(v.clone()), - Bytecode::Pop => { stack.pop(); } - Bytecode::Dup => { - if let Some(top) = stack.last().cloned() { stack.push(top); } - } - - // ── Arithmetic ──────────────────────────────────────────────── - Bytecode::Add => { - 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::Int(x + y), - (Value::Float(x), Value::Float(y)) => Value::Float(x + y), - (Value::Str(x), Value::Str(y)) => Value::Str(x + &y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 + y), - (Value::Float(x), Value::Int(y)) => Value::Float(x + y as f64), - _ => Value::Nil, - }); - } - Bytecode::Sub => { - 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::Int(x - y), - (Value::Float(x), Value::Float(y)) => Value::Float(x - y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 - y), - (Value::Float(x), Value::Int(y)) => Value::Float(x - y as f64), - _ => Value::Nil, - }); - } - Bytecode::Mul => { - 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::Int(x * y), - (Value::Float(x), Value::Float(y)) => Value::Float(x * y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 * y), - (Value::Float(x), Value::Int(y)) => Value::Float(x * y as f64), - _ => Value::Nil, - }); - } - Bytecode::Div => { - 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)) if y != 0 => Value::Int(x / y), - (Value::Float(x), Value::Float(y)) => Value::Float(x / y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 / y), - (Value::Float(x), Value::Int(y)) => Value::Float(x / y as f64), - _ => Value::Nil, - }); - } - Bytecode::Mod => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(1)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => if y == 0 { Value::Int(0) } else { Value::Int(x % y) }, - (Value::Float(x), Value::Float(y)) => Value::Float(x % y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 % y), - (Value::Float(x), Value::Int(y)) => Value::Float(x % y as f64), - _ => Value::Int(0), - }); - } - Bytecode::BitAnd => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x & y), _ => Value::Int(0) }); - } - Bytecode::BitOr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x | y), _ => Value::Int(0) }); - } - Bytecode::BitXor => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x ^ y), _ => Value::Int(0) }); - } - Bytecode::BitNot => { - let a = stack.pop().unwrap_or(Value::Int(0)); - stack.push(match a { Value::Int(x) => Value::Int(!x), _ => Value::Int(0) }); - } - Bytecode::Shl => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x << (y as u32)), _ => Value::Int(0) }); - } - Bytecode::Shr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x >> (y as u32)), _ => Value::Int(0) }); - } - - // ── Comparison ──────────────────────────────────────────────── - Bytecode::Eq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - 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(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Less)); - } - Bytecode::Gt => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Greater)); - } - Bytecode::LtEq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Greater)); - } - Bytecode::GtEq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Less)); - } - - // ── Logic ───────────────────────────────────────────────────── - 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 => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(!matches!(v, Value::Bool(true)))); - } - - // ── Locals ──────────────────────────────────────────────────── - Bytecode::StoreLocal(name) => { - let v = stack.pop().unwrap_or(Value::Nil); - locals.insert(name.clone(), v); - } - Bytecode::LoadLocal(name) => { - let v = locals.get(name).cloned().unwrap_or(Value::Nil); - stack.push(v); - } - - // ── Function calls ──────────────────────────────────────────── - Bytecode::Call { name, arity } => { - // ── native_instr_* builtins — global instruction accumulator ─ - match name.as_str() { - "native_instr_push" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, v => v.to_string(), - }; - self.instr_buf.push(s); - stack.push(Value::Nil); - ip += 1; continue; - } - "native_instr_len" => { - stack.push(Value::Int(self.instr_buf.len() as i64)); - ip += 1; continue; - } - "native_instr_get" => { - let idx = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - let v = self.instr_buf.get(idx) - .cloned() - .map(Value::Str) - .unwrap_or(Value::Nil); - stack.push(v); - ip += 1; continue; - } - "native_instr_patch" => { - let new_instr = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, v => v.to_string(), - }; - let idx = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - if idx < self.instr_buf.len() { - self.instr_buf[idx] = new_instr; - } - stack.push(Value::Nil); - ip += 1; continue; - } - "native_instr_all" => { - let list: Vec = self.instr_buf.iter() - .map(|s| Value::Str(s.clone())) - .collect(); - stack.push(Value::List(list)); - ip += 1; continue; - } - "native_instr_reset" => { - self.instr_buf.clear(); - stack.push(Value::Nil); - ip += 1; continue; - } - // ── native_token_* builtins — global token buffer ───────── - "native_tokens_init" => { - // native_tokens_init(token_list) — store token list globally - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, _ => vec![], - }; - self.token_buf = list; - stack.push(Value::Nil); - ip += 1; continue; - } - "native_token_at" => { - // native_token_at(pos) → Map (token at pos, or empty map) - let pos = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - let v = self.token_buf.get(pos).cloned().unwrap_or(Value::Nil); - stack.push(v); - ip += 1; continue; - } - "native_token_len" => { - // native_token_len() → Int - stack.push(Value::Int(self.token_buf.len() as i64)); - ip += 1; continue; - } - // ── native_char_* builtins — global char buffer ─────────── - "native_chars_init" => { - // native_chars_init(str) — split string into chars, store globally - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, _ => String::new(), - }; - self.char_buf = s.chars().collect(); - stack.push(Value::Nil); - ip += 1; continue; - } - "native_char_at" => { - // native_char_at(i) → String (single char) - let i = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - let v = self.char_buf.get(i) - .map(|c| Value::Str(c.to_string())) - .unwrap_or(Value::Nil); - stack.push(v); - ip += 1; continue; - } - "native_char_len" => { - // native_char_len() → Int - stack.push(Value::Int(self.char_buf.len() as i64)); - ip += 1; continue; - } - _ => {} - } - let result = builtins::dispatch(name, *arity, &mut stack, &self.program_args, &mut self.global_state); - match result { - BuiltinResult::Handled | BuiltinResult::HttpServe => {} - BuiltinResult::Exit(code) => std::process::exit(code), - BuiltinResult::NotBuiltin => { - // Higher-order function helpers that need fn_table. - match name.as_str() { - "list_map" => { - let fn_name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, _ => String::new(), - }; - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, _ => vec![], - }; - let mut result_list = Vec::new(); - if let Some(&entry) = fn_table.get(&fn_name) { - for item in list { - let mapped = self.call_fn(instructions, &fn_table, entry, vec![item]); - result_list.push(mapped); - } - } - stack.push(Value::List(result_list)); - } - "list_filter" => { - let fn_name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, _ => String::new(), - }; - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, _ => vec![], - }; - let mut result_list = Vec::new(); - if let Some(&entry) = fn_table.get(&fn_name) { - for item in list { - let keep = self.call_fn(instructions, &fn_table, entry, vec![item.clone()]); - if matches!(keep, Value::Bool(true)) { - result_list.push(item); - } - } - } - stack.push(Value::List(result_list)); - } - "list_reduce" => { - let fn_name = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, _ => String::new(), - }; - let init = stack.pop().unwrap_or(Value::Nil); - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, _ => vec![], - }; - let mut acc = init; - if let Some(&entry) = fn_table.get(&fn_name) { - for item in list { - acc = self.call_fn(instructions, &fn_table, entry, vec![acc, item]); - } - } - stack.push(acc); - } - "fn_ref" => { - let name_val = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, _ => String::new(), - }; - stack.push(Value::Str(name_val)); - } - _ => { - if let Some(&entry) = fn_table.get(name.as_str()) { - // Move (not clone) caller's locals into the frame; - // the callee starts with a fresh locals HashMap. - let saved = std::mem::take(&mut locals); - call_stack.push(CallFrame { return_ip: ip + 1, saved_locals: saved }); - ip = entry; - continue; - } - stack.push(Value::Nil); - } - } - } - } - } - - Bytecode::Return => { - if let Some(frame) = call_stack.pop() { - locals = frame.saved_locals; - ip = frame.return_ip; - continue; - } else { - break; - } - } - - // ── Control flow ────────────────────────────────────────────── - Bytecode::Jump(offset) => { - ip = (ip as i32 + 1 + offset) as usize; - continue; - } - Bytecode::JumpIf(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if matches!(cond, Value::Bool(true)) { - ip = (ip as i32 + 1 + offset) as usize; - continue; - } - } - Bytecode::JumpIfNot(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - ip = (ip as i32 + 1 + offset) as usize; - continue; - } - } - - // ── Fields & indexing ───────────────────────────────────────── - Bytecode::GetField(field) => { - let obj = stack.pop().unwrap_or(Value::Nil); - let v = match obj { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - Value::Struct { fields, .. } => fields.iter() - .find(|(n, _)| n == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - _ => Value::Nil, - }; - stack.push(v); - } - 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::Map(pairs), Value::Str(key)) => { - let v = pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| v.clone()) - .unwrap_or(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::BuildMap(n) => { - let mut pairs = Vec::new(); - for _ in 0..*n { - let val = stack.pop().unwrap_or(Value::Nil); - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, - other => other.to_string(), - }; - pairs.push((key, val)); - } - pairs.reverse(); - stack.push(Value::Map(pairs)); - } - Bytecode::BuildList(n) => { - let mut items: Vec = (0..*n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect(); - items.reverse(); - stack.push(Value::List(items)); - } - Bytecode::BuildStruct { type_name, fields } => { - let n = fields.len(); - let mut field_values: Vec = (0..n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect(); - field_values.reverse(); - let struct_fields: Vec<(String, Value)> = fields.iter().cloned() - .zip(field_values.into_iter()) - .collect(); - stack.push(Value::Struct { type_name: type_name.clone(), fields: struct_fields }); - } - Bytecode::SetField(field) => { - let val = stack.pop().unwrap_or(Value::Nil); - match stack.last_mut() { - Some(Value::Map(pairs)) => { - if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - pairs.push((field.clone(), val)); - } - } - Some(Value::Struct { fields, .. }) => { - if let Some(entry) = fields.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - fields.push((field.clone(), val)); - } - } - _ => {} - } - } - - // ── Special instructions ────────────────────────────────────── - Bytecode::Halt => break, - Bytecode::Nop => {} - Bytecode::SealedBegin | Bytecode::SealedEnd => {} - Bytecode::Activate { type_name, query } => { - let results = engram_activate_search(type_name, query); - stack.push(Value::List(results)); - } - Bytecode::Reason { query } => { - let text = soma_reason(query); - stack.push(Value::Str(text)); - } - Bytecode::TraceBegin { label } => { - let start_ms = now_ms(); - locals.insert(format!("__trace_start_{label}__"), Value::Int(start_ms)); - } - Bytecode::TraceEnd { label } => { - let now = now_ms(); - let start = match locals.get(&format!("__trace_start_{label}__")) { - Some(Value::Int(n)) => *n, - _ => now, - }; - eprintln!("[trace] {label}: {}ms", now.saturating_sub(start)); - } - Bytecode::ContractCheck { message } => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - eprintln!("contract violation: {message}"); - std::process::exit(1); - } - } - Bytecode::Parallel { entries } => { - use std::sync::Arc; - let instr_arc = Arc::new(instructions.to_vec()); - let fn_arc = Arc::new(fn_table.clone()); - let mut handles: Vec<(String, std::thread::JoinHandle)> = Vec::new(); - for (name, entry_ip) in entries { - let instr_clone = instr_arc.clone(); - let ft_clone = fn_arc.clone(); - let ep = *entry_ip; - let h = std::thread::spawn(move || { - let mut sub_vm = ElVm::new(); - sub_vm.call_fn_with_table(&instr_clone, &ft_clone, ep, vec![]) - }); - handles.push((name.clone(), h)); - } - let pairs: Vec<(String, Value)> = handles.into_iter() - .map(|(name, h)| (name, h.join().unwrap_or(Value::Nil))) - .collect(); - stack.push(Value::Map(pairs)); - } - Bytecode::DeployFn { fn_name, route, target } => { - let soma_url = std::env::var("SOMA_URL") - .unwrap_or_else(|_| "https://neuron.neurontechnologies.ai".into()); - let op_key = std::env::var("SOMA_OPERATOR_KEY").unwrap_or_default(); - let body = serde_json::json!({ "fn_name": fn_name, "route": route, "target": target }); - let resp = reqwest::blocking::Client::new() - .post(format!("{soma_url}/v1/deploy")) - .header("Authorization", format!("Bearer {op_key}")) - .json(&body) - .send() - .and_then(|r| r.text()) - .unwrap_or_else(|e| format!("{{\"error\":\"{e}}}" )); - eprintln!("[deploy] {fn_name} -> {route} via {target}: {resp}"); - stack.push(Value::Str(resp)); - } - } - ip += 1; - } - - stack.pop().unwrap_or(Value::Nil) - } - - // ── Internal helpers ────────────────────────────────────────────────────── - - /// Call a user-defined function at `entry` IP with the given arguments. - /// - /// Arguments should be in left-to-right order; the function body will - /// store them via `StoreLocal` in reverse order. - pub fn call_fn( - &mut self, - instructions: &[Bytecode], - fn_table: &HashMap, - entry: usize, - args: Vec, - ) -> Value { - self.call_fn_with_table(instructions, fn_table, entry, args) - } - - fn call_fn_with_table( - &mut self, - instructions: &[Bytecode], - fn_table: &HashMap, - entry: usize, - args: Vec, - ) -> Value { - // Build a minimal initial stack from the args. - // The function body expects args to be on the stack (pushed left-to-right), - // then the body does StoreLocal in reverse parameter order. - let mut stack: Vec = args; - let mut locals: HashMap = HashMap::new(); - let mut call_stack: Vec = Vec::new(); - let mut ip = entry; - - while ip < instructions.len() { - match &instructions[ip] { - Bytecode::Push(v) => stack.push(v.clone()), - Bytecode::Pop => { stack.pop(); } - Bytecode::Dup => { - if let Some(top) = stack.last().cloned() { stack.push(top); } - } - Bytecode::Add => { - 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::Int(x + y), - (Value::Float(x), Value::Float(y)) => Value::Float(x + y), - (Value::Str(x), Value::Str(y)) => Value::Str(x + &y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 + y), - (Value::Float(x), Value::Int(y)) => Value::Float(x + y as f64), - _ => Value::Nil, - }); - } - Bytecode::Sub => { - 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::Int(x - y), - (Value::Float(x), Value::Float(y)) => Value::Float(x - y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 - y), - (Value::Float(x), Value::Int(y)) => Value::Float(x - y as f64), - _ => Value::Nil, - }); - } - Bytecode::Mul => { - 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::Int(x * y), - (Value::Float(x), Value::Float(y)) => Value::Float(x * y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 * y), - (Value::Float(x), Value::Int(y)) => Value::Float(x * y as f64), - _ => Value::Nil, - }); - } - Bytecode::Div => { - 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)) if y != 0 => Value::Int(x / y), - (Value::Float(x), Value::Float(y)) => Value::Float(x / y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 / y), - (Value::Float(x), Value::Int(y)) => Value::Float(x / y as f64), - _ => Value::Nil, - }); - } - Bytecode::Mod => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(1)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { - (Value::Int(x), Value::Int(y)) => if y == 0 { Value::Int(0) } else { Value::Int(x % y) }, - (Value::Float(x), Value::Float(y)) => Value::Float(x % y), - (Value::Int(x), Value::Float(y)) => Value::Float(x as f64 % y), - (Value::Float(x), Value::Int(y)) => Value::Float(x % y as f64), - _ => Value::Int(0), - }); - } - Bytecode::BitAnd => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x & y), _ => Value::Int(0) }); - } - Bytecode::BitOr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x | y), _ => Value::Int(0) }); - } - Bytecode::BitXor => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x ^ y), _ => Value::Int(0) }); - } - Bytecode::BitNot => { - let a = stack.pop().unwrap_or(Value::Int(0)); - stack.push(match a { Value::Int(x) => Value::Int(!x), _ => Value::Int(0) }); - } - Bytecode::Shl => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x << (y as u32)), _ => Value::Int(0) }); - } - Bytecode::Shr => { - let (b, a) = (stack.pop().unwrap_or(Value::Int(0)), stack.pop().unwrap_or(Value::Int(0))); - stack.push(match (a, b) { (Value::Int(x), Value::Int(y)) => Value::Int(x >> (y as u32)), _ => Value::Int(0) }); - } - Bytecode::Eq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - 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(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Less)); - } - Bytecode::Gt => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) == std::cmp::Ordering::Greater)); - } - Bytecode::LtEq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Greater)); - } - Bytecode::GtEq => { - let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil)); - stack.push(Value::Bool(cmp_values(&a, &b) != std::cmp::Ordering::Less)); - } - 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 => { - let v = stack.pop().unwrap_or(Value::Nil); - stack.push(Value::Bool(!matches!(v, Value::Bool(true)))); - } - Bytecode::StoreLocal(name) => { - let v = stack.pop().unwrap_or(Value::Nil); - locals.insert(name.clone(), v); - } - Bytecode::LoadLocal(name) => { - let v = locals.get(name).cloned().unwrap_or(Value::Nil); - stack.push(v); - } - Bytecode::Call { name, arity } => { - // ── native_instr_* builtins — global instruction accumulator ─ - match name.as_str() { - "native_instr_push" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, v => v.to_string(), - }; - self.instr_buf.push(s); - stack.push(Value::Nil); - ip += 1; continue; - } - "native_instr_len" => { - stack.push(Value::Int(self.instr_buf.len() as i64)); - ip += 1; continue; - } - "native_instr_get" => { - let idx = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - let v = self.instr_buf.get(idx) - .cloned() - .map(Value::Str) - .unwrap_or(Value::Nil); - stack.push(v); - ip += 1; continue; - } - "native_instr_patch" => { - let new_instr = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, v => v.to_string(), - }; - let idx = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - if idx < self.instr_buf.len() { - self.instr_buf[idx] = new_instr; - } - stack.push(Value::Nil); - ip += 1; continue; - } - "native_instr_all" => { - let list: Vec = self.instr_buf.iter() - .map(|s| Value::Str(s.clone())) - .collect(); - stack.push(Value::List(list)); - ip += 1; continue; - } - "native_instr_reset" => { - self.instr_buf.clear(); - stack.push(Value::Nil); - ip += 1; continue; - } - // ── native_token_* builtins — global token buffer ───────── - "native_tokens_init" => { - let list = match stack.pop().unwrap_or(Value::Nil) { - Value::List(l) => l, _ => vec![], - }; - self.token_buf = list; - stack.push(Value::Nil); - ip += 1; continue; - } - "native_token_at" => { - let pos = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - let v = self.token_buf.get(pos).cloned().unwrap_or(Value::Nil); - stack.push(v); - ip += 1; continue; - } - "native_token_len" => { - stack.push(Value::Int(self.token_buf.len() as i64)); - ip += 1; continue; - } - // ── native_char_* builtins — global char buffer ─────────── - "native_chars_init" => { - let s = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, _ => String::new(), - }; - self.char_buf = s.chars().collect(); - stack.push(Value::Nil); - ip += 1; continue; - } - "native_char_at" => { - let i = match stack.pop().unwrap_or(Value::Nil) { - Value::Int(i) => i as usize, _ => 0, - }; - let v = self.char_buf.get(i) - .map(|c| Value::Str(c.to_string())) - .unwrap_or(Value::Nil); - stack.push(v); - ip += 1; continue; - } - "native_char_len" => { - stack.push(Value::Int(self.char_buf.len() as i64)); - ip += 1; continue; - } - _ => {} - } - let result = builtins::dispatch(name, *arity, &mut stack, &self.program_args, &mut self.global_state); - match result { - BuiltinResult::Handled | BuiltinResult::HttpServe => {} - BuiltinResult::Exit(code) => std::process::exit(code), - BuiltinResult::NotBuiltin => { - match name.as_str() { - "fn_ref" => { - let n = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, _ => String::new(), - }; - stack.push(Value::Str(n)); - } - _ => { - if let Some(&entry2) = fn_table.get(name.as_str()) { - // Move (not clone) caller's locals into the frame. - let saved = std::mem::take(&mut locals); - call_stack.push(CallFrame { return_ip: ip + 1, saved_locals: saved }); - ip = entry2; - continue; - } - stack.push(Value::Nil); - } - } - } - } - } - Bytecode::Return => { - if let Some(frame) = call_stack.pop() { - locals = frame.saved_locals; - ip = frame.return_ip; - continue; - } else { - break; - } - } - Bytecode::Jump(offset) => { - ip = (ip as i32 + 1 + offset) as usize; - continue; - } - Bytecode::JumpIf(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if matches!(cond, Value::Bool(true)) { - ip = (ip as i32 + 1 + offset) as usize; - continue; - } - } - Bytecode::JumpIfNot(offset) => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - ip = (ip as i32 + 1 + offset) as usize; - continue; - } - } - Bytecode::GetField(field) => { - let obj = stack.pop().unwrap_or(Value::Nil); - let v = match obj { - Value::Map(pairs) => pairs.iter() - .find(|(k, _)| k == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - Value::Struct { fields, .. } => fields.iter() - .find(|(n, _)| n == field) - .map(|(_, v)| v.clone()) - .unwrap_or(Value::Nil), - _ => Value::Nil, - }; - stack.push(v); - } - 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::Map(pairs), Value::Str(key)) => { - let v = pairs.iter() - .find(|(k, _)| k == &key) - .map(|(_, v)| v.clone()) - .unwrap_or(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::BuildMap(n) => { - let mut pairs = Vec::new(); - for _ in 0..*n { - let val = stack.pop().unwrap_or(Value::Nil); - let key = match stack.pop().unwrap_or(Value::Nil) { - Value::Str(s) => s, other => other.to_string(), - }; - pairs.push((key, val)); - } - pairs.reverse(); - stack.push(Value::Map(pairs)); - } - Bytecode::BuildList(n) => { - let mut items: Vec = (0..*n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect(); - items.reverse(); - stack.push(Value::List(items)); - } - Bytecode::BuildStruct { type_name, fields } => { - let n = fields.len(); - let mut field_values: Vec = (0..n).map(|_| stack.pop().unwrap_or(Value::Nil)).collect(); - field_values.reverse(); - let struct_fields: Vec<(String, Value)> = fields.iter().cloned() - .zip(field_values.into_iter()) - .collect(); - stack.push(Value::Struct { type_name: type_name.clone(), fields: struct_fields }); - } - Bytecode::SetField(field) => { - let val = stack.pop().unwrap_or(Value::Nil); - match stack.last_mut() { - Some(Value::Map(pairs)) => { - if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - pairs.push((field.clone(), val)); - } - } - Some(Value::Struct { fields, .. }) => { - if let Some(entry) = fields.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - fields.push((field.clone(), val)); - } - } - _ => {} - } - } - Bytecode::Halt => break, - Bytecode::Nop => {} - Bytecode::SealedBegin | Bytecode::SealedEnd => {} - Bytecode::Activate { type_name, query } => { - let results = engram_activate_search(type_name, query); - stack.push(Value::List(results)); - } - Bytecode::Reason { query } => { - let text = soma_reason(query); - stack.push(Value::Str(text)); - } - Bytecode::TraceBegin { label } => { - let ms = now_ms(); - locals.insert(format!("__trace_start_{label}__"), Value::Int(ms)); - } - Bytecode::TraceEnd { label } => { - let now = now_ms(); - let start = match locals.get(&format!("__trace_start_{label}__")) { - Some(Value::Int(n)) => *n, _ => now, - }; - eprintln!("[trace] {label}: {}ms", now.saturating_sub(start)); - } - Bytecode::ContractCheck { message } => { - let cond = stack.pop().unwrap_or(Value::Nil); - if !matches!(cond, Value::Bool(true)) { - eprintln!("contract violation: {message}"); - std::process::exit(1); - } - } - _ => {} - } - ip += 1; - } - - stack.pop().unwrap_or(Value::Nil) - } - - // ── Scan pass ───────────────────────────────────────────────────────────── - - /// Scan the instruction stream to build a function name → entry-IP table. - /// - /// The codegen emits `Push(Int(entry)) StoreLocal("__fn_")` stanzas - /// immediately after each function body. This pass collects them. - pub fn scan_fn_table(instructions: &[Bytecode]) -> HashMap { - let mut table = HashMap::new(); - let mut i = 0usize; - while i < instructions.len() { - if let Bytecode::Push(Value::Int(n)) = &instructions[i] { - if i + 1 < instructions.len() { - if let Bytecode::StoreLocal(name) = &instructions[i + 1] { - if let Some(fn_name) = name.strip_prefix("__fn_") { - table.insert(fn_name.to_string(), *n as usize); - } - } - } - } - i += 1; - } - table - } -} - -// ── Utility functions ───────────────────────────────────────────────────────── - -fn cmp_values(a: &Value, b: &Value) -> std::cmp::Ordering { - match (a, b) { - (Value::Int(x), Value::Int(y)) => x.cmp(y), - (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal), - (Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal), - (Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(std::cmp::Ordering::Equal), - (Value::Str(x), Value::Str(y)) => x.cmp(y), - _ => std::cmp::Ordering::Equal, - } -} - -fn now_ms() -> i64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis() as i64) - .unwrap_or(0) -} - -fn engram_activate_search(type_name: &str, query: &str) -> Vec { - let engram_url = std::env::var("ENGRAM_URL") - .unwrap_or_else(|_| "http://localhost:8742".to_string()); - let api_key = std::env::var("ENGRAM_API_KEY").unwrap_or_default(); - let body = serde_json::json!({ "query": query, "limit": 20 }).to_string(); - let client = reqwest::blocking::Client::new(); - let mut req = client.post(format!("{engram_url}/search")) - .header("Content-Type", "application/json") - .body(body); - if !api_key.is_empty() { - req = req.header("Authorization", format!("Bearer {api_key}")); - } - req.send() - .and_then(|r| r.json::()) - .ok() - .and_then(|v| v.as_array().cloned()) - .unwrap_or_default() - .iter() - .map(json_to_value) - .collect() -} - -fn soma_reason(query: &str) -> String { - let soma_url = std::env::var("SOMA_URL") - .unwrap_or_else(|_| "https://neuron.neurontechnologies.ai".into()); - let op_key = std::env::var("SOMA_OPERATOR_KEY").unwrap_or_default(); - let body = serde_json::json!({ - "model": "neuron", - "messages": [{"role": "user", "content": query}], - "max_tokens": 500 - }); - reqwest::blocking::Client::new() - .post(format!("{soma_url}/v1/chat/completions")) - .header("Authorization", format!("Bearer {op_key}")) - .json(&body) - .send() - .and_then(|r| r.json::()) - .ok() - .and_then(|v| v["choices"][0]["message"]["content"].as_str().map(|s| s.to_string())) - .unwrap_or_else(|| "[soma unavailable]".into()) -} - -pub fn json_to_value(v: &serde_json::Value) -> Value { - match v { - serde_json::Value::String(s) => Value::Str(s.clone()), - serde_json::Value::Number(n) => { - if let Some(i) = n.as_i64() { Value::Int(i) } - else if let Some(f) = n.as_f64() { Value::Float(f) } - else { Value::Str(n.to_string()) } - } - serde_json::Value::Bool(b) => Value::Bool(*b), - serde_json::Value::Null => Value::Nil, - serde_json::Value::Array(a) => Value::List(a.iter().map(json_to_value).collect()), - serde_json::Value::Object(o) => { - let fields = o.iter().map(|(k, v)| (k.clone(), json_to_value(v))).collect(); - Value::Struct { type_name: "Object".to_string(), fields } - } - } -} diff --git a/_archive/rust-bootstrap/engrams/el-wasm/Cargo.toml b/_archive/rust-bootstrap/engrams/el-wasm/Cargo.toml deleted file mode 100644 index 423fb38..0000000 --- a/_archive/rust-bootstrap/engrams/el-wasm/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "el-wasm" -description = "Engram language WebAssembly runtime — runs .el programs natively in browsers" -version.workspace = true -edition.workspace = true -license.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -el-compiler = { workspace = true } -wasm-bindgen = { workspace = true, optional = true } -serde = { workspace = true } -serde_json = { workspace = true } -getrandom = { workspace = true, optional = true } - -[features] -# Enable the wasm-bindgen JS API. Pass `--features wasm` to wasm-pack. -wasm = ["dep:wasm-bindgen", "dep:getrandom"] - -[dev-dependencies] -wasm-bindgen-test = "0.3" diff --git a/_archive/rust-bootstrap/engrams/el-wasm/build.sh b/_archive/rust-bootstrap/engrams/el-wasm/build.sh deleted file mode 100755 index 653f7ed..0000000 --- a/_archive/rust-bootstrap/engrams/el-wasm/build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Build the el WASM package. -# -# Requires wasm-pack: -# cargo install wasm-pack -# -# Output: crates/el-wasm/pkg/ -# el_wasm_bg.wasm — the compiled WebAssembly module -# el_wasm.js — ES module JS bindings generated by wasm-bindgen -# el_wasm.d.ts — TypeScript type definitions -# package.json — npm package metadata - -set -euo pipefail -cd "$(dirname "$0")" - -echo "Building el WASM runtime..." -wasm-pack build --target web --out-dir pkg -- --features wasm - -echo "" -echo "Done. Output in crates/el-wasm/pkg/" -echo "" -ls -lh pkg/*.wasm pkg/*.js 2>/dev/null || ls pkg/ diff --git a/_archive/rust-bootstrap/engrams/el-wasm/js/pwa-manifest.json b/_archive/rust-bootstrap/engrams/el-wasm/js/pwa-manifest.json deleted file mode 100644 index ffd8452..0000000 --- a/_archive/rust-bootstrap/engrams/el-wasm/js/pwa-manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "Neuron", - "short_name": "Neuron", - "description": "Neuron — AI companion powered by el", - "start_url": "/", - "display": "standalone", - "background_color": "#0a0a0f", - "theme_color": "#6c7fff", - "orientation": "portrait-primary", - "icons": [ - { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, - { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } - ], - "categories": ["productivity", "utilities"], - "lang": "en-US" -} diff --git a/_archive/rust-bootstrap/engrams/el-wasm/js/runtime.js b/_archive/rust-bootstrap/engrams/el-wasm/js/runtime.js deleted file mode 100644 index b76ac65..0000000 --- a/_archive/rust-bootstrap/engrams/el-wasm/js/runtime.js +++ /dev/null @@ -1,72 +0,0 @@ -// Engram-lang WASM runtime loader -// Include this in any web app to run .el programs natively in the browser. -// -// Usage: -// import { initRuntime, runElc, evalSource } from '/js/runtime.js'; -// -// await initRuntime(); -// const result = await runElc('/programs/main.elc'); - -let wasmModule = null; - -/** - * Initialise the el WASM runtime. Safe to call multiple times — - * subsequent calls return the already-loaded module immediately. - * - * @param {string} wasmUrl - Path to the .wasm file (default: /pkg/el_wasm_bg.wasm) - * @returns {Promise} The loaded runtime API - */ -export async function initRuntime(wasmUrl = '/pkg/el_wasm_bg.wasm') { - if (wasmModule) return wasmModule; - - const { default: init, compile_source, load_and_run, eval: elEval, version } = - await import('/pkg/el_wasm.js'); - - await init(wasmUrl); - - wasmModule = { compile_source, load_and_run, eval: elEval, version }; - console.log(`el WASM runtime v${version()} loaded`); - return wasmModule; -} - -/** - * Fetch a pre-compiled .elc file from the server and execute it. - * - * The browser caches the .elc file automatically based on Cache-Control - * headers set by the server. New bytecode is available immediately on the - * next fetch — no app-store review required. - * - * @param {string} elcUrl - URL of the .elc bytecode file - * @returns {Promise} The JSON-deserialised result value - */ -export async function runElc(elcUrl) { - const rt = await initRuntime(); - const response = await fetch(elcUrl); - if (!response.ok) throw new Error(`Failed to fetch ${elcUrl}: ${response.status}`); - const bytes = new Uint8Array(await response.arrayBuffer()); - return JSON.parse(rt.load_and_run(bytes)); -} - -/** - * Compile el source and run it immediately. Useful for REPL and - * developer-mode execution where source is available at runtime. - * - * @param {string} source - Engram-lang source code - * @returns {Promise} The JSON-deserialised result value - */ -export async function evalSource(source) { - const rt = await initRuntime(); - return JSON.parse(rt.eval(source)); -} - -/** - * Compile el source to bytecode bytes without running it. - * The returned Uint8Array can be stored or uploaded as a .elc file. - * - * @param {string} source - Engram-lang source code - * @returns {Promise} Compiled bytecode bytes - */ -export async function compileSource(source) { - const rt = await initRuntime(); - return rt.compile_source(source); -} diff --git a/_archive/rust-bootstrap/engrams/el-wasm/js/service-worker.js b/_archive/rust-bootstrap/engrams/el-wasm/js/service-worker.js deleted file mode 100644 index e7647e3..0000000 --- a/_archive/rust-bootstrap/engrams/el-wasm/js/service-worker.js +++ /dev/null @@ -1,77 +0,0 @@ -// Service worker for the Neuron PWA powered by el. -// -// Caching strategy: -// /pkg/ — WASM runtime files, cache-first (content-addressed, change on version bump) -// *.elc — compiled bytecode, stale-while-revalidate (instant load, background update) -// -// The WASM runtime is fetched once and cached indefinitely. -// Engram programs (.elc) are served from cache immediately, then refreshed in -// the background so the next load gets the newest version — no user action needed. - -const CACHE_NAME = 'engram-v1'; -const WASM_CACHE = 'engram-wasm-v1'; - -// Files to pre-cache during service worker installation. -const PRECACHE = [ - '/pkg/el_wasm_bg.wasm', - '/pkg/el_wasm.js', -]; - -self.addEventListener('install', event => { - event.waitUntil( - caches.open(WASM_CACHE).then(cache => cache.addAll(PRECACHE)) - ); - // Take control immediately — don't wait for existing tabs to close. - self.skipWaiting(); -}); - -self.addEventListener('fetch', event => { - const url = new URL(event.request.url); - - // WASM runtime files: cache-first. - // These are large and rarely change; the version bump forces a new URL. - if (url.pathname.startsWith('/pkg/')) { - event.respondWith( - caches.match(event.request).then(cached => - cached || fetch(event.request).then(response => { - caches.open(WASM_CACHE).then(cache => - cache.put(event.request, response.clone()) - ); - return response; - }) - ) - ); - return; - } - - // Compiled bytecode (.elc): stale-while-revalidate. - // Respond immediately from cache, update in background. - if (url.pathname.endsWith('.elc')) { - event.respondWith( - caches.open(CACHE_NAME).then(async cache => { - const cached = await cache.match(event.request); - const fetchPromise = fetch(event.request).then(response => { - cache.put(event.request, response.clone()); - return response; - }); - return cached || fetchPromise; - }) - ); - return; - } -}); - -self.addEventListener('activate', event => { - // Purge old cache versions to reclaim storage. - event.waitUntil( - caches.keys().then(keys => - Promise.all( - keys - .filter(k => k !== CACHE_NAME && k !== WASM_CACHE) - .map(k => caches.delete(k)) - ) - ) - ); - // Claim all clients immediately. - self.clients.claim(); -}); diff --git a/_archive/rust-bootstrap/engrams/el-wasm/src/lib.rs b/_archive/rust-bootstrap/engrams/el-wasm/src/lib.rs deleted file mode 100644 index 0c39187..0000000 --- a/_archive/rust-bootstrap/engrams/el-wasm/src/lib.rs +++ /dev/null @@ -1,735 +0,0 @@ -//! el-wasm — Engram language WebAssembly runtime. -//! -//! Compiles the el compiler and (optionally) execution pipeline to -//! `wasm32-unknown-unknown`, exposing a JavaScript API via `wasm-bindgen`. -//! -//! # Build for browsers -//! -//! ```bash -//! wasm-pack build --target web --out-dir pkg -- --features wasm -//! ``` -//! -//! # JavaScript API -//! -//! ```js -//! import init, { compile_source, load_and_run, eval, version } from '/pkg/el_wasm.js'; -//! await init(); -//! const result = eval('1 + 2'); // => "3" -//! ``` -//! -//! # Architecture -//! -//! The WASM module exposes three entry points: -//! -//! - **`compile_source`** — source → `.elc` bytes (serialised bytecode) -//! - **`load_and_run`** — `.elc` bytes → JSON-encoded result value -//! - **`eval`** — source → JSON-encoded result value (compile + run in one step) -//! -//! The browser caches the `.wasm` file after the first load. Programs are -//! distributed as tiny `.elc` bytecode files fetched on demand, enabling a -//! PWA strategy that bypasses app-store review cycles. - -pub use el_compiler::{ - compile_to_bytecode, deserialize_bytecode, serialize_bytecode, Bytecode, CompileError, Value, -}; - -// ── WASM bindings ───────────────────────────────────────────────────────────── -// Only compiled when the `wasm` feature is active (i.e. wasm-pack builds). - -#[cfg(feature = "wasm")] -use wasm_bindgen::prelude::*; - -/// Initialize the WASM module. Call once from JavaScript before any other API. -/// -/// Sets up the panic hook so Rust panics appear as readable messages in the -/// browser developer console rather than opaque `unreachable` traps. -#[cfg(feature = "wasm")] -#[wasm_bindgen(start)] -pub fn init() { - // Redirect Rust panics to console.error in the browser. - std::panic::set_hook(Box::new(console_error_panic_hook)); -} - -/// Forward panics to the browser console. -#[cfg(feature = "wasm")] -fn console_error_panic_hook(info: &std::panic::PanicHookInfo<'_>) { - let msg = info.to_string(); - web_sys_log(&msg); -} - -#[cfg(feature = "wasm")] -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console, js_name = error)] - fn web_sys_log(s: &str); -} - -/// Compile el source code to bytecode bytes (`.elc` format). -/// -/// Returns the raw bytecode bytes on success, or throws a JS error string -/// describing the first compilation error. -/// -/// The returned bytes can be cached by the browser and later passed to -/// `load_and_run` to execute the program. -#[cfg(feature = "wasm")] -#[wasm_bindgen] -pub fn compile_source(source: &str) -> Result, JsValue> { - compile_source_inner(source).map_err(|e| JsValue::from_str(&e)) -} - -/// Load pre-compiled bytecode (`.elc` bytes) and execute it. -/// -/// Returns the JSON-encoded final value from the program, or throws on error. -/// The result is always valid JSON — use `JSON.parse(result)` in JavaScript. -#[cfg(feature = "wasm")] -#[wasm_bindgen] -pub fn load_and_run(bytecode_bytes: &[u8]) -> Result { - load_and_run_inner(bytecode_bytes).map_err(|e| JsValue::from_str(&e)) -} - -/// Compile and run el source in one step. -/// -/// Equivalent to `load_and_run(compile_source(source))`. Useful for REPL -/// and developer-mode execution where the source is available at runtime. -/// -/// Returns the JSON-encoded result, or throws a descriptive error string. -#[cfg(feature = "wasm")] -#[wasm_bindgen] -pub fn eval(source: &str) -> Result { - let bytes = compile_source_inner(source).map_err(|e| JsValue::from_str(&e))?; - load_and_run_inner(&bytes).map_err(|e| JsValue::from_str(&e)) -} - -/// Return the el runtime version string. -#[cfg(feature = "wasm")] -#[wasm_bindgen] -pub fn version() -> String { - env!("CARGO_PKG_VERSION").to_string() -} - -// ── Inner implementations (callable from Rust tests without wasm-bindgen) ───── - -/// Compile source to `.elc` bytes. Returns `Err(String)` on failure. -pub fn compile_source_inner(source: &str) -> Result, String> { - let (bytecode, _source_map) = - compile_to_bytecode(source).map_err(|e| e.to_string())?; - serialize_bytecode(&bytecode) -} - -/// Deserialise `.elc` bytes, execute the bytecode, return a JSON-encoded Value. -/// -/// The result is always clean JSON: integers as numbers, strings as strings, -/// booleans as booleans, nil as null, lists as arrays, maps as objects. -pub fn load_and_run_inner(bytecode_bytes: &[u8]) -> Result { - let bytecode = deserialize_bytecode(bytecode_bytes)?; - let result = run_bytecode(&bytecode)?; - let json_value = value_to_json(&result); - serde_json::to_string(&json_value).map_err(|e| format!("Serialize result error: {e}")) -} - -/// Convert an engram `Value` to a clean `serde_json::Value` for JS consumption. -/// -/// Maps engram types to natural JSON equivalents: -/// - `Int` → JSON number -/// - `Float` → JSON number -/// - `Str` → JSON string -/// - `Bool` → JSON boolean -/// - `Nil` → JSON null -/// - `List` → JSON array -/// - `Map` → JSON object -/// - `ResultOk(v)` → `{"ok": v}` -/// - `ResultErr(e)` → `{"err": e}` -pub fn value_to_json(v: &Value) -> serde_json::Value { - match v { - Value::Int(n) => serde_json::Value::Number(serde_json::Number::from(*n)), - Value::Float(f) => serde_json::Number::from_f64(*f) - .map(serde_json::Value::Number) - .unwrap_or(serde_json::Value::Null), - Value::Str(s) => serde_json::Value::String(s.clone()), - Value::Bool(b) => serde_json::Value::Bool(*b), - Value::Nil => serde_json::Value::Null, - Value::List(items) => { - serde_json::Value::Array(items.iter().map(value_to_json).collect()) - } - Value::Map(pairs) => { - let obj: serde_json::Map = pairs - .iter() - .map(|(k, v)| (k.clone(), value_to_json(v))) - .collect(); - serde_json::Value::Object(obj) - } - Value::ResultOk(inner) => { - serde_json::json!({ "ok": value_to_json(inner) }) - } - Value::ResultErr(inner) => { - serde_json::json!({ "err": value_to_json(inner) }) - } - } -} - -/// Execute a bytecode program on the engram stack machine. -/// -/// Returns the value left on the stack when `Halt` is reached, or `Value::Nil` -/// if the program is empty. -/// -/// # Supported instructions -/// -/// This is a pure stack machine — no I/O, no filesystem, no OS interaction — -/// which makes it safe to run inside WASM. Instructions that reference the -/// Engram runtime (`Activate`) return a placeholder `Nil` value; a full -/// runtime integration would supply a callback from JS. -pub fn run_bytecode(bytecode: &[Bytecode]) -> Result { - let mut stack: Vec = Vec::new(); - // Local variable environment (flat scope for now). - let mut locals: std::collections::HashMap = std::collections::HashMap::new(); - let mut ip: usize = 0; - - while ip < bytecode.len() { - let instr = &bytecode[ip]; - match instr { - // ── Stack ───────────────────────────────────────────────────────── - Bytecode::Push(v) => { - stack.push(v.clone()); - } - Bytecode::Pop => { - stack.pop(); - } - Bytecode::Dup => { - let top = stack.last().ok_or("DUP on empty stack")?.clone(); - stack.push(top); - } - - // ── Arithmetic ──────────────────────────────────────────────────── - Bytecode::Add => { - let (a, b) = pop2(&mut stack)?; - stack.push(arith_add(a, b)?); - } - Bytecode::Sub => { - let (a, b) = pop2(&mut stack)?; - stack.push(arith_sub(a, b)?); - } - Bytecode::Mul => { - let (a, b) = pop2(&mut stack)?; - stack.push(arith_mul(a, b)?); - } - Bytecode::Div => { - let (a, b) = pop2(&mut stack)?; - stack.push(arith_div(a, b)?); - } - - // ── Comparison ──────────────────────────────────────────────────── - Bytecode::Eq => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(values_eq(&a, &b))); - } - Bytecode::NotEq => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(!values_eq(&a, &b))); - } - Bytecode::Lt => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(cmp_values(&a, &b)? < 0)); - } - Bytecode::Gt => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(cmp_values(&a, &b)? > 0)); - } - Bytecode::LtEq => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(cmp_values(&a, &b)? <= 0)); - } - Bytecode::GtEq => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(cmp_values(&a, &b)? >= 0)); - } - - // ── Logical ─────────────────────────────────────────────────────── - Bytecode::And => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(is_truthy(&a) && is_truthy(&b))); - } - Bytecode::Or => { - let (a, b) = pop2(&mut stack)?; - stack.push(Value::Bool(is_truthy(&a) || is_truthy(&b))); - } - Bytecode::Not => { - let v = stack.pop().ok_or("NOT on empty stack")?; - stack.push(Value::Bool(!is_truthy(&v))); - } - - // ── Locals ──────────────────────────────────────────────────────── - Bytecode::LoadLocal(name) => { - let v = locals.get(name).cloned().unwrap_or(Value::Nil); - stack.push(v); - } - Bytecode::StoreLocal(name) => { - let v = stack.pop().ok_or("STORE on empty stack")?; - locals.insert(name.clone(), v); - } - - // ── Functions ───────────────────────────────────────────────────── - // The bytecode model stores function bodies inline and registers entry - // points as locals (`__fn_`). A full call-frame implementation - // would use a separate call stack; for WASM we handle the most common - // case of stdlib builtins and leave dynamic dispatch as a stub. - Bytecode::Call { name, arity } => { - let result = call_builtin(name, *arity, &mut stack)?; - stack.push(result); - } - Bytecode::Return => { - // Return leaves the value on the stack; the caller pops it. - // In this simplified VM we just continue execution. - break; - } - - // ── Control flow ────────────────────────────────────────────────── - Bytecode::Jump(offset) => { - ip = apply_offset(ip, *offset)?; - continue; // skip ip += 1 below - } - Bytecode::JumpIf(offset) => { - let v = stack.pop().ok_or("JUMPIF on empty stack")?; - if is_truthy(&v) { - ip = apply_offset(ip, *offset)?; - continue; - } - } - Bytecode::JumpIfNot(offset) => { - let v = stack.pop().ok_or("JUMPIFNOT on empty stack")?; - if !is_truthy(&v) { - ip = apply_offset(ip, *offset)?; - continue; - } - } - - // ── Fields & Indexing ───────────────────────────────────────────── - Bytecode::GetField(field) => { - let obj = stack.pop().ok_or("GETFIELD on empty stack")?; - let v = match &obj { - Value::Map(pairs) => pairs - .iter() - .find(|(k, _v)| k == field) - .map(|(_k, v)| v.clone()) - .unwrap_or(Value::Nil), - _ => Value::Nil, - }; - stack.push(v); - } - Bytecode::GetIndex => { - let idx = stack.pop().ok_or("GETINDEX: missing index")?; - let obj = stack.pop().ok_or("GETINDEX: missing object")?; - let v = match (&obj, &idx) { - (Value::List(items), Value::Int(i)) => { - let i = *i as usize; - items.get(i).cloned().unwrap_or(Value::Nil) - } - _ => Value::Nil, - }; - stack.push(v); - } - Bytecode::BuildMap(n) => { - let mut pairs = Vec::new(); - let n = *n as usize; - // Stack: key0, val0, key1, val1, ... (pushed in order) - // We collect from the top, so reverse at the end. - let start = stack.len().saturating_sub(n * 2); - let raw: Vec = stack.drain(start..).collect(); - for chunk in raw.chunks(2) { - if let [Value::Str(k), v] = chunk { - pairs.push((k.clone(), v.clone())); - } - } - stack.push(Value::Map(pairs)); - } - Bytecode::BuildStruct { fields, .. } => { - let mut pairs: Vec<(String, Value)> = Vec::new(); - let start = stack.len().saturating_sub(fields.len()); - let raw: Vec = stack.drain(start..).collect(); - for (field, val) in fields.iter().zip(raw.into_iter()) { - pairs.push((field.clone(), val)); - } - stack.push(Value::Map(pairs)); - } - Bytecode::SetField(field) => { - let val = stack.pop().ok_or("SETFIELD: missing value")?; - let obj = stack.pop().ok_or("SETFIELD: missing object")?; - let v = match obj { - Value::Map(mut pairs) => { - if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == field) { - entry.1 = val; - } else { - pairs.push((field.clone(), val)); - } - Value::Map(pairs) - } - other => other, - }; - stack.push(v); - } - - // ── Special ─────────────────────────────────────────────────────── - Bytecode::Activate { type_name, query } => { - // The Engram runtime integration is provided by the host JS environment. - // In a full implementation the JS host would register an `activate` callback. - // For now, return a placeholder list so programs using `activate` don't crash. - let _ = (type_name, query); - stack.push(Value::List(Vec::new())); - } - Bytecode::SealedBegin | Bytecode::SealedEnd | Bytecode::Nop => { - // No-ops in the pure VM. - } - Bytecode::Halt => { - break; - } - } - ip += 1; - } - - Ok(stack.pop().unwrap_or(Value::Nil)) -} - -// ── Stack helpers ───────────────────────────────────────────────────────────── - -fn pop2(stack: &mut Vec) -> Result<(Value, Value), String> { - let b = stack.pop().ok_or("stack underflow (right operand)")?; - let a = stack.pop().ok_or("stack underflow (left operand)")?; - Ok((a, b)) -} - -fn apply_offset(ip: usize, offset: i32) -> Result { - // offset is relative to the instruction *after* the jump - let target = (ip as i64) + 1 + (offset as i64); - if target < 0 { - return Err(format!("Jump to negative address {target}")); - } - Ok(target as usize) -} - -// ── Value helpers ───────────────────────────────────────────────────────────── - -fn is_truthy(v: &Value) -> bool { - match v { - Value::Bool(b) => *b, - Value::Nil => false, - Value::Int(0) => false, - _ => true, - } -} - -fn values_eq(a: &Value, b: &Value) -> bool { - match (a, b) { - (Value::Int(x), Value::Int(y)) => x == y, - (Value::Float(x), Value::Float(y)) => x == y, - (Value::Str(x), Value::Str(y)) => x == y, - (Value::Bool(x), Value::Bool(y)) => x == y, - (Value::Nil, Value::Nil) => true, - _ => false, - } -} - -/// Compare two values; returns negative / zero / positive like `Ord::cmp`. -fn cmp_values(a: &Value, b: &Value) -> Result { - match (a, b) { - (Value::Int(x), Value::Int(y)) => Ok(x.cmp(y) as i32), - (Value::Float(x), Value::Float(y)) => Ok(x.partial_cmp(y).map(|o| o as i32).unwrap_or(0)), - (Value::Str(x), Value::Str(y)) => Ok(x.cmp(y) as i32), - _ => Err(format!("Cannot compare {a:?} and {b:?}")), - } -} - -// ── Arithmetic helpers ──────────────────────────────────────────────────────── - -fn arith_add(a: Value, b: Value) -> Result { - match (a, b) { - (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x.wrapping_add(y))), - (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x + y)), - (Value::Str(x), Value::Str(y)) => Ok(Value::Str(x + &y)), - (a, b) => Err(format!("ADD: type mismatch {a:?} + {b:?}")), - } -} - -fn arith_sub(a: Value, b: Value) -> Result { - match (a, b) { - (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x.wrapping_sub(y))), - (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x - y)), - (a, b) => Err(format!("SUB: type mismatch {a:?} - {b:?}")), - } -} - -fn arith_mul(a: Value, b: Value) -> Result { - match (a, b) { - (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x.wrapping_mul(y))), - (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x * y)), - (a, b) => Err(format!("MUL: type mismatch {a:?} * {b:?}")), - } -} - -fn arith_div(a: Value, b: Value) -> Result { - match (a, b) { - (Value::Int(_), Value::Int(0)) => Err("Division by zero".to_string()), - (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x / y)), - (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x / y)), - (a, b) => Err(format!("DIV: type mismatch {a:?} / {b:?}")), - } -} - -// ── Builtin function dispatch ───────────────────────────────────────────────── - -fn call_builtin(name: &str, arity: u32, stack: &mut Vec) -> Result { - match name { - "__build_list__" => { - let n = arity as usize; - let start = stack.len().saturating_sub(n); - let items: Vec = stack.drain(start..).collect(); - Ok(Value::List(items)) - } - "print" | "println" => { - // In WASM, print is a no-op unless the host wires up a callback. - let n = arity as usize; - let start = stack.len().saturating_sub(n); - let _args: Vec = stack.drain(start..).collect(); - Ok(Value::Nil) - } - "len" => { - let n = arity as usize; - let start = stack.len().saturating_sub(n); - let mut args: Vec = stack.drain(start..).collect(); - let v = args.pop().unwrap_or(Value::Nil); - let len = match &v { - Value::List(items) => items.len() as i64, - Value::Str(s) => s.len() as i64, - Value::Map(pairs) => pairs.len() as i64, - _ => 0, - }; - Ok(Value::Int(len)) - } - _ => { - // Unknown function: consume args, return Nil. - let n = arity as usize; - let start = stack.len().saturating_sub(n); - let _: Vec = stack.drain(start..).collect(); - Ok(Value::Nil) - } - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - // ── Compile pipeline tests ──────────────────────────────────────────────── - - #[test] - fn test_compile_source_produces_bytes() { - let bytes = compile_source_inner("42").unwrap(); - assert!(!bytes.is_empty()); - } - - #[test] - fn test_roundtrip_bytecode_serialization() { - let source = "let x = 1 + 2"; - let bytes = compile_source_inner(source).unwrap(); - let (original, _) = compile_to_bytecode(source).unwrap(); - let restored = deserialize_bytecode(&bytes).unwrap(); - assert_eq!(original, restored); - } - - #[test] - fn test_compile_function_def() { - let source = r#"fn add(a: Int, b: Int) -> Int { a + b }"#; - let bytes = compile_source_inner(source).unwrap(); - assert!(!bytes.is_empty()); - } - - #[test] - fn test_compile_activate() { - let source = r#"activate User where "active users""#; - let (bytecode, _) = compile_to_bytecode(source).unwrap(); - assert!(bytecode - .iter() - .any(|b| matches!(b, Bytecode::Activate { .. }))); - } - - #[test] - fn test_serialize_deserialize_activate() { - let source = r#"activate User where "query""#; - let bytes = compile_source_inner(source).unwrap(); - let restored = deserialize_bytecode(&bytes).unwrap(); - assert!(restored - .iter() - .any(|b| matches!(b, Bytecode::Activate { .. }))); - } - - #[test] - fn test_compile_sealed_block() { - let source = "sealed { let x = 1 }"; - let (bytecode, _) = compile_to_bytecode(source).unwrap(); - assert!(bytecode - .iter() - .any(|b| matches!(b, Bytecode::SealedBegin))); - } - - #[test] - fn test_empty_program_compiles() { - let source = ""; - let (bytecode, _) = compile_to_bytecode(source).unwrap(); - assert!(matches!(bytecode.last(), Some(Bytecode::Halt))); - } - - #[test] - fn test_complex_program_compiles() { - let source = r#" - let x = 10 - let y = 20 - let z = x + y - "#; - let (bytecode, _) = compile_to_bytecode(source).unwrap(); - assert!(!bytecode.is_empty()); - } - - #[test] - fn test_bytecode_json_is_valid() { - let bytes = compile_source_inner("1 + 2").unwrap(); - let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); - assert!(json.is_array()); - } - - #[test] - fn test_version_string() { - assert!(!env!("CARGO_PKG_VERSION").is_empty()); - } - - // ── VM execution tests ──────────────────────────────────────────────────── - - #[test] - fn test_run_integer_literal() { - let result = load_and_run_inner(&compile_source_inner("42").unwrap()).unwrap(); - // The final value on the stack is the integer 42. - assert_eq!(result, "42"); - } - - #[test] - fn test_run_addition() { - let result = load_and_run_inner(&compile_source_inner("1 + 2").unwrap()).unwrap(); - assert_eq!(result, "3"); - } - - #[test] - fn test_run_string_literal() { - let result = - load_and_run_inner(&compile_source_inner(r#""hello""#).unwrap()).unwrap(); - assert_eq!(result, r#""hello""#); - } - - #[test] - fn test_run_boolean() { - let result = load_and_run_inner(&compile_source_inner("true").unwrap()).unwrap(); - assert_eq!(result, "true"); - } - - #[test] - fn test_run_let_binding_and_use() { - let source = "let x = 10\nx"; - let result = load_and_run_inner(&compile_source_inner(source).unwrap()).unwrap(); - assert_eq!(result, "10"); - } - - #[test] - fn test_run_arithmetic_chain() { - // 2 * 3 + 4 should be 10 (if parsed left-to-right) - let source = "2 * 3"; - let result = load_and_run_inner(&compile_source_inner(source).unwrap()).unwrap(); - assert_eq!(result, "6"); - } - - #[test] - fn test_run_activate_returns_list() { - let source = r#"activate User where "all""#; - let result = load_and_run_inner(&compile_source_inner(source).unwrap()).unwrap(); - // Activate returns an empty list placeholder in the pure VM. - assert_eq!(result, "[]"); - } - - #[test] - fn test_run_if_true_branch() { - let source = "if true { 1 } else { 2 }"; - let result = load_and_run_inner(&compile_source_inner(source).unwrap()).unwrap(); - assert_eq!(result, "1"); - } - - #[test] - fn test_run_if_false_branch() { - let source = "if false { 1 } else { 2 }"; - let result = load_and_run_inner(&compile_source_inner(source).unwrap()).unwrap(); - assert_eq!(result, "2"); - } - - #[test] - fn test_run_comparison_eq() { - let result = load_and_run_inner(&compile_source_inner("1 == 1").unwrap()).unwrap(); - assert_eq!(result, "true"); - } - - #[test] - fn test_run_comparison_neq() { - let result = load_and_run_inner(&compile_source_inner("1 != 2").unwrap()).unwrap(); - assert_eq!(result, "true"); - } - - #[test] - fn test_direct_run_empty_bytecode() { - let result = run_bytecode(&[]).unwrap(); - assert_eq!(result, Value::Nil); - } - - #[test] - fn test_direct_run_halt_only() { - let result = run_bytecode(&[Bytecode::Halt]).unwrap(); - assert_eq!(result, Value::Nil); - } - - #[test] - fn test_direct_run_push_halt() { - let result = run_bytecode(&[Bytecode::Push(Value::Int(99)), Bytecode::Halt]).unwrap(); - assert_eq!(result, Value::Int(99)); - } - - #[test] - fn test_direct_run_add() { - let bc = [ - Bytecode::Push(Value::Int(3)), - Bytecode::Push(Value::Int(4)), - Bytecode::Add, - Bytecode::Halt, - ]; - let result = run_bytecode(&bc).unwrap(); - assert_eq!(result, Value::Int(7)); - } - - #[test] - fn test_direct_run_string_concat() { - let bc = [ - Bytecode::Push(Value::Str("hello ".to_string())), - Bytecode::Push(Value::Str("world".to_string())), - Bytecode::Add, - Bytecode::Halt, - ]; - let result = run_bytecode(&bc).unwrap(); - assert_eq!(result, Value::Str("hello world".to_string())); - } - - #[test] - fn test_direct_run_jump() { - // Jump over a push, land on the second push. - let bc = [ - Bytecode::Jump(1), // ip=0 → skip 1 → ip becomes 2 - Bytecode::Push(Value::Int(0)), // ip=1 — skipped - Bytecode::Push(Value::Int(42)), // ip=2 - Bytecode::Halt, - ]; - let result = run_bytecode(&bc).unwrap(); - assert_eq!(result, Value::Int(42)); - } -}