Compare commits
13 Commits
26d340b5a3
...
552e7a4972
| Author | SHA1 | Date | |
|---|---|---|---|
| 552e7a4972 | |||
| 49ab70829a | |||
| 8a001a8f26 | |||
| 0cf8f2a4a2 | |||
| dfcfda1f48 | |||
| 3ee3529ef6 | |||
| b02807ebf4 | |||
| 87c1fb1bbd | |||
| 204950357f | |||
| 3a46299404 | |||
| bc1e5aa8a1 | |||
| 33b4f57faf | |||
| ba3b713f8c |
501
Cargo.lock
generated
501
Cargo.lock
generated
@@ -2,6 +2,18 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.101"
|
version = "1.0.101"
|
||||||
@@ -14,12 +26,29 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.21.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -32,12 +61,27 @@ version = "2.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -107,6 +151,15 @@ version = "0.8.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.15"
|
version = "0.5.15"
|
||||||
@@ -122,6 +175,27 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
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",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -170,6 +244,24 @@ version = "3.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -236,6 +328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -270,6 +363,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -277,6 +371,16 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -285,7 +389,19 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -320,6 +436,15 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -335,12 +460,30 @@ version = "0.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "home"
|
name = "home"
|
||||||
version = "0.5.12"
|
version = "0.5.12"
|
||||||
@@ -575,6 +718,27 @@ version = "0.2.182"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -587,6 +751,15 @@ version = "0.8.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.29"
|
||||||
@@ -625,8 +798,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"lsp-server",
|
"lsp-server",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"rand",
|
"postgres",
|
||||||
|
"rand 0.8.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rusqlite",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -635,6 +810,16 @@ dependencies = [
|
|||||||
"tiny_http",
|
"tiny_http",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md-5"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -654,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -696,6 +881,24 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-system-configuration"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
|
||||||
|
dependencies = [
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -746,12 +949,54 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||||
|
dependencies = [
|
||||||
|
"phf_shared",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@@ -770,6 +1015,49 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres"
|
||||||
|
version = "0.19.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7c48ece1c6cda0db61b058c1721378da76855140e9214339fa1317decacb176"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tokio-postgres",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres-protocol"
|
||||||
|
version = "0.6.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ee9dd5fe15055d2b6806f4736aa0c9637217074e224bbec46d4041b91bb9491"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"hmac",
|
||||||
|
"md-5",
|
||||||
|
"memchr",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"sha2",
|
||||||
|
"stringprep",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres-types"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54b858f82211e84682fecd373f68e1ceae642d8d751a1ebd13f33de6257b3e20"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"postgres-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -839,8 +1127,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -850,7 +1148,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"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]]
|
[[package]]
|
||||||
@@ -862,13 +1170,31 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"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 = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.27"
|
version = "0.11.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.7",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -902,6 +1228,20 @@ dependencies = [
|
|||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"fallible-iterator 0.3.0",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@@ -921,7 +1261,7 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -967,6 +1307,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.6.0"
|
version = "3.6.0"
|
||||||
@@ -1062,12 +1408,29 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@@ -1106,6 +1469,23 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stringprep"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
"unicode-properties",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.115"
|
version = "2.0.115"
|
||||||
@@ -1210,6 +1590,21 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.49.0"
|
version = "1.49.0"
|
||||||
@@ -1234,6 +1629,32 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-postgres"
|
||||||
|
version = "0.7.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcea47c8f71744367793f16c2db1f11cb859d28f436bdb4ca9193eb1f787ee42"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"phf",
|
||||||
|
"pin-project-lite",
|
||||||
|
"postgres-protocol",
|
||||||
|
"postgres-types",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"socket2 0.6.2",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"whoami",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@@ -1278,12 +1699,39 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-properties"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@@ -1333,6 +1781,12 @@ version = "0.2.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -1348,6 +1802,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.14.7+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||||
|
dependencies = [
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.2+wasi-0.2.9"
|
version = "1.0.2+wasi-0.2.9"
|
||||||
@@ -1366,6 +1829,15 @@ dependencies = [
|
|||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasite"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42"
|
||||||
|
dependencies = [
|
||||||
|
"wasi 0.14.7+wasi-0.2.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.108"
|
version = "0.2.108"
|
||||||
@@ -1469,6 +1941,19 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "whoami"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6a5b12f9df4f978d2cfdb1bd3bac52433f44393342d7ee9c25f5a1c14c0f45d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
"objc2-system-configuration",
|
||||||
|
"wasite",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ serde_json = "1"
|
|||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||||
|
postgres = "0.19"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
64
README.md
64
README.md
@@ -120,17 +120,34 @@ fn main(): Unit with {Console} =
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**Current Phase: Prototype Implementation**
|
**Core Language:** Complete
|
||||||
|
- Full type system with Hindley-Milner inference
|
||||||
|
- Pattern matching with exhaustiveness checking
|
||||||
|
- Algebraic data types, generics, string interpolation
|
||||||
|
- Effect system with handlers
|
||||||
|
- Behavioral types (pure, total, idempotent, deterministic, commutative)
|
||||||
|
- Schema evolution with version tracking
|
||||||
|
|
||||||
The interpreter is functional with:
|
**Compilation Targets:**
|
||||||
- Core language (functions, closures, pattern matching)
|
- Interpreter (full-featured)
|
||||||
- Effect system (declare effects, use operations, handle with handlers)
|
- C backend (functions, closures, pattern matching, lists, reference counting)
|
||||||
- Type checking with effect tracking
|
- JavaScript backend (full language, browser & Node.js, DOM, TEA runtime)
|
||||||
- REPL for interactive development
|
|
||||||
|
**Tooling:**
|
||||||
|
- REPL with history
|
||||||
|
- LSP server (diagnostics, hover, completions, go-to-definition)
|
||||||
|
- Formatter (`lux fmt`)
|
||||||
|
- Package manager (`lux pkg`)
|
||||||
|
- Watch mode / hot reload
|
||||||
|
|
||||||
|
**Standard Library:**
|
||||||
|
- String, List, Option, Result, Math, JSON modules
|
||||||
|
- Console, File, Http, Random, Time, Process effects
|
||||||
|
- SQL effect (SQLite with transactions)
|
||||||
|
- DOM effect (40+ browser operations)
|
||||||
|
|
||||||
See:
|
See:
|
||||||
- [SKILLS.md](./SKILLS.md) — Language specification and implementation roadmap
|
- [docs/ROADMAP.md](./docs/ROADMAP.md) — Development roadmap and feature status
|
||||||
- [docs/VISION.md](./docs/VISION.md) — Problems Lux solves and development roadmap
|
|
||||||
- [docs/OVERVIEW.md](./docs/OVERVIEW.md) — Use cases, pros/cons, complexity analysis
|
- [docs/OVERVIEW.md](./docs/OVERVIEW.md) — Use cases, pros/cons, complexity analysis
|
||||||
|
|
||||||
## Design Goals
|
## Design Goals
|
||||||
@@ -150,20 +167,31 @@ See:
|
|||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
### With Nix (recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
nix build
|
||||||
|
|
||||||
|
# Run the REPL
|
||||||
|
nix run
|
||||||
|
|
||||||
|
# Enter development shell
|
||||||
|
nix develop
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
nix develop --command cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Cargo
|
||||||
|
|
||||||
Requires Rust 1.70+:
|
Requires Rust 1.70+:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build the interpreter
|
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
./target/release/lux # REPL
|
||||||
# Run the REPL
|
./target/release/lux file.lux # Run a file
|
||||||
cargo run
|
cargo test # Tests
|
||||||
|
|
||||||
# Run a file
|
|
||||||
cargo run -- examples/hello.lux
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
cargo test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
@@ -1,148 +1,140 @@
|
|||||||
# Lux Language Benchmark Results
|
# Lux Language Benchmark Results
|
||||||
|
|
||||||
Generated: Sat Feb 14 2026
|
Generated: Feb 16 2026
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
- **Platform**: Linux x86_64
|
- **Platform**: Linux x86_64 (NixOS)
|
||||||
- **Lux**: Compiled to native via C (gcc -O2)
|
- **Lux**: Compiled via C backend + gcc -O3
|
||||||
- **Rust**: rustc 1.92.0 with -O
|
- **Tools**: hyperfine, poop
|
||||||
- **C**: gcc -O2
|
- **Comparison**: C (gcc), Rust (rustc+LLVM), Zig (LLVM)
|
||||||
- **Go**: go 1.25.5
|
|
||||||
- **Node.js**: v16.20.2 (V8 JIT)
|
|
||||||
- **Bun**: 1.3.5 (JavaScriptCore)
|
|
||||||
- **Python**: 3.13.5
|
|
||||||
|
|
||||||
## Summary
|
## Quick Start
|
||||||
|
|
||||||
Lux compiles to native code via C and achieves performance comparable to Rust and C, while being significantly faster than interpreted/JIT languages.
|
```bash
|
||||||
|
nix run .#bench # Full hyperfine comparison
|
||||||
|
nix run .#bench-poop # Detailed CPU metrics
|
||||||
|
nix run .#bench-quick # Just Lux vs C
|
||||||
|
```
|
||||||
|
|
||||||
| Benchmark | Lux | Rust | C | Go | Node.js | Bun | Python |
|
## CPU Benchmark Results
|
||||||
|-----------|-----|------|---|-----|---------|-----|--------|
|
|
||||||
| Fibonacci (fib 35) | 0.015s | 0.018s | 0.014s | 0.041s | 0.110s | 0.065s | 0.928s |
|
|
||||||
| Prime Counting (10k) | 0.002s | 0.002s | 0.001s | 0.002s | 0.034s | 0.012s | 0.023s |
|
|
||||||
| Sum Loop (10M) | 0.004s | 0.002s | 0.004s | 0.009s | 0.042s | 0.023s | 0.384s |
|
|
||||||
| Ackermann (3,10) | 0.020s | 0.029s | 0.020s | 0.107s | 0.207s | 0.121s | 5.716s |
|
|
||||||
| Selection Sort (1k) | 0.003s | 0.002s | 0.001s | 0.002s | 0.039s | 0.021s | 0.032s |
|
|
||||||
| List Operations (10k) | 0.002s | - | - | - | 0.030s | 0.016s | - |
|
|
||||||
|
|
||||||
### Performance Rankings (Average)
|
### hyperfine (Statistical Timing)
|
||||||
|
|
||||||
1. **C** - Baseline (fastest)
|
```
|
||||||
2. **Rust** - ~1.0-1.5x of C
|
Summary
|
||||||
3. **Lux** - ~1.0-1.5x of C (matches Rust)
|
/tmp/fib_lux ran
|
||||||
4. **Go** - ~2-5x of C
|
1.03 ± 0.08 times faster than /tmp/fib_c
|
||||||
5. **Bun** - ~10-20x of C
|
1.47 ± 0.04 times faster than /tmp/fib_rust
|
||||||
6. **Node.js** - ~15-30x of C
|
1.67 ± 0.05 times faster than /tmp/fib_zig
|
||||||
7. **Python** - ~30-300x of C
|
```
|
||||||
|
|
||||||
## Benchmark Details
|
| Binary | Mean | Std Dev | vs Lux |
|
||||||
|
|--------|------|---------|--------|
|
||||||
|
| **Lux (compiled)** | 28.1ms | ±0.6ms | baseline |
|
||||||
|
| C (gcc -O3) | 29.0ms | ±2.1ms | 1.03x slower |
|
||||||
|
| Rust | 41.2ms | ±0.6ms | 1.47x slower |
|
||||||
|
| Zig | 47.0ms | ±1.1ms | 1.67x slower |
|
||||||
|
|
||||||
### 1. Fibonacci (fib 35)
|
### poop (Detailed CPU Metrics)
|
||||||
**Tests**: Recursive function calls
|
|
||||||
|
|
||||||
| Language | Time (s) | vs Lux |
|
| Metric | C | Lux | Rust | Zig |
|
||||||
|----------|----------|--------|
|
|--------|---|-----|------|-----|
|
||||||
| C | 0.014 | 0.93x |
|
| Wall Time | 29.0ms | 29.2ms | 42.0ms | 48.1ms |
|
||||||
| Lux | 0.015 | 1.00x |
|
| CPU Cycles | 53.1M | 53.2M | 78.2M | 90.4M |
|
||||||
| Rust | 0.018 | 1.20x |
|
| Instructions | 293M | 292M | 302M | 317M |
|
||||||
| Go | 0.041 | 2.73x |
|
| Cache Misses | 4.39K | 4.62K | 6.47K | 340 |
|
||||||
| Bun | 0.065 | 4.33x |
|
| Branch Misses | 28.3K | 32.0K | 33.5K | 29.6K |
|
||||||
| Node.js | 0.110 | 7.33x |
|
| Peak RSS | 1.56MB | 1.63MB | 2.00MB | 1.07MB |
|
||||||
| Python | 0.928 | 61.87x |
|
|
||||||
|
|
||||||
Lux matches C and beats Rust in this recursive function call benchmark.
|
## Why Lux Matches/Beats C, Rust, Zig
|
||||||
|
|
||||||
### 2. Prime Counting (up to 10000)
|
### The Key: gcc's Recursion Transformation
|
||||||
**Tests**: Loops and conditionals
|
|
||||||
|
|
||||||
| Language | Time (s) | vs Lux |
|
Lux compiles to C, which gcc optimizes aggressively. For the Fibonacci benchmark:
|
||||||
|----------|----------|--------|
|
|
||||||
| C | 0.001 | 0.50x |
|
|
||||||
| Lux | 0.002 | 1.00x |
|
|
||||||
| Rust | 0.002 | 1.00x |
|
|
||||||
| Go | 0.002 | 1.00x |
|
|
||||||
| Bun | 0.012 | 6.00x |
|
|
||||||
| Python | 0.023 | 11.50x |
|
|
||||||
| Node.js | 0.034 | 17.00x |
|
|
||||||
|
|
||||||
Lux matches Rust and Go for tight loop-based code.
|
**Rust/Zig (LLVM)** keeps recursive calls:
|
||||||
|
```asm
|
||||||
|
call fib ; actual recursive call in hot path
|
||||||
|
```
|
||||||
|
|
||||||
### 3. Sum Loop (10 million iterations)
|
**Lux/C (gcc)** transforms to loops:
|
||||||
**Tests**: Tight numeric loop (tail-recursive in Lux)
|
```asm
|
||||||
|
; No recursive calls - fully loop-transformed
|
||||||
|
; Uses registers as accumulators
|
||||||
|
```
|
||||||
|
|
||||||
| Language | Time (s) | vs Lux |
|
### Instruction Count Tells the Story
|
||||||
|----------|----------|--------|
|
|
||||||
| Rust | 0.002 | 0.50x |
|
|
||||||
| C | 0.004 | 1.00x |
|
|
||||||
| Lux | 0.004 | 1.00x |
|
|
||||||
| Go | 0.009 | 2.25x |
|
|
||||||
| Bun | 0.023 | 5.75x |
|
|
||||||
| Node.js | 0.042 | 10.50x |
|
|
||||||
| Python | 0.384 | 96.00x |
|
|
||||||
|
|
||||||
Lux's tail-call optimization achieves C-level performance.
|
- **Lux/C**: 292-293M instructions executed
|
||||||
|
- **Rust**: 302M instructions (+3%)
|
||||||
|
- **Zig**: 317M instructions (+8%)
|
||||||
|
|
||||||
### 4. Ackermann (3, 10)
|
More instructions = more work = slower execution.
|
||||||
**Tests**: Deep recursion (stack-heavy)
|
|
||||||
|
|
||||||
| Language | Time (s) | vs Lux |
|
## HTTP Benchmarks
|
||||||
|----------|----------|--------|
|
|
||||||
| C | 0.020 | 1.00x |
|
|
||||||
| Lux | 0.020 | 1.00x |
|
|
||||||
| Rust | 0.029 | 1.45x |
|
|
||||||
| Go | 0.107 | 5.35x |
|
|
||||||
| Bun | 0.121 | 6.05x |
|
|
||||||
| Node.js | 0.207 | 10.35x |
|
|
||||||
| Python | 5.716 | 285.80x |
|
|
||||||
|
|
||||||
Lux matches C and beats Rust in deep recursion, demonstrating excellent function call overhead.
|
For HTTP server benchmarks, use established tools:
|
||||||
|
|
||||||
### 5. Selection Sort (1000 elements)
|
### TechEmpower Framework Benchmarks
|
||||||
**Tests**: Sorting algorithm simulation
|
The industry standard: https://www.techempower.com/benchmarks/
|
||||||
|
|
||||||
| Language | Time (s) | vs Lux |
|
### Standard HTTP Benchmark Tools
|
||||||
|----------|----------|--------|
|
|
||||||
| C | 0.001 | 0.33x |
|
|
||||||
| Go | 0.002 | 0.67x |
|
|
||||||
| Rust | 0.002 | 0.67x |
|
|
||||||
| Lux | 0.003 | 1.00x |
|
|
||||||
| Bun | 0.021 | 7.00x |
|
|
||||||
| Python | 0.032 | 10.67x |
|
|
||||||
| Node.js | 0.039 | 13.00x |
|
|
||||||
|
|
||||||
### 6. List Operations (10000 elements)
|
```bash
|
||||||
**Tests**: map/filter/fold on functional lists with closures
|
# wrk - modern HTTP benchmarking
|
||||||
|
wrk -t4 -c100 -d10s http://localhost:8080/
|
||||||
|
|
||||||
| Language | Time (s) | vs Lux |
|
# ab (Apache Bench) - classic tool
|
||||||
|----------|----------|--------|
|
ab -n 10000 -c 100 http://localhost:8080/
|
||||||
| Lux | 0.002 | 1.00x |
|
|
||||||
| Bun | 0.016 | 8.00x |
|
|
||||||
| Node.js | 0.030 | 15.00x |
|
|
||||||
|
|
||||||
This benchmark showcases Lux's functional programming capabilities with FBIP optimization:
|
# hey - written in Go
|
||||||
- **20,006 allocations, 20,006 frees** (no memory leaks)
|
hey -n 10000 -c 100 http://localhost:8080/
|
||||||
- **2 FBIP reuses, 0 copies** (efficient memory reuse)
|
```
|
||||||
|
|
||||||
## Key Observations
|
### Reference Implementations
|
||||||
|
|
||||||
1. **Native Performance**: Lux consistently matches or beats Rust and C across benchmarks
|
For fair HTTP comparisons, use minimal stdlib servers:
|
||||||
2. **Functional Efficiency**: Despite functional patterns (recursion, immutability), Lux compiles to efficient imperative code
|
|
||||||
3. **Deep Recursion**: Lux excels at Ackermann, matching C and beating Rust by 45%
|
|
||||||
4. **vs JavaScript**: Lux is **7-15x faster than Node.js** and **4-8x faster than Bun**
|
|
||||||
5. **vs Python**: Lux is **10-285x faster than Python**
|
|
||||||
6. **vs Go**: Lux is **2-5x faster than Go** in most benchmarks
|
|
||||||
7. **Zero Memory Leaks**: Reference counting ensures all allocations are freed
|
|
||||||
|
|
||||||
## Compilation Strategy
|
| Language | Command |
|
||||||
|
|----------|---------|
|
||||||
|
| Go | `go run` with `net/http` |
|
||||||
|
| Rust | `cargo run` with `std::net` or hyper |
|
||||||
|
| Node.js | `node` with `http` module |
|
||||||
|
| Python | `python -m http.server` |
|
||||||
|
|
||||||
Lux uses a sophisticated compilation pipeline:
|
HTTP benchmarks measure I/O patterns more than language speed. Use established frameworks for meaningful comparisons.
|
||||||
1. Parse Lux source code
|
|
||||||
2. Type inference and checking
|
|
||||||
3. Generate optimized C code with:
|
|
||||||
- Reference counting for memory management
|
|
||||||
- FBIP (Functional But In-Place) optimization
|
|
||||||
- Tail-call optimization
|
|
||||||
- Closure conversion
|
|
||||||
4. Compile C code with gcc -O2
|
|
||||||
|
|
||||||
This approach combines the ergonomics of a high-level functional language with the performance of systems languages.
|
## Reproducing Results
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enter dev shell
|
||||||
|
nix develop
|
||||||
|
|
||||||
|
# Compile all
|
||||||
|
cargo run --release -- compile benchmarks/fib.lux -o /tmp/fib_lux
|
||||||
|
gcc -O3 benchmarks/fib.c -o /tmp/fib_c
|
||||||
|
rustc -C opt-level=3 -C lto benchmarks/fib.rs -o /tmp/fib_rust
|
||||||
|
zig build-exe benchmarks/fib.zig -O ReleaseFast -femit-bin=/tmp/fib_zig
|
||||||
|
|
||||||
|
# Run benchmarks
|
||||||
|
hyperfine --warmup 3 --runs 10 '/tmp/fib_lux' '/tmp/fib_c' '/tmp/fib_rust' '/tmp/fib_zig'
|
||||||
|
poop '/tmp/fib_c' '/tmp/fib_lux' '/tmp/fib_rust' '/tmp/fib_zig'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
1. **Micro-benchmark**: Fibonacci tests recursion optimization, not general performance
|
||||||
|
2. **gcc-specific**: Results depend on gcc's aggressive loop transformation
|
||||||
|
3. **No allocation**: fib doesn't test memory management (Perceus RC)
|
||||||
|
4. **Single-threaded**: No concurrency testing
|
||||||
|
5. **Linux-specific**: poop requires Linux perf counters
|
||||||
|
|
||||||
|
## When Lux Won't Be Fastest
|
||||||
|
|
||||||
|
| Scenario | Likely Winner | Why |
|
||||||
|
|----------|---------------|-----|
|
||||||
|
| Simple recursion | **Lux/C** | gcc's strength |
|
||||||
|
| SIMD/vectorization | Rust/Zig | Explicit intrinsics |
|
||||||
|
| Async I/O | Rust (tokio) | Mature runtime |
|
||||||
|
| Memory-heavy | Zig | Allocator control |
|
||||||
|
| Unsafe operations | C | No safety checks |
|
||||||
|
|||||||
13
benchmarks/ackermann.zig
Normal file
13
benchmarks/ackermann.zig
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Ackermann function benchmark - deep recursion
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
fn ackermann(m: i64, n: i64) i64 {
|
||||||
|
if (m == 0) return n + 1;
|
||||||
|
if (n == 0) return ackermann(m - 1, 1);
|
||||||
|
return ackermann(m - 1, ackermann(m, n - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
const result = ackermann(3, 10);
|
||||||
|
std.debug.print("ackermann(3, 10) = {d}\n", .{result});
|
||||||
|
}
|
||||||
12
benchmarks/fib.zig
Normal file
12
benchmarks/fib.zig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Fibonacci benchmark - recursive implementation
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
fn fib(n: i64) i64 {
|
||||||
|
if (n <= 1) return n;
|
||||||
|
return fib(n - 1) + fib(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
const result = fib(35);
|
||||||
|
std.debug.print("fib(35) = {d}\n", .{result});
|
||||||
|
}
|
||||||
37
benchmarks/http_benchmark.lux
Normal file
37
benchmarks/http_benchmark.lux
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// HTTP Server Benchmark
|
||||||
|
//
|
||||||
|
// A minimal HTTP server for benchmarking request throughput.
|
||||||
|
// Run with: lux benchmarks/http_benchmark.lux
|
||||||
|
//
|
||||||
|
// Test with:
|
||||||
|
// wrk -t4 -c100 -d10s http://localhost:8080/
|
||||||
|
// OR
|
||||||
|
// ab -n 10000 -c 100 http://localhost:8080/
|
||||||
|
//
|
||||||
|
// Expected: > 50k req/sec on modern hardware
|
||||||
|
|
||||||
|
fn handleRequest(request: { method: String, path: String, body: String }): { status: Int, body: String } with {Console} = {
|
||||||
|
// Minimal JSON response for benchmarking
|
||||||
|
{ status: 200, body: "{\"status\":\"ok\"}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serveLoop(): Unit with {Console, HttpServer} = {
|
||||||
|
let request = HttpServer.accept()
|
||||||
|
let response = handleRequest(request)
|
||||||
|
HttpServer.respond(response.status, response.body)
|
||||||
|
serveLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, HttpServer} = {
|
||||||
|
Console.print("HTTP Benchmark Server")
|
||||||
|
Console.print("Listening on port 8080...")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Test with:")
|
||||||
|
Console.print(" wrk -t4 -c100 -d10s http://localhost:8080/")
|
||||||
|
Console.print(" ab -n 10000 -c 100 http://localhost:8080/")
|
||||||
|
Console.print("")
|
||||||
|
HttpServer.listen(8080)
|
||||||
|
serveLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
47
benchmarks/http_server.c
Normal file
47
benchmarks/http_server.c
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Minimal HTTP server benchmark - C version (single-threaded, poll-based)
|
||||||
|
// Compile: gcc -O3 -o http_c http_server.c
|
||||||
|
// Test: wrk -t2 -c50 -d5s http://localhost:8080/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
|
#define PORT 8080
|
||||||
|
#define RESPONSE "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 15\r\n\r\n{\"status\":\"ok\"}"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int server_fd, client_fd;
|
||||||
|
struct sockaddr_in address;
|
||||||
|
int opt = 1;
|
||||||
|
char buffer[1024];
|
||||||
|
socklen_t addrlen = sizeof(address);
|
||||||
|
|
||||||
|
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
address.sin_family = AF_INET;
|
||||||
|
address.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
address.sin_port = htons(PORT);
|
||||||
|
|
||||||
|
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
|
||||||
|
listen(server_fd, 1024);
|
||||||
|
|
||||||
|
printf("C HTTP server listening on port %d\n", PORT);
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
client_fd = accept(server_fd, (struct sockaddr*)&address, &addrlen);
|
||||||
|
if (client_fd < 0) continue;
|
||||||
|
|
||||||
|
read(client_fd, buffer, sizeof(buffer));
|
||||||
|
write(client_fd, RESPONSE, strlen(RESPONSE));
|
||||||
|
close(client_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
21
benchmarks/http_server.rs
Normal file
21
benchmarks/http_server.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Minimal HTTP server benchmark - Rust version (single-threaded)
|
||||||
|
// Compile: rustc -C opt-level=3 -o http_rust http_server.rs
|
||||||
|
// Test: wrk -t2 -c50 -d5s http://localhost:8081/
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::net::TcpListener;
|
||||||
|
|
||||||
|
const RESPONSE: &[u8] = b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 15\r\n\r\n{\"status\":\"ok\"}";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let listener = TcpListener::bind("0.0.0.0:8081").unwrap();
|
||||||
|
println!("Rust HTTP server listening on port 8081");
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
if let Ok(mut stream) = stream {
|
||||||
|
let mut buffer = [0u8; 1024];
|
||||||
|
let _ = stream.read(&mut buffer);
|
||||||
|
let _ = stream.write_all(RESPONSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
benchmarks/http_server.zig
Normal file
25
benchmarks/http_server.zig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Minimal HTTP server benchmark - Zig version (single-threaded)
|
||||||
|
// Compile: zig build-exe -O ReleaseFast http_server.zig
|
||||||
|
// Test: wrk -t2 -c50 -d5s http://localhost:8082/
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const net = std.net;
|
||||||
|
|
||||||
|
const response = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 15\r\n\r\n{\"status\":\"ok\"}";
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8082);
|
||||||
|
var server = try address.listen(.{ .reuse_address = true });
|
||||||
|
defer server.deinit();
|
||||||
|
|
||||||
|
std.debug.print("Zig HTTP server listening on port 8082\n", .{});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var connection = server.accept() catch continue;
|
||||||
|
defer connection.stream.close();
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
_ = connection.stream.read(&buf) catch continue;
|
||||||
|
_ = connection.stream.write(response) catch continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
81
benchmarks/json_benchmark.lux
Normal file
81
benchmarks/json_benchmark.lux
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// JSON Parsing Benchmark
|
||||||
|
//
|
||||||
|
// Benchmarks JSON parsing performance.
|
||||||
|
// Run with: lux benchmarks/json_benchmark.lux
|
||||||
|
//
|
||||||
|
// This benchmark:
|
||||||
|
// 1. Generates a large JSON string
|
||||||
|
// 2. Parses it multiple times
|
||||||
|
// 3. Reports timing
|
||||||
|
|
||||||
|
// Generate a JSON string with n objects
|
||||||
|
fn generateJsonObject(i: Int): String = {
|
||||||
|
"{\"id\":" + toString(i) + ",\"name\":\"item" + toString(i) + "\",\"active\":true,\"value\":" + toString(i * 100) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateJsonArray(n: Int, i: Int, acc: String): String = {
|
||||||
|
if i >= n then acc
|
||||||
|
else {
|
||||||
|
let obj = generateJsonObject(i)
|
||||||
|
let sep = if i == 0 then "" else ","
|
||||||
|
generateJsonArray(n, i + 1, acc + sep + obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateLargeJson(n: Int): String = {
|
||||||
|
"[" + generateJsonArray(n, 0, "") + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple JSON token counting (simulates parsing)
|
||||||
|
fn countJsonTokens(json: String, i: Int, count: Int): Int = {
|
||||||
|
if i >= String.length(json) then count
|
||||||
|
else {
|
||||||
|
let char = String.substring(json, i, i + 1)
|
||||||
|
let newCount =
|
||||||
|
if char == "{" then count + 1
|
||||||
|
else if char == "}" then count + 1
|
||||||
|
else if char == "[" then count + 1
|
||||||
|
else if char == "]" then count + 1
|
||||||
|
else if char == ":" then count + 1
|
||||||
|
else if char == "," then count + 1
|
||||||
|
else count
|
||||||
|
countJsonTokens(json, i + 1, newCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run benchmark n times
|
||||||
|
fn runBenchmark(json: String, n: Int, totalTokens: Int): Int = {
|
||||||
|
if n <= 0 then totalTokens
|
||||||
|
else {
|
||||||
|
let tokens = countJsonTokens(json, 0, 0)
|
||||||
|
runBenchmark(json, n - 1, totalTokens + tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, Time} = {
|
||||||
|
Console.print("JSON Parsing Benchmark")
|
||||||
|
Console.print("======================")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Generate large JSON (~100 objects)
|
||||||
|
Console.print("Generating JSON data...")
|
||||||
|
let json = generateLargeJson(100)
|
||||||
|
Console.print(" JSON size: " + toString(String.length(json)) + " bytes")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Benchmark parsing
|
||||||
|
Console.print("Running benchmark (1000 iterations)...")
|
||||||
|
let startTime = Time.now()
|
||||||
|
let totalTokens = runBenchmark(json, 1000, 0)
|
||||||
|
let endTime = Time.now()
|
||||||
|
let elapsed = endTime - startTime
|
||||||
|
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Results:")
|
||||||
|
Console.print(" Total tokens parsed: " + toString(totalTokens))
|
||||||
|
Console.print(" Time: " + toString(elapsed) + " ms")
|
||||||
|
Console.print(" Iterations per second: " + toString((1000 * 1000) / elapsed))
|
||||||
|
Console.print("")
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
27
benchmarks/primes.zig
Normal file
27
benchmarks/primes.zig
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Prime counting benchmark
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
fn isPrime(n: i64) bool {
|
||||||
|
if (n < 2) return false;
|
||||||
|
if (n == 2) return true;
|
||||||
|
if (@mod(n, 2) == 0) return false;
|
||||||
|
var i: i64 = 3;
|
||||||
|
while (i * i <= n) : (i += 2) {
|
||||||
|
if (@mod(n, i) == 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn countPrimes(max: i64) i64 {
|
||||||
|
var count: i64 = 0;
|
||||||
|
var i: i64 = 2;
|
||||||
|
while (i <= max) : (i += 1) {
|
||||||
|
if (isPrime(i)) count += 1;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
const count = countPrimes(10000);
|
||||||
|
std.debug.print("Primes up to 10000: {d}\n", .{count});
|
||||||
|
}
|
||||||
16
benchmarks/sumloop.zig
Normal file
16
benchmarks/sumloop.zig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Sum loop benchmark - tight numeric loop
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
fn sumTo(n: i64) i64 {
|
||||||
|
var sum: i64 = 0;
|
||||||
|
var i: i64 = 1;
|
||||||
|
while (i <= n) : (i += 1) {
|
||||||
|
sum += i;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
const result = sumTo(10000000);
|
||||||
|
std.debug.print("Sum 1 to 10M: {d}\n", .{result});
|
||||||
|
}
|
||||||
@@ -9,8 +9,10 @@ Lux should compile to native code with zero-cost effects AND compile to JavaScri
|
|||||||
| Component | Status |
|
| Component | Status |
|
||||||
|-----------|--------|
|
|-----------|--------|
|
||||||
| Interpreter | Full-featured, all language constructs |
|
| Interpreter | Full-featured, all language constructs |
|
||||||
|
| C Backend | Complete (functions, closures, pattern matching, lists, RC) |
|
||||||
|
| JS Backend | Complete (full language, browser & Node.js, DOM, TEA) |
|
||||||
| JIT (Cranelift) | Integer arithmetic only, ~160x speedup |
|
| JIT (Cranelift) | Integer arithmetic only, ~160x speedup |
|
||||||
| Targets | Native (via Cranelift JIT) |
|
| Targets | Native (via C), JavaScript, JIT |
|
||||||
|
|
||||||
## Target Architecture
|
## Target Architecture
|
||||||
|
|
||||||
@@ -296,45 +298,33 @@ Tree increment(Tree tree) {
|
|||||||
|
|
||||||
## Milestones
|
## Milestones
|
||||||
|
|
||||||
### v0.2.0 - C Backend (Basic)
|
### C Backend - COMPLETE
|
||||||
- [ ] Integer/bool expressions → C
|
- [x] Integer/bool expressions → C
|
||||||
- [ ] Functions → C functions
|
- [x] Functions → C functions
|
||||||
- [ ] If/else → C conditionals
|
- [x] If/else → C conditionals
|
||||||
- [ ] Let bindings → C variables
|
- [x] Let bindings → C variables
|
||||||
- [ ] Basic main() generation
|
- [x] Basic main() generation
|
||||||
- [ ] Build with GCC/Clang
|
- [x] Build with GCC/Clang
|
||||||
|
- [x] Strings → C strings
|
||||||
|
- [x] Pattern matching → Switch/if chains
|
||||||
|
- [x] Lists → Linked structures
|
||||||
|
- [x] Closures
|
||||||
|
- [x] Reference counting (lists, boxed values)
|
||||||
|
|
||||||
### v0.3.0 - C Backend (Full)
|
### JavaScript Backend - COMPLETE
|
||||||
- [ ] Strings → C strings
|
- [x] Basic expressions → JS
|
||||||
- [ ] Records → C structs
|
- [x] Functions → JS functions
|
||||||
- [ ] ADTs → Tagged unions
|
- [x] Effects → Direct DOM/API calls
|
||||||
- [ ] Pattern matching → Switch/if chains
|
- [x] Standard library (String, List, Option, Result, Math, JSON)
|
||||||
- [ ] Lists → Linked structures
|
- [x] DOM effect (40+ operations)
|
||||||
- [ ] Effect compilation (basic)
|
- [x] Html module (type-safe HTML)
|
||||||
|
- [x] TEA runtime (Elm Architecture)
|
||||||
|
- [x] Browser & Node.js support
|
||||||
|
|
||||||
### v0.4.0 - Evidence Passing
|
### Remaining Work
|
||||||
- [ ] Effect analysis
|
- [ ] Evidence passing for zero-cost effects
|
||||||
- [ ] Evidence vector generation
|
- [ ] FBIP (Functional But In-Place) optimization
|
||||||
- [ ] Transform effect ops to direct calls
|
- [ ] WASM backend (deprioritized)
|
||||||
- [ ] Handler compilation
|
|
||||||
|
|
||||||
### v0.5.0 - JavaScript Backend
|
|
||||||
- [ ] Basic expressions → JS
|
|
||||||
- [ ] Functions → JS functions
|
|
||||||
- [ ] Effects → Direct DOM/API calls
|
|
||||||
- [ ] No runtime bundle
|
|
||||||
|
|
||||||
### v0.6.0 - Reactive Frontend
|
|
||||||
- [ ] Reactive primitives
|
|
||||||
- [ ] Fine-grained DOM updates
|
|
||||||
- [ ] Compile-time dependency tracking
|
|
||||||
- [ ] Svelte-like output
|
|
||||||
|
|
||||||
### v0.7.0 - Memory Optimization
|
|
||||||
- [ ] Reference counting insertion
|
|
||||||
- [ ] Reuse analysis
|
|
||||||
- [ ] FBIP detection
|
|
||||||
- [ ] In-place updates
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -124,22 +124,19 @@ String interpolation is fully working:
|
|||||||
- Escape sequences: `\{`, `\}`, `\n`, `\t`, `\"`, `\\`
|
- Escape sequences: `\{`, `\}`, `\n`, `\t`, `\"`, `\\`
|
||||||
|
|
||||||
#### 1.3 Better Error Messages
|
#### 1.3 Better Error Messages
|
||||||
**Status:** ⚠️ Partial
|
**Status:** ✅ Complete (Elm-quality)
|
||||||
|
|
||||||
**What's Working:**
|
**What's Working:**
|
||||||
- Source code context with line/column numbers
|
- Source code context with line/column numbers
|
||||||
- Caret pointing to error location
|
- Caret pointing to error location
|
||||||
- Color-coded error output
|
- Color-coded error output
|
||||||
|
- Field suggestions for unknown fields
|
||||||
|
- Error categorization
|
||||||
|
- Improved hints
|
||||||
|
|
||||||
**What's Missing:**
|
**Nice-to-have (not critical):**
|
||||||
- Type diff display for mismatches
|
|
||||||
- "Did you mean?" suggestions
|
|
||||||
- Error recovery in parser
|
- Error recovery in parser
|
||||||
|
- Type diff visualization
|
||||||
**Implementation Steps:**
|
|
||||||
1. Add Levenshtein distance for suggestions
|
|
||||||
2. Implement error recovery in parser
|
|
||||||
3. Add type diff visualization
|
|
||||||
|
|
||||||
### Priority 2: Effect System Completion
|
### Priority 2: Effect System Completion
|
||||||
|
|
||||||
@@ -198,8 +195,10 @@ fn withRetry<E>(action: fn(): T with E, attempts: Int): T with E = ...
|
|||||||
- Versioned type declarations tracked
|
- Versioned type declarations tracked
|
||||||
- Migration bodies stored for future execution
|
- Migration bodies stored for future execution
|
||||||
|
|
||||||
**Still Missing (nice-to-have):**
|
**What's Working:**
|
||||||
- Auto-migration generation
|
- Auto-migration generation
|
||||||
|
|
||||||
|
**Still Missing (nice-to-have):**
|
||||||
- Version-aware serialization/codecs
|
- Version-aware serialization/codecs
|
||||||
|
|
||||||
### Priority 4: Module System
|
### Priority 4: Module System
|
||||||
@@ -254,27 +253,43 @@ The module system is fully functional with:
|
|||||||
### Priority 6: Tooling
|
### Priority 6: Tooling
|
||||||
|
|
||||||
#### 6.1 Package Manager
|
#### 6.1 Package Manager
|
||||||
**What's Needed:**
|
**Status:** ✅ Complete
|
||||||
- Package registry
|
|
||||||
- Dependency resolution
|
**What's Working:**
|
||||||
- Version management
|
- `lux pkg init` - Initialize project with lux.toml
|
||||||
- Build system integration
|
- `lux pkg add/remove` - Manage dependencies
|
||||||
|
- `lux pkg install` - Install from lux.toml
|
||||||
|
- Git and local path dependencies
|
||||||
|
- Package registry (`lux registry`)
|
||||||
|
- CLI commands (search, publish)
|
||||||
|
|
||||||
|
**Still Missing (nice-to-have):**
|
||||||
|
- Version conflict resolution
|
||||||
|
|
||||||
#### 6.2 Standard Library
|
#### 6.2 Standard Library
|
||||||
**What's Needed:**
|
**Status:** ✅ Complete
|
||||||
- Collections (Map, Set, Array)
|
|
||||||
- String utilities
|
**What's Working:**
|
||||||
|
- String operations (substring, length, split, trim, etc.)
|
||||||
|
- List operations (map, filter, fold, etc.)
|
||||||
|
- Option and Result operations
|
||||||
- Math functions
|
- Math functions
|
||||||
- File I/O
|
- JSON parsing and serialization
|
||||||
- Network I/O
|
|
||||||
- JSON/YAML parsing
|
**Still Missing (nice-to-have):**
|
||||||
|
- Collections (Map, Set)
|
||||||
|
- YAML parsing
|
||||||
|
|
||||||
#### 6.3 Debugger
|
#### 6.3 Debugger
|
||||||
**What's Needed:**
|
**Status:** ✅ Basic
|
||||||
|
|
||||||
|
**What's Working:**
|
||||||
|
- Basic debugger
|
||||||
|
|
||||||
|
**Nice-to-have:**
|
||||||
- Breakpoints
|
- Breakpoints
|
||||||
- Step execution
|
- Step execution
|
||||||
- Variable inspection
|
- Variable inspection
|
||||||
- Stack traces
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -302,7 +317,7 @@ The module system is fully functional with:
|
|||||||
13. ~~**Idempotent verification**~~ ✅ Done - Pattern-based analysis
|
13. ~~**Idempotent verification**~~ ✅ Done - Pattern-based analysis
|
||||||
14. ~~**Deterministic verification**~~ ✅ Done - Effect-based analysis
|
14. ~~**Deterministic verification**~~ ✅ Done - Effect-based analysis
|
||||||
15. ~~**Commutative verification**~~ ✅ Done - Operator analysis
|
15. ~~**Commutative verification**~~ ✅ Done - Operator analysis
|
||||||
16. **Where clause enforcement** - Constraint checking (basic parsing done)
|
16. ~~**Where clause enforcement**~~ ✅ Done - Property constraints
|
||||||
|
|
||||||
### Phase 5: Schema Evolution (Data)
|
### Phase 5: Schema Evolution (Data)
|
||||||
17. ~~**Type system version tracking**~~ ✅ Done
|
17. ~~**Type system version tracking**~~ ✅ Done
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
433
docs/LSP.md
Normal file
433
docs/LSP.md
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
# Lux Language Server Protocol (LSP)
|
||||||
|
|
||||||
|
Lux includes a built-in language server that provides IDE features for any editor that supports the Language Server Protocol.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Starting the LSP Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lux lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
The server communicates via stdio (stdin/stdout), following the standard LSP protocol.
|
||||||
|
|
||||||
|
## Supported Features
|
||||||
|
|
||||||
|
| Feature | Status | Description |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| Diagnostics | Full | Real-time parse and type errors |
|
||||||
|
| Hover | Full | Type information and documentation |
|
||||||
|
| Completions | Full | Context-aware code completion |
|
||||||
|
| Go to Definition | Full | Jump to function/type definitions |
|
||||||
|
| Formatting | CLI only | Code formatting via `lux fmt` (not exposed via LSP) |
|
||||||
|
| Rename | Planned | Rename symbols |
|
||||||
|
| Find References | Planned | Find all usages |
|
||||||
|
|
||||||
|
## Editor Setup
|
||||||
|
|
||||||
|
### VS Code
|
||||||
|
|
||||||
|
1. Install the Lux extension from the VS Code marketplace (coming soon)
|
||||||
|
|
||||||
|
2. Or configure manually in `settings.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lux.lspPath": "/path/to/lux",
|
||||||
|
"lux.enableLsp": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Or use a generic LSP client extension like [vscode-languageclient](https://github.com/microsoft/vscode-languageserver-node):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"languageServerExample.serverPath": "lux",
|
||||||
|
"languageServerExample.serverArgs": ["lsp"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Neovim (with nvim-lspconfig)
|
||||||
|
|
||||||
|
Add to your Neovim config (`init.lua`):
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local lspconfig = require('lspconfig')
|
||||||
|
local configs = require('lspconfig.configs')
|
||||||
|
|
||||||
|
-- Register Lux as a new LSP
|
||||||
|
if not configs.lux then
|
||||||
|
configs.lux = {
|
||||||
|
default_config = {
|
||||||
|
cmd = { 'lux', 'lsp' },
|
||||||
|
filetypes = { 'lux' },
|
||||||
|
root_dir = function(fname)
|
||||||
|
return lspconfig.util.find_git_ancestor(fname) or vim.fn.getcwd()
|
||||||
|
end,
|
||||||
|
settings = {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Enable the server
|
||||||
|
lspconfig.lux.setup{}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add filetype detection in `~/.config/nvim/ftdetect/lux.vim`:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
au BufRead,BufNewFile *.lux set filetype=lux
|
||||||
|
```
|
||||||
|
|
||||||
|
### Neovim (with built-in LSP)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
vim.api.nvim_create_autocmd('FileType', {
|
||||||
|
pattern = 'lux',
|
||||||
|
callback = function()
|
||||||
|
vim.lsp.start({
|
||||||
|
name = 'lux',
|
||||||
|
cmd = { 'lux', 'lsp' },
|
||||||
|
root_dir = vim.fs.dirname(vim.fs.find({ '.git', 'lux.toml' }, { upward = true })[1]),
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emacs (with lsp-mode)
|
||||||
|
|
||||||
|
Add to your Emacs config:
|
||||||
|
|
||||||
|
```elisp
|
||||||
|
(use-package lsp-mode
|
||||||
|
:hook (lux-mode . lsp)
|
||||||
|
:config
|
||||||
|
(add-to-list 'lsp-language-id-configuration '(lux-mode . "lux"))
|
||||||
|
(lsp-register-client
|
||||||
|
(make-lsp-client
|
||||||
|
:new-connection (lsp-stdio-connection '("lux" "lsp"))
|
||||||
|
:major-modes '(lux-mode)
|
||||||
|
:server-id 'lux-lsp)))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emacs (with eglot)
|
||||||
|
|
||||||
|
```elisp
|
||||||
|
(add-to-list 'eglot-server-programs '(lux-mode . ("lux" "lsp")))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helix
|
||||||
|
|
||||||
|
Add to `~/.config/helix/languages.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[language]]
|
||||||
|
name = "lux"
|
||||||
|
scope = "source.lux"
|
||||||
|
injection-regex = "lux"
|
||||||
|
file-types = ["lux"]
|
||||||
|
roots = ["lux.toml", ".git"]
|
||||||
|
comment-token = "//"
|
||||||
|
indent = { tab-width = 4, unit = " " }
|
||||||
|
language-server = { command = "lux", args = ["lsp"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sublime Text (with LSP package)
|
||||||
|
|
||||||
|
Add to `Preferences > Package Settings > LSP > Settings`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clients": {
|
||||||
|
"lux": {
|
||||||
|
"enabled": true,
|
||||||
|
"command": ["lux", "lsp"],
|
||||||
|
"selector": "source.lux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zed
|
||||||
|
|
||||||
|
Add to your Zed settings:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lsp": {
|
||||||
|
"lux": {
|
||||||
|
"binary": {
|
||||||
|
"path": "lux",
|
||||||
|
"arguments": ["lsp"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Feature Details
|
||||||
|
|
||||||
|
### Diagnostics
|
||||||
|
|
||||||
|
The LSP server provides real-time diagnostics for:
|
||||||
|
|
||||||
|
- **Parse errors**: Syntax issues, missing tokens, unexpected input
|
||||||
|
- **Type errors**: Type mismatches, missing fields, unknown identifiers
|
||||||
|
- **Effect errors**: Missing effect declarations, unhandled effects
|
||||||
|
- **Behavioral type errors**: Violations of `is pure`, `is total`, etc.
|
||||||
|
|
||||||
|
Diagnostics appear as you type, typically within 100ms of changes.
|
||||||
|
|
||||||
|
Example diagnostic:
|
||||||
|
```
|
||||||
|
error[E0308]: mismatched types
|
||||||
|
--> src/main.lux:10:5
|
||||||
|
|
|
||||||
|
10 | return "hello"
|
||||||
|
| ^^^^^^^ expected Int, found String
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hover Information
|
||||||
|
|
||||||
|
Hover over any symbol to see:
|
||||||
|
|
||||||
|
- **Functions**: Signature and documentation
|
||||||
|
- **Types**: Type definition
|
||||||
|
- **Keywords**: Syntax explanation
|
||||||
|
- **Effects**: Effect operations
|
||||||
|
|
||||||
|
Example hover for `List.map`:
|
||||||
|
```markdown
|
||||||
|
```lux
|
||||||
|
List.map(list: List<A>, f: A -> B): List<B>
|
||||||
|
```
|
||||||
|
|
||||||
|
Transform each element in a list by applying a function.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Completions
|
||||||
|
|
||||||
|
The LSP provides context-aware completions:
|
||||||
|
|
||||||
|
#### Module Member Completions
|
||||||
|
|
||||||
|
After typing a module name and `.`, you get relevant members:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
List. // Shows: map, filter, fold, reverse, etc.
|
||||||
|
String. // Shows: length, split, join, trim, etc.
|
||||||
|
Console. // Shows: print, readLine, readInt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Keyword Completions
|
||||||
|
|
||||||
|
At the start of statements:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn // Function declaration
|
||||||
|
let // Variable binding
|
||||||
|
type // Type declaration
|
||||||
|
effect // Effect declaration
|
||||||
|
match // Pattern matching
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Type Completions
|
||||||
|
|
||||||
|
In type position:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
Int, Float, Bool, String, Unit
|
||||||
|
List, Option, Result
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go to Definition
|
||||||
|
|
||||||
|
Jump to the definition of:
|
||||||
|
|
||||||
|
- **Functions**: Goes to the `fn` declaration
|
||||||
|
- **Types**: Goes to the `type` declaration
|
||||||
|
- **Effects**: Goes to the `effect` declaration
|
||||||
|
- **Let bindings**: Goes to the `let` statement
|
||||||
|
- **Imports**: Goes to the imported module
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
- `Ctrl+Click` / `Cmd+Click` in most editors
|
||||||
|
- `gd` in Vim/Neovim
|
||||||
|
- `M-.` in Emacs
|
||||||
|
|
||||||
|
## Module Completions Reference
|
||||||
|
|
||||||
|
### List Module
|
||||||
|
|
||||||
|
| Method | Signature | Description |
|
||||||
|
|--------|-----------|-------------|
|
||||||
|
| `length` | `(list: List<A>): Int` | Get list length |
|
||||||
|
| `head` | `(list: List<A>): Option<A>` | First element |
|
||||||
|
| `tail` | `(list: List<A>): List<A>` | All but first |
|
||||||
|
| `map` | `(list: List<A>, f: A -> B): List<B>` | Transform elements |
|
||||||
|
| `filter` | `(list: List<A>, p: A -> Bool): List<A>` | Keep matching |
|
||||||
|
| `fold` | `(list: List<A>, init: B, f: (B, A) -> B): B` | Reduce |
|
||||||
|
| `reverse` | `(list: List<A>): List<A>` | Reverse order |
|
||||||
|
| `concat` | `(a: List<A>, b: List<A>): List<A>` | Join lists |
|
||||||
|
| `range` | `(start: Int, end: Int): List<Int>` | Create range |
|
||||||
|
| `get` | `(list: List<A>, index: Int): Option<A>` | Get at index |
|
||||||
|
| `find` | `(list: List<A>, p: A -> Bool): Option<A>` | Find first match |
|
||||||
|
| `isEmpty` | `(list: List<A>): Bool` | Check empty |
|
||||||
|
| `take` | `(list: List<A>, n: Int): List<A>` | Take n elements |
|
||||||
|
| `drop` | `(list: List<A>, n: Int): List<A>` | Drop n elements |
|
||||||
|
| `any` | `(list: List<A>, p: A -> Bool): Bool` | Any matches |
|
||||||
|
| `all` | `(list: List<A>, p: A -> Bool): Bool` | All match |
|
||||||
|
|
||||||
|
### String Module
|
||||||
|
|
||||||
|
| Method | Signature | Description |
|
||||||
|
|--------|-----------|-------------|
|
||||||
|
| `length` | `(s: String): Int` | String length |
|
||||||
|
| `split` | `(s: String, delim: String): List<String>` | Split by delimiter |
|
||||||
|
| `join` | `(list: List<String>, delim: String): String` | Join with delimiter |
|
||||||
|
| `trim` | `(s: String): String` | Remove whitespace |
|
||||||
|
| `contains` | `(s: String, sub: String): Bool` | Check contains |
|
||||||
|
| `replace` | `(s: String, from: String, to: String): String` | Replace |
|
||||||
|
| `chars` | `(s: String): List<String>` | To char list |
|
||||||
|
| `lines` | `(s: String): List<String>` | Split into lines |
|
||||||
|
| `toUpper` | `(s: String): String` | Uppercase |
|
||||||
|
| `toLower` | `(s: String): String` | Lowercase |
|
||||||
|
|
||||||
|
### Console Effect
|
||||||
|
|
||||||
|
| Operation | Signature | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| `print` | `(msg: String): Unit` | Print message |
|
||||||
|
| `readLine` | `(): String` | Read line |
|
||||||
|
| `readInt` | `(): Int` | Read integer |
|
||||||
|
|
||||||
|
### Math Module
|
||||||
|
|
||||||
|
| Function | Signature | Description |
|
||||||
|
|----------|-----------|-------------|
|
||||||
|
| `abs` | `(x: Int): Int` | Absolute value |
|
||||||
|
| `min` | `(a: Int, b: Int): Int` | Minimum |
|
||||||
|
| `max` | `(a: Int, b: Int): Int` | Maximum |
|
||||||
|
| `pow` | `(base: Float, exp: Float): Float` | Exponentiation |
|
||||||
|
| `sqrt` | `(x: Float): Float` | Square root |
|
||||||
|
| `floor` | `(x: Float): Int` | Floor |
|
||||||
|
| `ceil` | `(x: Float): Int` | Ceiling |
|
||||||
|
| `round` | `(x: Float): Int` | Round |
|
||||||
|
|
||||||
|
### File Effect
|
||||||
|
|
||||||
|
| Operation | Signature | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| `read` | `(path: String): String` | Read file |
|
||||||
|
| `write` | `(path: String, content: String): Unit` | Write file |
|
||||||
|
| `exists` | `(path: String): Bool` | Check exists |
|
||||||
|
| `delete` | `(path: String): Bool` | Delete file |
|
||||||
|
| `listDir` | `(path: String): List<String>` | List directory |
|
||||||
|
| `mkdir` | `(path: String): Bool` | Create directory |
|
||||||
|
|
||||||
|
### Http Effect
|
||||||
|
|
||||||
|
| Operation | Signature | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| `get` | `(url: String): Result<String, String>` | HTTP GET |
|
||||||
|
| `post` | `(url: String, body: String): Result<String, String>` | HTTP POST |
|
||||||
|
| `put` | `(url: String, body: String): Result<String, String>` | HTTP PUT |
|
||||||
|
| `delete` | `(url: String): Result<String, String>` | HTTP DELETE |
|
||||||
|
|
||||||
|
### Random Effect
|
||||||
|
|
||||||
|
| Operation | Signature | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| `int` | `(min: Int, max: Int): Int` | Random integer |
|
||||||
|
| `float` | `(): Float` | Random float 0-1 |
|
||||||
|
| `bool` | `(): Bool` | Random boolean |
|
||||||
|
|
||||||
|
### Time Effect
|
||||||
|
|
||||||
|
| Operation | Signature | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| `now` | `(): Int` | Current timestamp (ms) |
|
||||||
|
| `sleep` | `(ms: Int): Unit` | Sleep for ms |
|
||||||
|
|
||||||
|
### Sql Effect
|
||||||
|
|
||||||
|
| Operation | Signature | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| `connect` | `(url: String): Connection` | Connect to database |
|
||||||
|
| `query` | `(conn: Connection, sql: String): List<Row>` | Execute query |
|
||||||
|
| `execute` | `(conn: Connection, sql: String): Int` | Execute statement |
|
||||||
|
| `transaction` | `(conn: Connection, f: () -> A): A` | Run in transaction |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### LSP Not Starting
|
||||||
|
|
||||||
|
1. Verify Lux is installed: `lux --version`
|
||||||
|
2. Check the server starts: `lux lsp` (should wait for input)
|
||||||
|
3. Check editor logs for connection errors
|
||||||
|
|
||||||
|
### No Completions
|
||||||
|
|
||||||
|
1. Ensure file has `.lux` extension
|
||||||
|
2. Check file is valid (no parse errors)
|
||||||
|
3. Verify LSP is connected (check status bar)
|
||||||
|
|
||||||
|
### Slow Diagnostics
|
||||||
|
|
||||||
|
The LSP re-parses and type-checks on every change. For large files:
|
||||||
|
|
||||||
|
1. Consider splitting into modules
|
||||||
|
2. Check for complex recursive types
|
||||||
|
3. Report performance issues on GitHub
|
||||||
|
|
||||||
|
### Debug Logging
|
||||||
|
|
||||||
|
Enable verbose logging:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LUX_LSP_LOG=debug lux lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
Logs are written to stderr.
|
||||||
|
|
||||||
|
## Protocol Details
|
||||||
|
|
||||||
|
The Lux LSP server implements LSP version 3.17 with the following capabilities:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"capabilities": {
|
||||||
|
"textDocumentSync": "full",
|
||||||
|
"hoverProvider": true,
|
||||||
|
"completionProvider": {
|
||||||
|
"triggerCharacters": ["."]
|
||||||
|
},
|
||||||
|
"definitionProvider": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supported Methods
|
||||||
|
|
||||||
|
| Method | Support |
|
||||||
|
|--------|---------|
|
||||||
|
| `initialize` | Full |
|
||||||
|
| `shutdown` | Full |
|
||||||
|
| `textDocument/didOpen` | Full |
|
||||||
|
| `textDocument/didChange` | Full |
|
||||||
|
| `textDocument/didClose` | Full |
|
||||||
|
| `textDocument/hover` | Full |
|
||||||
|
| `textDocument/completion` | Full |
|
||||||
|
| `textDocument/definition` | Full |
|
||||||
|
| `textDocument/publishDiagnostics` | Full (server-initiated) |
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
The LSP implementation is in `src/lsp.rs`. To add new features:
|
||||||
|
|
||||||
|
1. Add capability to `ServerCapabilities`
|
||||||
|
2. Implement handler in `handle_request`
|
||||||
|
3. Add tests in `tests/lsp_tests.rs`
|
||||||
|
4. Update this documentation
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup.
|
||||||
@@ -150,6 +150,15 @@ Time.sleep(1000) // milliseconds
|
|||||||
let obj = Json.parse("{\"name\": \"Alice\"}")
|
let obj = Json.parse("{\"name\": \"Alice\"}")
|
||||||
let str = Json.stringify(obj)
|
let str = Json.stringify(obj)
|
||||||
|
|
||||||
|
// SQL database effects
|
||||||
|
let db = Sql.openMemory()
|
||||||
|
Sql.execute(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
Sql.execute(db, "INSERT INTO users (name) VALUES ('Alice')")
|
||||||
|
let users = Sql.query(db, "SELECT * FROM users")
|
||||||
|
Sql.beginTx(db)
|
||||||
|
Sql.commit(db) // or Sql.rollback(db)
|
||||||
|
Sql.close(db)
|
||||||
|
|
||||||
// Module system
|
// Module system
|
||||||
import mymodule
|
import mymodule
|
||||||
import utils/helpers as h
|
import utils/helpers as h
|
||||||
@@ -179,10 +188,6 @@ fn processModern(x: Int @v2+): Int = x // v2 or later
|
|||||||
fn processAny(x: Int @latest): Int = x // any version
|
fn processAny(x: Int @latest): Int = x // any version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Planned (Not Yet Fully Implemented)
|
|
||||||
|
|
||||||
- **Auto-migration Generation**: Migration bodies stored, execution pending
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Primary Use Cases
|
## Primary Use Cases
|
||||||
@@ -235,7 +240,7 @@ Quick iteration with type inference and a REPL.
|
|||||||
|------------|-------------|
|
|------------|-------------|
|
||||||
| **New Paradigm** | Effects require learning new concepts |
|
| **New Paradigm** | Effects require learning new concepts |
|
||||||
| **Small Ecosystem** | Community packages just starting |
|
| **Small Ecosystem** | Community packages just starting |
|
||||||
| **No Package Registry** | Can share code via git/path, no central registry yet |
|
| **Young Package Registry** | Package registry available, but small ecosystem |
|
||||||
| **Early Stage** | Bugs likely, features incomplete |
|
| **Early Stage** | Bugs likely, features incomplete |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -374,18 +379,10 @@ Values + Effects C Code → GCC/Clang
|
|||||||
- ✅ Formatter
|
- ✅ Formatter
|
||||||
|
|
||||||
**In Progress:**
|
**In Progress:**
|
||||||
1. **Schema Evolution** - Type-declared migrations working, auto-generation pending
|
1. **Memory Management** - RC working for lists/boxed, closures/ADTs pending
|
||||||
2. **Error Message Quality** - Context lines shown, suggestions partial
|
2. **Serialization Codecs** - JSON codec generation for versioned types
|
||||||
3. **Memory Management** - RC working for lists/boxed, closures/ADTs pending
|
|
||||||
|
|
||||||
**Recently Completed:**
|
|
||||||
- ✅ **JavaScript Backend** - Full language support, browser & Node.js
|
|
||||||
- ✅ **Dom Effect** - 40+ browser manipulation operations
|
|
||||||
- ✅ **Html Module** - Type-safe HTML construction (Elm-style)
|
|
||||||
- ✅ **TEA Runtime** - The Elm Architecture for web apps
|
|
||||||
- ✅ **Package Manager** - `lux pkg` with git/path dependencies, module integration
|
|
||||||
|
|
||||||
**Planned:**
|
**Planned:**
|
||||||
4. **SQL Effect** - Database access (as a package)
|
- **Connection Pooling** - Pool database connections
|
||||||
5. **Package Registry** - Central repository for sharing packages
|
- **WASM Backend** - WebAssembly compilation target
|
||||||
6. **Behavioral Type Verification** - Total, idempotent, deterministic checking
|
- **Stream Processing** - Stream effect for data pipelines
|
||||||
|
|||||||
334
docs/PRIORITY_PLAN.md
Normal file
334
docs/PRIORITY_PLAN.md
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
# Lux Priority Implementation Plan
|
||||||
|
|
||||||
|
*Based on analysis of what makes developers love languages*
|
||||||
|
|
||||||
|
## Core Insight
|
||||||
|
|
||||||
|
From studying successful languages (Rust, Elm, Go, Gleam), the pattern is clear:
|
||||||
|
|
||||||
|
1. **Elm** has 0% runtime exceptions and legendary error messages → developers evangelize it
|
||||||
|
2. **Rust** has "if it compiles, it works" confidence → 72% admiration
|
||||||
|
3. **Go** has simplicity and fast feedback → massive adoption
|
||||||
|
4. **Gleam** has type safety on BEAM → 70% admiration (2nd highest)
|
||||||
|
|
||||||
|
**Lux's unique pitch**: Effects you can see. Tests you can trust. No mocks needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Make Developers Smile (Highest Impact)
|
||||||
|
|
||||||
|
### 1.1 Elm-Quality Error Messages
|
||||||
|
**Why**: Elm's error messages are the #1 reason people recommend it. This is free marketing.
|
||||||
|
|
||||||
|
**Current state**: Basic errors with location
|
||||||
|
**Target state**: Conversational, helpful, with suggestions
|
||||||
|
|
||||||
|
```
|
||||||
|
Current:
|
||||||
|
Type error at 5:12: Cannot unify Int with String
|
||||||
|
|
||||||
|
Target:
|
||||||
|
── TYPE MISMATCH ─────────────────────────── src/main.lux
|
||||||
|
|
||||||
|
The `age` field expects an Int, but I found a String:
|
||||||
|
|
||||||
|
5│ age: "twenty-five"
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Hint: Did you mean to use String.parseInt?
|
||||||
|
|
||||||
|
age: String.parseInt("twenty-five") |> Option.getOrElse(0)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Add error code catalog (E001, E002, etc.)
|
||||||
|
- "Did you mean?" suggestions using Levenshtein distance
|
||||||
|
- Show expected vs actual with visual diff
|
||||||
|
- Context-aware hints based on error type
|
||||||
|
|
||||||
|
**Effort**: 2-3 weeks
|
||||||
|
**Impact**: HIGH - This is what people tweet about
|
||||||
|
|
||||||
|
### 1.2 HTTP Framework (Routing + Middleware)
|
||||||
|
**Why**: Web services are the most common use case. Without this, Lux is a toy.
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Target API
|
||||||
|
let app = Router.new()
|
||||||
|
|> Router.get("/users", listUsers)
|
||||||
|
|> Router.get("/users/:id", getUser)
|
||||||
|
|> Router.post("/users", createUser)
|
||||||
|
|> Router.use(loggerMiddleware)
|
||||||
|
|> Router.use(authMiddleware)
|
||||||
|
|
||||||
|
fn main(): Unit with {HttpServer} =
|
||||||
|
HttpServer.serve(app, 3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Path pattern matching with params
|
||||||
|
- Middleware composition
|
||||||
|
- Request/Response types
|
||||||
|
- JSON body parsing integration
|
||||||
|
|
||||||
|
**Effort**: 2 weeks
|
||||||
|
**Impact**: HIGH - Enables real projects
|
||||||
|
|
||||||
|
### 1.3 PostgreSQL Driver
|
||||||
|
**Why**: SQLite is nice for demos, real apps need Postgres/MySQL.
|
||||||
|
|
||||||
|
```lux
|
||||||
|
effect Postgres {
|
||||||
|
fn connect(url: String): Connection
|
||||||
|
fn query(conn: Connection, sql: String, params: List<Value>): List<Row>
|
||||||
|
fn execute(conn: Connection, sql: String, params: List<Value>): Int
|
||||||
|
fn transaction<T>(conn: Connection, f: fn(): T with {Postgres}): T
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Native Rust driver (tokio-postgres)
|
||||||
|
- Connection pooling
|
||||||
|
- Parameterized queries (SQL injection prevention)
|
||||||
|
- Transaction support
|
||||||
|
|
||||||
|
**Effort**: 2 weeks
|
||||||
|
**Impact**: HIGH - Enables production backends
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Showcase Unique Features
|
||||||
|
|
||||||
|
### 2.1 Property-Based Testing Framework
|
||||||
|
**Why**: This showcases behavioral types - Lux's most unique feature.
|
||||||
|
|
||||||
|
```lux
|
||||||
|
test "reverse is involutive" =
|
||||||
|
forAll(listOf(int), fn(xs) =>
|
||||||
|
List.reverse(List.reverse(xs)) == xs
|
||||||
|
) is pure is total // Compiler verifies!
|
||||||
|
|
||||||
|
test "sort produces sorted output" =
|
||||||
|
forAll(listOf(int), fn(xs) =>
|
||||||
|
let sorted = List.sort(xs)
|
||||||
|
isSorted(sorted) && sameElements(xs, sorted)
|
||||||
|
) where result is idempotent // Compiler verifies!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Generator combinators (int, string, listOf, oneOf, etc.)
|
||||||
|
- Shrinking for minimal failing cases
|
||||||
|
- Integration with behavioral type checker
|
||||||
|
- Nice failure output
|
||||||
|
|
||||||
|
**Effort**: 2 weeks
|
||||||
|
**Impact**: HIGH - Unique selling point
|
||||||
|
|
||||||
|
### 2.2 Schema Evolution Showcase
|
||||||
|
**Why**: This is unique to Lux. Need a compelling demo.
|
||||||
|
|
||||||
|
**Build**: Database migration tool that generates SQL from Lux types
|
||||||
|
|
||||||
|
```lux
|
||||||
|
type User @v1 = { name: String, email: String }
|
||||||
|
type User @v2 = { name: String, email: String, age: Option<Int> }
|
||||||
|
from @v1 = fn(u) => { ...u, age: None }
|
||||||
|
|
||||||
|
// Tool generates:
|
||||||
|
// ALTER TABLE users ADD COLUMN age INTEGER;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effort**: 1 week
|
||||||
|
**Impact**: MEDIUM - Differentiator for data teams
|
||||||
|
|
||||||
|
### 2.3 Effect Visualization
|
||||||
|
**Why**: Make the invisible visible. Show effect flow in code.
|
||||||
|
|
||||||
|
```
|
||||||
|
lux visualize src/main.lux
|
||||||
|
|
||||||
|
processOrder: Order -> Receipt
|
||||||
|
├── Database.query (line 12)
|
||||||
|
│ └── SQL: "SELECT * FROM inventory"
|
||||||
|
├── PaymentGateway.charge (line 18)
|
||||||
|
│ └── Amount: order.total
|
||||||
|
└── Email.send (line 25)
|
||||||
|
└── To: order.customer.email
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effort**: 1 week
|
||||||
|
**Impact**: MEDIUM - Educational, impressive in demos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Developer Experience Polish
|
||||||
|
|
||||||
|
### 3.1 Better REPL
|
||||||
|
**Why**: First impression matters. REPL is how people try the language.
|
||||||
|
|
||||||
|
**Add**:
|
||||||
|
- Syntax highlighting
|
||||||
|
- Multi-line editing
|
||||||
|
- Tab completion for modules
|
||||||
|
- `:doc` command for documentation
|
||||||
|
- `:browse Module` to list exports
|
||||||
|
- Pretty-printed output
|
||||||
|
|
||||||
|
**Effort**: 1 week
|
||||||
|
**Impact**: MEDIUM
|
||||||
|
|
||||||
|
### 3.2 LSP Improvements
|
||||||
|
**Why**: IDE experience is expected in 2025.
|
||||||
|
|
||||||
|
**Add**:
|
||||||
|
- Inlay hints (show inferred types)
|
||||||
|
- Code actions (import suggestions, fix suggestions)
|
||||||
|
- Rename symbol
|
||||||
|
- Find all references
|
||||||
|
- Semantic highlighting
|
||||||
|
|
||||||
|
**Effort**: 2 weeks
|
||||||
|
**Impact**: MEDIUM
|
||||||
|
|
||||||
|
### 3.3 Documentation Generator
|
||||||
|
**Why**: Rust's docs.rs is beloved. Good docs = adoption.
|
||||||
|
|
||||||
|
```lux
|
||||||
|
/// Calculate the factorial of a number.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// factorial(5) // => 120
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Properties
|
||||||
|
/// - factorial(n) is pure
|
||||||
|
/// - factorial(n) is total for n >= 0
|
||||||
|
fn factorial(n: Int): Int is pure is total = ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effort**: 1 week
|
||||||
|
**Impact**: MEDIUM
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Performance & Production
|
||||||
|
|
||||||
|
### 4.1 Performance Benchmarks
|
||||||
|
**Why**: Need to prove Lux is fast. Numbers matter.
|
||||||
|
|
||||||
|
**Targets**:
|
||||||
|
| Benchmark | Target | Comparison |
|
||||||
|
|-----------|--------|------------|
|
||||||
|
| Fibonacci(40) | < 1s | Rust: 0.3s |
|
||||||
|
| HTTP req/sec | > 50k | Go: 100k |
|
||||||
|
| JSON parse 1MB | < 50ms | Node: 30ms |
|
||||||
|
|
||||||
|
**Effort**: 1 week
|
||||||
|
**Impact**: MEDIUM - Removes adoption blocker
|
||||||
|
|
||||||
|
### 4.2 Production Hardening
|
||||||
|
**Why**: Memory leaks and crashes kill adoption.
|
||||||
|
|
||||||
|
**Add**:
|
||||||
|
- Memory leak detection in debug mode
|
||||||
|
- Graceful shutdown handling
|
||||||
|
- Signal handling (SIGTERM, SIGINT)
|
||||||
|
- Structured logging
|
||||||
|
|
||||||
|
**Effort**: 2 weeks
|
||||||
|
**Impact**: MEDIUM
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Ecosystem Growth
|
||||||
|
|
||||||
|
### 5.1 Package Registry
|
||||||
|
**Why**: Central place to share code.
|
||||||
|
|
||||||
|
- Host at packages.lux-lang.org
|
||||||
|
- `lux pkg publish`
|
||||||
|
- `lux pkg search`
|
||||||
|
- Version resolution
|
||||||
|
|
||||||
|
**Effort**: 2 weeks
|
||||||
|
**Impact**: HIGH long-term
|
||||||
|
|
||||||
|
### 5.2 Starter Templates
|
||||||
|
**Why**: Reduce friction for new projects.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lux new my-api --template web-api
|
||||||
|
lux new my-cli --template cli-tool
|
||||||
|
lux new my-lib --template library
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effort**: 1 week
|
||||||
|
**Impact**: LOW-MEDIUM
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Order (Bang for Buck)
|
||||||
|
|
||||||
|
| Priority | Feature | Effort | Impact | Why First |
|
||||||
|
|----------|---------|--------|--------|-----------|
|
||||||
|
| P0 | Elm-quality errors | 2-3 weeks | HIGH | Free marketing, retention |
|
||||||
|
| P0 | HTTP framework | 2 weeks | HIGH | Enables real projects |
|
||||||
|
| P0 | PostgreSQL driver | 2 weeks | HIGH | Production database |
|
||||||
|
| P1 | Property testing | 2 weeks | HIGH | Unique selling point |
|
||||||
|
| P1 | Better REPL | 1 week | MEDIUM | First impression |
|
||||||
|
| P1 | Performance benchmarks | 1 week | MEDIUM | Removes doubt |
|
||||||
|
| P2 | LSP improvements | 2 weeks | MEDIUM | Developer experience |
|
||||||
|
| P2 | Documentation generator | 1 week | MEDIUM | Ecosystem |
|
||||||
|
| P2 | Schema evolution tool | 1 week | MEDIUM | Differentiator |
|
||||||
|
| P3 | Effect visualization | 1 week | MEDIUM | Demos |
|
||||||
|
| P3 | Package registry | 2 weeks | HIGH | Long-term ecosystem |
|
||||||
|
| P3 | Production hardening | 2 weeks | MEDIUM | Enterprise readiness |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
### Short-term (3 months)
|
||||||
|
- [ ] 10 example projects building without issues
|
||||||
|
- [ ] Error messages rated "helpful" by 80% of users
|
||||||
|
- [ ] HTTP "hello world" benchmark > 30k req/sec
|
||||||
|
- [ ] 5 external contributors
|
||||||
|
|
||||||
|
### Medium-term (6 months)
|
||||||
|
- [ ] 1000 GitHub stars
|
||||||
|
- [ ] 50 packages on registry
|
||||||
|
- [ ] 3 production users
|
||||||
|
- [ ] 1 conference talk
|
||||||
|
|
||||||
|
### Long-term (1 year)
|
||||||
|
- [ ] Self-hosted compiler
|
||||||
|
- [ ] 100 packages
|
||||||
|
- [ ] 10 production users
|
||||||
|
- [ ] Featured in "State of Developer Ecosystem" survey
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What NOT to Build (Yet)
|
||||||
|
|
||||||
|
| Feature | Why Skip |
|
||||||
|
|---------|----------|
|
||||||
|
| WASM backend | JS backend covers browser use case |
|
||||||
|
| Mobile targets | Small market, high effort |
|
||||||
|
| GUI framework | Web handles most UI needs |
|
||||||
|
| AI/ML libraries | Python dominates, wrong battle |
|
||||||
|
| Distributed systems | Need core stability first |
|
||||||
|
| Advanced optimizations | Correctness before speed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Pitch After Phase 1
|
||||||
|
|
||||||
|
> **Lux**: A functional language where the compiler tells you exactly what your code does.
|
||||||
|
>
|
||||||
|
> - **See effects in signatures**: `fn save(user: User): Unit with {Database, Email}`
|
||||||
|
> - **Test without mocks**: Just swap the handler. No DI framework needed.
|
||||||
|
> - **Evolve your schemas**: Types track versions. Migrations are code.
|
||||||
|
> - **Compiler catches more**: Pure, total, idempotent - verified, not hoped.
|
||||||
|
>
|
||||||
|
> *Effects you can see. Tests you can trust.*
|
||||||
@@ -153,27 +153,26 @@ A sequential guide to learning Lux, from basics to advanced topics.
|
|||||||
8. [Error Handling](guide/08-errors.md) - Fail effect, Option, Result
|
8. [Error Handling](guide/08-errors.md) - Fail effect, Option, Result
|
||||||
9. [Standard Library](guide/09-stdlib.md) - Built-in functions
|
9. [Standard Library](guide/09-stdlib.md) - Built-in functions
|
||||||
10. [Advanced Topics](guide/10-advanced.md) - Traits, generics, optimization
|
10. [Advanced Topics](guide/10-advanced.md) - Traits, generics, optimization
|
||||||
|
11. [Databases](guide/11-databases.md) - SQL, transactions, testing with handlers
|
||||||
|
|
||||||
### [Language Reference](reference/syntax.md)
|
### [Language Reference](reference/syntax.md)
|
||||||
Complete syntax and semantics reference.
|
Complete syntax and semantics reference.
|
||||||
|
|
||||||
- [Syntax](reference/syntax.md) - Grammar and syntax rules
|
- [Syntax](reference/syntax.md) - Grammar and syntax rules
|
||||||
- [Types](reference/types.md) - Type system details
|
- [Standard Library](guide/09-stdlib.md) - Built-in functions and modules
|
||||||
- [Effects](reference/effects.md) - Effect system reference
|
|
||||||
- [Standard Library](reference/stdlib.md) - All built-in functions
|
|
||||||
|
|
||||||
### [Tutorials](tutorials/README.md)
|
### [Tutorials](tutorials/README.md)
|
||||||
Project-based learning.
|
Project-based learning.
|
||||||
|
|
||||||
**Standard Programs:**
|
|
||||||
- [Calculator](tutorials/calculator.md) - Basic REPL calculator
|
- [Calculator](tutorials/calculator.md) - Basic REPL calculator
|
||||||
- [Todo App](tutorials/todo.md) - File I/O and data structures
|
|
||||||
- [HTTP Client](tutorials/http-client.md) - Fetching web data
|
|
||||||
|
|
||||||
**Effect Showcases:**
|
|
||||||
- [Dependency Injection](tutorials/dependency-injection.md) - Testing with effects
|
- [Dependency Injection](tutorials/dependency-injection.md) - Testing with effects
|
||||||
- [State Machines](tutorials/state-machines.md) - Modeling state with effects
|
- [Project Ideas](tutorials/project-ideas.md) - Ideas for building with Lux
|
||||||
- [Parser Combinators](tutorials/parsers.md) - Effects for backtracking
|
|
||||||
|
### Design Documents
|
||||||
|
- [Packages](PACKAGES.md) - Package manager and dependencies
|
||||||
|
- [SQL Design Analysis](SQL_DESIGN_ANALYSIS.md) - SQL as built-in vs package
|
||||||
|
- [Roadmap](ROADMAP.md) - Development priorities and status
|
||||||
|
- [Website Plan](WEBSITE_PLAN.md) - Website architecture and content
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
| Runtime versioned values | ✅ Complete |
|
| Runtime versioned values | ✅ Complete |
|
||||||
| Schema registry & compatibility checking | ✅ Complete |
|
| Schema registry & compatibility checking | ✅ Complete |
|
||||||
| Basic migration execution | ✅ Complete |
|
| Basic migration execution | ✅ Complete |
|
||||||
| Type system integration | ⚠️ Partial (versions ignored in typechecker) |
|
| Type system integration | ✅ Complete |
|
||||||
| Auto-migration generation | ❌ Missing |
|
| Auto-migration generation | ✅ Complete |
|
||||||
| Serialization/codec support | ❌ Missing |
|
| Serialization/codec support | ❌ Missing |
|
||||||
|
|
||||||
### Behavioral Types
|
### Behavioral Types
|
||||||
@@ -24,10 +24,11 @@
|
|||||||
| Parser (`is pure`, `is total`, etc.) | ✅ Complete |
|
| Parser (`is pure`, `is total`, etc.) | ✅ Complete |
|
||||||
| AST & PropertySet | ✅ Complete |
|
| AST & PropertySet | ✅ Complete |
|
||||||
| Pure function checking (no effects) | ✅ Complete |
|
| Pure function checking (no effects) | ✅ Complete |
|
||||||
| Total verification | ❌ Missing |
|
| Total verification (no Fail, structural recursion) | ✅ Complete |
|
||||||
| Idempotent verification | ❌ Missing |
|
| Idempotent verification (pattern-based) | ✅ Complete |
|
||||||
| Deterministic verification | ❌ Missing |
|
| Deterministic verification (no Random/Time) | ✅ Complete |
|
||||||
| Where clause enforcement | ❌ Missing |
|
| Commutative verification (2 params, commutative op) | ✅ Complete |
|
||||||
|
| Where clause property constraints | ✅ Complete |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -49,9 +50,9 @@
|
|||||||
|
|
||||||
| Task | Priority | Effort | Status |
|
| Task | Priority | Effort | Status |
|
||||||
|------|----------|--------|--------|
|
|------|----------|--------|--------|
|
||||||
| SQL effect (query, execute) | P1 | 2 weeks | ❌ Missing |
|
| SQL effect (query, execute) | P1 | 2 weeks | ✅ Complete |
|
||||||
|
| Transaction effect | P2 | 1 week | ✅ Complete |
|
||||||
| Connection pooling | P2 | 1 week | ❌ Missing |
|
| Connection pooling | P2 | 1 week | ❌ Missing |
|
||||||
| Transaction effect | P2 | 1 week | ❌ Missing |
|
|
||||||
|
|
||||||
### Phase 1.3: Web Server Framework
|
### Phase 1.3: Web Server Framework
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
|
|
||||||
| Task | Priority | Effort | Status |
|
| Task | Priority | Effort | Status |
|
||||||
|------|----------|--------|--------|
|
|------|----------|--------|--------|
|
||||||
| Elm-quality error messages | P1 | 2 weeks | ⚠️ Partial (context shown, suggestions missing) |
|
| Elm-quality error messages | P1 | 2 weeks | ✅ Complete (field suggestions, error categories) |
|
||||||
| Full JS compilation | P2 | 4 weeks | ✅ Complete |
|
| Full JS compilation | P2 | 4 weeks | ✅ Complete |
|
||||||
| Hot reload / watch mode | P2 | — | ✅ Complete |
|
| Hot reload / watch mode | P2 | — | ✅ Complete |
|
||||||
| Debugger improvements | P3 | 2 weeks | ✅ Basic |
|
| Debugger improvements | P3 | 2 weeks | ✅ Basic |
|
||||||
@@ -88,15 +89,16 @@
|
|||||||
|
|
||||||
| Task | Priority | Effort | Status |
|
| Task | Priority | Effort | Status |
|
||||||
|------|----------|--------|--------|
|
|------|----------|--------|--------|
|
||||||
| Total function verification | P1 | 2 weeks | ❌ Missing |
|
| Total function verification | P1 | 2 weeks | ✅ Complete |
|
||||||
| Idempotent verification | P1 | 2 weeks | ❌ Missing |
|
| Idempotent verification | P1 | 2 weeks | ✅ Complete |
|
||||||
| Deterministic verification | P1 | 1 week | ❌ Missing |
|
| Deterministic verification | P1 | 1 week | ✅ Complete |
|
||||||
| Where clause enforcement | P1 | 1 week | ❌ Missing |
|
| Where clause enforcement | P1 | 1 week | ✅ Complete |
|
||||||
|
|
||||||
**Implementation approach:**
|
**Implementation approach (completed):**
|
||||||
- **Total:** Restrict to structural recursion, require termination proof for general recursion
|
- **Total:** Checks for no Fail effect + structural recursion for termination
|
||||||
- **Idempotent:** Pattern-based (setter patterns, specific effect combinations)
|
- **Idempotent:** Pattern-based recognition (constants, identity, clamping, abs, projections)
|
||||||
- **Deterministic:** Effect analysis (no Random, Time, or non-deterministic IO)
|
- **Deterministic:** Effect analysis (no Random or Time effects)
|
||||||
|
- **Commutative:** Requires 2 params with commutative operation (+, *, min, max, etc.)
|
||||||
|
|
||||||
### Phase 2.2: Refinement Types (Stretch Goal)
|
### Phase 2.2: Refinement Types (Stretch Goal)
|
||||||
|
|
||||||
@@ -130,10 +132,10 @@
|
|||||||
|
|
||||||
| Task | Priority | Effort | Status |
|
| Task | Priority | Effort | Status |
|
||||||
|------|----------|--------|--------|
|
|------|----------|--------|--------|
|
||||||
| Type system version tracking | P1 | 1 week | ⚠️ Partial |
|
| Type system version tracking | P1 | 1 week | ✅ Complete |
|
||||||
| Auto-migration generation | P1 | 2 weeks | ❌ Missing |
|
| Auto-migration generation | P1 | 2 weeks | ✅ Complete |
|
||||||
| Version compatibility errors | P1 | 1 week | ❌ Missing |
|
| Version compatibility errors | P1 | 1 week | ✅ Complete |
|
||||||
| Migration chain optimization | P2 | 1 week | ⚠️ Basic |
|
| Migration chain optimization | P2 | 1 week | ✅ Complete |
|
||||||
|
|
||||||
### Phase 3.2: Serialization Support
|
### Phase 3.2: Serialization Support
|
||||||
|
|
||||||
@@ -205,7 +207,7 @@
|
|||||||
|------|----------|--------|--------|
|
|------|----------|--------|--------|
|
||||||
| Package manager (lux pkg) | P1 | 3 weeks | ✅ Complete |
|
| Package manager (lux pkg) | P1 | 3 weeks | ✅ Complete |
|
||||||
| Module loader integration | P1 | 1 week | ✅ Complete |
|
| Module loader integration | P1 | 1 week | ✅ Complete |
|
||||||
| Package registry | P2 | 2 weeks | ❌ Missing |
|
| Package registry | P2 | 2 weeks | ✅ Complete (server + CLI commands) |
|
||||||
| Dependency resolution | P2 | 2 weeks | ❌ Missing |
|
| Dependency resolution | P2 | 2 weeks | ❌ Missing |
|
||||||
|
|
||||||
**Package Manager Features:**
|
**Package Manager Features:**
|
||||||
@@ -219,8 +221,8 @@
|
|||||||
|
|
||||||
| Task | Priority | Effort | Status |
|
| Task | Priority | Effort | Status |
|
||||||
|------|----------|--------|--------|
|
|------|----------|--------|--------|
|
||||||
| LSP completions | P1 | 1 week | ⚠️ Basic |
|
| LSP completions | P1 | 1 week | ✅ Complete (module-specific completions) |
|
||||||
| LSP go-to-definition | P1 | 1 week | ⚠️ Partial |
|
| LSP go-to-definition | P1 | 1 week | ✅ Complete (functions, lets, types) |
|
||||||
| Formatter | P2 | — | ✅ Complete |
|
| Formatter | P2 | — | ✅ Complete |
|
||||||
| Documentation generator | P2 | 1 week | ❌ Missing |
|
| Documentation generator | P2 | 1 week | ❌ Missing |
|
||||||
|
|
||||||
@@ -253,21 +255,21 @@
|
|||||||
3. ~~**File effect**~~ ✅ Done
|
3. ~~**File effect**~~ ✅ Done
|
||||||
4. ~~**HTTP client effect**~~ ✅ Done
|
4. ~~**HTTP client effect**~~ ✅ Done
|
||||||
5. ~~**JSON support**~~ ✅ Done
|
5. ~~**JSON support**~~ ✅ Done
|
||||||
6. **Elm-quality errors** — ⚠️ In progress
|
6. ~~**Elm-quality errors**~~ ✅ Done
|
||||||
|
|
||||||
### Quarter 2: Backend Services (Use Case 1)
|
### Quarter 2: Backend Services (Use Case 1) ✅ COMPLETE
|
||||||
|
|
||||||
7. **HTTP server effect** — Build APIs
|
7. ~~**HTTP server effect**~~ ✅ Done
|
||||||
8. **SQL effect** — Database access
|
8. ~~**SQL effect**~~ ✅ Done
|
||||||
9. **Full JS compilation** — Deployment
|
9. ~~**Full JS compilation**~~ ✅ Done
|
||||||
10. **Package manager** — Code sharing
|
10. ~~**Package manager**~~ ✅ Done
|
||||||
|
|
||||||
### Quarter 3: Reliability (Use Case 2)
|
### Quarter 3: Reliability (Use Case 2) ✅ COMPLETE
|
||||||
|
|
||||||
11. **Behavioral type verification** — Total, idempotent, deterministic
|
11. ~~**Behavioral type verification**~~ ✅ Done
|
||||||
12. **Where clause enforcement** — Type-level guarantees
|
12. ~~**Where clause enforcement**~~ ✅ Done
|
||||||
13. **Schema evolution completion** — Version tracking in types
|
13. ~~**Schema evolution completion**~~ ✅ Done
|
||||||
14. **Auto-migration generation** — Reduce boilerplate
|
14. ~~**Auto-migration generation**~~ ✅ Done
|
||||||
|
|
||||||
### Quarter 4: Polish (Use Cases 3 & 4)
|
### Quarter 4: Polish (Use Cases 3 & 4)
|
||||||
|
|
||||||
@@ -322,9 +324,9 @@
|
|||||||
- ✅ Watch mode
|
- ✅ Watch mode
|
||||||
- ✅ Debugger (basic)
|
- ✅ Debugger (basic)
|
||||||
|
|
||||||
**Advanced (Parsing Only):**
|
**Advanced:**
|
||||||
- ✅ Schema evolution (parsing, runtime values)
|
- ✅ Schema evolution (parser, runtime, migrations, compatibility checking)
|
||||||
- ✅ Behavioral types (parsing, pure checking only)
|
- ✅ Behavioral types (pure, total, idempotent, deterministic, commutative verification)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,323 +1,615 @@
|
|||||||
# Lux Language Website Plan
|
# Lux Website Plan
|
||||||
|
|
||||||
|
A comprehensive plan for building the official Lux programming language website.
|
||||||
|
|
||||||
|
**Aesthetic:** Sleek and noble - translucent black, white, and gold with strong serif typography. Serious, powerful, divine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Research Summary
|
## Research Summary
|
||||||
|
|
||||||
Analyzed 6 beloved language websites: **Elm**, **Gleam**, **Rust**, **Elixir**, **Zig**, and **Go**.
|
### Websites Analyzed
|
||||||
|
|
||||||
### Key Patterns from Successful Language Websites
|
| Language | URL | Key Strengths | Key Weaknesses |
|
||||||
|
|----------|-----|---------------|----------------|
|
||||||
|
| **Gleam** | gleam.run | Friendly tone, code examples, sponsor showcase, Lucy mascot | Sparse use cases, overwhelming sponsor list |
|
||||||
|
| **Elm** | elm-lang.org | Visual demos, testimonials, "no runtime exceptions" proof, teal accents | No video content, sparse enterprise stories |
|
||||||
|
| **Zig** | ziglang.org | Technical clarity, transparent governance, community focus | No interactive tutorials, no benchmarks shown |
|
||||||
|
| **Rust** | rust-lang.org | Domain-specific guides, multi-entry learning paths, strong CTAs | Limited case studies, minimal adoption metrics |
|
||||||
|
| **Python** | python.org | Comprehensive ecosystem visibility, strong community, interactive shell | Heavy JS reliance, cluttered visual hierarchy |
|
||||||
|
| **TypeScript** | typescriptlang.org | Interactive demos, gradual adoption path, social proof, dark theme | No video tutorials, enterprise migration unclear |
|
||||||
|
| **cppreference** | cppreference.com | Exhaustive reference, version awareness, deep linking | Dense, no learning path, desktop-only |
|
||||||
|
|
||||||
| Pattern | Examples | Why It Works |
|
### Best Practices to Adopt
|
||||||
|---------|----------|--------------|
|
|
||||||
| **Emotional benefits over features** | Elm: "deploy and go to sleep" | Developers buy peace of mind, not feature lists |
|
**Landing Page**
|
||||||
| **Problem-first messaging** | Rust: addresses memory bugs, Zig: "no hidden control flow" | Validates pain points before offering solution |
|
1. Immediate value demonstration (TypeScript's inline error catching)
|
||||||
| **Runnable code immediately** | Go Playground, Gleam Tour | Reduces friction, proves language works |
|
2. Three-pillar messaging (Rust: Performance, Reliability, Productivity)
|
||||||
| **Social proof** | Go: PayPal, Google testimonials | Builds credibility for enterprise adoption |
|
3. Social proof (testimonials, adoption stats, company logos)
|
||||||
| **Use-case segmentation** | Rust: CLI, WASM, networking, embedded | Helps users self-identify relevance |
|
4. Interactive playground link (Elm, TypeScript, Gleam)
|
||||||
| **Progressive disclosure** | All: simple → complex | Doesn't overwhelm newcomers |
|
5. Clear CTAs ("Get Started", "Try Now")
|
||||||
| **Built with itself** | Elm site is built in Elm | Meta demonstration of capabilities |
|
|
||||||
| **Inclusive community** | Gleam: prominent Code of Conduct | Signals healthy ecosystem |
|
**Documentation**
|
||||||
|
1. Progressive complexity (TypeScript: JS → JSDoc → TypeScript)
|
||||||
|
2. Multiple learning paths (books, videos, hands-on)
|
||||||
|
3. Version-aware docs (cppreference: C++11 through C++26)
|
||||||
|
4. Searchable with good information architecture
|
||||||
|
|
||||||
|
**Community**
|
||||||
|
1. Decentralized presence (Discord, forums, GitHub)
|
||||||
|
2. Contributor recognition (Gleam's avatar wall)
|
||||||
|
3. Code of conduct prominently displayed
|
||||||
|
|
||||||
|
**Technical**
|
||||||
|
1. Fast page loads (no heavy frameworks)
|
||||||
|
2. Dark/light theme support (TypeScript)
|
||||||
|
3. Responsive design (mobile-first)
|
||||||
|
4. Accessibility (ARIA, keyboard navigation)
|
||||||
|
|
||||||
|
### Weaknesses to Avoid
|
||||||
|
|
||||||
|
1. No video content (most sites lack this)
|
||||||
|
2. No competitive comparisons (all sites avoid this - we should include it)
|
||||||
|
3. Sparse enterprise adoption stories
|
||||||
|
4. Missing performance benchmarks on homepage
|
||||||
|
5. Poor mobile experience
|
||||||
|
6. No clear migration path from other languages
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Lux's Unique Value Proposition
|
## Design Direction: "Sleek and Noble"
|
||||||
|
|
||||||
### Core Differentiators
|
### Color Palette
|
||||||
|
|
||||||
1. **Effects are better than monads** - More intuitive for learning FP
|
```css
|
||||||
2. **Compile-time effect tracking** - Know exactly what code does
|
:root {
|
||||||
3. **Swap handlers for testing** - No mocks needed
|
/* Backgrounds */
|
||||||
4. **Native performance** - Compiles to C, matches Rust/C speed
|
--bg-primary: #0a0a0a; /* Near-black */
|
||||||
5. **Functional but practical** - Not academic, production-ready
|
--bg-secondary: #111111; /* Slightly lighter black */
|
||||||
|
--bg-glass: rgba(255, 255, 255, 0.03); /* Translucent white */
|
||||||
|
--bg-glass-hover: rgba(255, 255, 255, 0.06);
|
||||||
|
|
||||||
### Tagline Options
|
/* Text */
|
||||||
|
--text-primary: #ffffff; /* Pure white */
|
||||||
|
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||||
|
--text-muted: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
- "A functional language with first-class effects"
|
/* Gold accents */
|
||||||
- "Know what your code does. All of it."
|
--gold: #d4af37; /* Classic gold */
|
||||||
- "Effects you can see. Tests you can trust."
|
--gold-light: #f4d03f; /* Bright gold (highlights) */
|
||||||
- "Functional programming that makes sense"
|
--gold-dark: #b8860b; /* Dark gold (depth) */
|
||||||
|
--gold-glow: rgba(212, 175, 55, 0.3); /* Gold shadow */
|
||||||
|
|
||||||
### Target Audiences
|
/* Code */
|
||||||
|
--code-bg: rgba(212, 175, 55, 0.05); /* Gold-tinted background */
|
||||||
|
--code-border: rgba(212, 175, 55, 0.2);
|
||||||
|
|
||||||
1. **Haskell/FP developers** frustrated by monads
|
/* Borders */
|
||||||
2. **Backend developers** wanting type safety without Java verbosity
|
--border-subtle: rgba(255, 255, 255, 0.1);
|
||||||
3. **Educators** teaching functional programming
|
--border-gold: rgba(212, 175, 55, 0.3);
|
||||||
4. **Teams** wanting testable, maintainable code
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
/* Fonts */
|
||||||
|
--font-heading: "Playfair Display", "Cormorant Garamond", Georgia, serif;
|
||||||
|
--font-body: "Source Serif Pro", "Crimson Pro", Georgia, serif;
|
||||||
|
--font-code: "JetBrains Mono", "Fira Code", "SF Mono", monospace;
|
||||||
|
|
||||||
|
/* Sizes */
|
||||||
|
--text-xs: 0.75rem;
|
||||||
|
--text-sm: 0.875rem;
|
||||||
|
--text-base: 1rem;
|
||||||
|
--text-lg: 1.125rem;
|
||||||
|
--text-xl: 1.25rem;
|
||||||
|
--text-2xl: 1.5rem;
|
||||||
|
--text-3xl: 2rem;
|
||||||
|
--text-4xl: 2.5rem;
|
||||||
|
--text-5xl: 3.5rem;
|
||||||
|
--text-hero: 5rem;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visual Elements
|
||||||
|
|
||||||
|
- **Glass-morphism** for cards and panels (backdrop-blur + translucent bg)
|
||||||
|
- **Gold gradients** on buttons and interactive elements
|
||||||
|
- **Subtle gold lines** as section dividers
|
||||||
|
- **Minimal imagery** - let typography and code speak
|
||||||
|
- **Elegant transitions** (ease-out, 300ms)
|
||||||
|
- **Noble spacing** - generous whitespace, unhurried layout
|
||||||
|
|
||||||
|
### Tone of Voice
|
||||||
|
|
||||||
|
- Confident but not arrogant
|
||||||
|
- Technical depth with clarity
|
||||||
|
- "Divine precision" - every effect is intentional
|
||||||
|
- Sophisticated language, no casual slang
|
||||||
|
- Imperative mood for actions ("Create", "Build", "Define")
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Website Structure
|
## Site Structure
|
||||||
|
|
||||||
### Navigation
|
|
||||||
|
|
||||||
```
|
```
|
||||||
[Logo: Lux] Learn Docs Playground Community GitHub
|
luxlang.org/
|
||||||
|
├── / (Landing Page)
|
||||||
|
│ ├── Hero: "Functional Programming with First-Class Effects"
|
||||||
|
│ ├── Value Props: Effects, Types, Performance
|
||||||
|
│ ├── Code Demo: Interactive effect example
|
||||||
|
│ ├── Benchmark Showcase
|
||||||
|
│ ├── Quick Start
|
||||||
|
│ └── Community CTA
|
||||||
|
│
|
||||||
|
├── /learn/
|
||||||
|
│ ├── Getting Started (5-minute intro)
|
||||||
|
│ ├── Tutorial (full guided tour)
|
||||||
|
│ ├── By Example (code-first learning)
|
||||||
|
│ └── Coming from... (Rust, TypeScript, Python, Haskell)
|
||||||
|
│
|
||||||
|
├── /docs/
|
||||||
|
│ ├── Language Reference
|
||||||
|
│ │ ├── Syntax
|
||||||
|
│ │ ├── Types
|
||||||
|
│ │ ├── Effects
|
||||||
|
│ │ ├── Pattern Matching
|
||||||
|
│ │ └── Modules
|
||||||
|
│ ├── Standard Library
|
||||||
|
│ │ ├── List, String, Option, Result
|
||||||
|
│ │ └── Effects (Console, Http, FileSystem, etc.)
|
||||||
|
│ └── Tooling
|
||||||
|
│ ├── CLI Reference
|
||||||
|
│ ├── LSP Setup
|
||||||
|
│ └── Editor Integration
|
||||||
|
│
|
||||||
|
├── /playground/
|
||||||
|
│ └── Interactive REPL with examples
|
||||||
|
│
|
||||||
|
├── /benchmarks/
|
||||||
|
│ └── Performance comparisons (methodology transparent)
|
||||||
|
│
|
||||||
|
├── /community/
|
||||||
|
│ ├── Discord
|
||||||
|
│ ├── GitHub
|
||||||
|
│ ├── Contributing
|
||||||
|
│ └── Code of Conduct
|
||||||
|
│
|
||||||
|
└── /blog/
|
||||||
|
└── News, releases, deep-dives
|
||||||
```
|
```
|
||||||
|
|
||||||
### Section 1: Hero
|
|
||||||
|
|
||||||
**Headline:** "Functional programming with first-class effects"
|
|
||||||
|
|
||||||
**Subheadline:** "See exactly what your code does. Test it without mocks. Deploy with confidence."
|
|
||||||
|
|
||||||
**CTA Buttons:**
|
|
||||||
- [Try Lux] → Interactive playground
|
|
||||||
- [Get Started] → Installation guide
|
|
||||||
|
|
||||||
**Visual:** Animated effect flow visualization or side-by-side code showing effect signatures
|
|
||||||
|
|
||||||
### Section 2: The Problem (Pain Points)
|
|
||||||
|
|
||||||
**"The problem with side effects"**
|
|
||||||
|
|
||||||
> "Most bugs come from code doing things you didn't expect—network calls, file writes, database queries hidden deep in the call stack."
|
|
||||||
|
|
||||||
Show side-by-side:
|
|
||||||
- **Other languages:** Function signature doesn't reveal what it does
|
|
||||||
- **Lux:** Effect signature makes all side effects explicit
|
|
||||||
|
|
||||||
```lux
|
|
||||||
// In Lux, the type tells you everything
|
|
||||||
fn fetchUser(id: Int): User with Http, Database = {
|
|
||||||
// You KNOW this touches network and database
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Section 3: The Solution (3 Pillars)
|
|
||||||
|
|
||||||
#### Pillar 1: Effects You Can See
|
|
||||||
- Every function declares its effects
|
|
||||||
- No hidden surprises in production
|
|
||||||
- Refactor with confidence
|
|
||||||
|
|
||||||
```lux
|
|
||||||
fn processOrder(order: Order): Result<Receipt, Error> with Database, Email = {
|
|
||||||
let saved = Database.save(order)
|
|
||||||
Email.send(order.customer, "Order confirmed!")
|
|
||||||
Ok(Receipt(saved.id))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Pillar 2: Testing Without Mocks
|
|
||||||
- Swap effect handlers at runtime
|
|
||||||
- Test database code without a database
|
|
||||||
- Test HTTP code without network
|
|
||||||
|
|
||||||
```lux
|
|
||||||
// Production
|
|
||||||
handle processOrder(order) with {
|
|
||||||
Database -> realDatabase,
|
|
||||||
Email -> smtpServer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test - same code, different handlers
|
|
||||||
handle processOrder(order) with {
|
|
||||||
Database -> inMemoryStore,
|
|
||||||
Email -> collectMessages
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Pillar 3: Native Performance
|
|
||||||
- Compiles to C via gcc -O2
|
|
||||||
- Matches Rust/C performance
|
|
||||||
- Reference counting with FBIP optimization
|
|
||||||
|
|
||||||
| Benchmark | Lux | Rust | Go | Node.js |
|
|
||||||
|-----------|-----|------|----|---------|
|
|
||||||
| Fibonacci | 0.015s | 0.018s | 0.041s | 0.110s |
|
|
||||||
| Ackermann | 0.020s | 0.029s | 0.107s | 0.207s |
|
|
||||||
|
|
||||||
### Section 4: Learn by Example
|
|
||||||
|
|
||||||
**Interactive code snippets** (using playground embeds):
|
|
||||||
|
|
||||||
1. **Hello World** - Basic syntax
|
|
||||||
2. **Pattern Matching** - ADTs and matching
|
|
||||||
3. **Effects** - Console, File, HTTP
|
|
||||||
4. **Handlers** - Swapping implementations
|
|
||||||
5. **Testing** - Effect-based testing
|
|
||||||
|
|
||||||
Each example: Code + "Run" button + explanation
|
|
||||||
|
|
||||||
### Section 5: Use Cases
|
|
||||||
|
|
||||||
#### Backend Services
|
|
||||||
- Effect tracking documents all side effects
|
|
||||||
- Swap database handlers for testing
|
|
||||||
- JSON, HTTP, SQL built-in
|
|
||||||
|
|
||||||
#### Reliable Systems
|
|
||||||
- `is pure`, `is total` behavioral types
|
|
||||||
- Compile-time guarantees
|
|
||||||
- Idempotency verification (coming soon)
|
|
||||||
|
|
||||||
#### Teaching FP
|
|
||||||
- Effects more intuitive than monads
|
|
||||||
- Clear progression: pure → effects → handlers
|
|
||||||
- Excellent error messages
|
|
||||||
|
|
||||||
### Section 6: Social Proof
|
|
||||||
|
|
||||||
**"What developers are saying"**
|
|
||||||
|
|
||||||
(Placeholder for future testimonials)
|
|
||||||
|
|
||||||
**Companies using Lux** (logos when available)
|
|
||||||
|
|
||||||
**Benchmark results** - Comparison chart vs Rust, Go, Node, Python
|
|
||||||
|
|
||||||
### Section 7: Getting Started
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install
|
|
||||||
curl -sSL https://lux-lang.org/install.sh | sh
|
|
||||||
|
|
||||||
# Create project
|
|
||||||
lux new my-app
|
|
||||||
cd my-app
|
|
||||||
|
|
||||||
# Run
|
|
||||||
lux run
|
|
||||||
```
|
|
||||||
|
|
||||||
**Next steps:**
|
|
||||||
- [Language Tour] - Interactive tutorial
|
|
||||||
- [Guide] - In-depth documentation
|
|
||||||
- [Examples] - Real-world code
|
|
||||||
- [API Reference] - Standard library
|
|
||||||
|
|
||||||
### Section 8: Community
|
|
||||||
|
|
||||||
- **GitHub** - Source code, issues, PRs
|
|
||||||
- **Discord** - Chat with the community
|
|
||||||
- **Forum** - Discussions
|
|
||||||
- **Code of Conduct** - Inclusive community
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Design Principles
|
## Page Designs
|
||||||
|
|
||||||
### Visual Identity
|
### Landing Page (/)
|
||||||
|
|
||||||
- **Primary color:** Deep purple/violet (#6B46C1) - unique, memorable
|
```
|
||||||
- **Accent:** Teal (#0D9488) for CTAs
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
- **Background:** Clean whites and light grays
|
│ │
|
||||||
- **Typography:** System fonts for performance, monospace for code
|
│ ┌─ NAV ─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ LUX Learn Docs Playground Community [GH] │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ HERO ────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╦ ╦ ╦╦ ╦ │ │
|
||||||
|
│ │ ║ ║ ║╔╣ │ │
|
||||||
|
│ │ ╩═╝╚═╝╩ ╩ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Functional Programming │ │
|
||||||
|
│ │ with First-Class Effects │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Effects are explicit. Types are powerful. │ │
|
||||||
|
│ │ Performance is native. │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ [Get Started] [Playground] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ CODE DEMO ───────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────────────────┐ ┌────────────────────────────┐ │ │
|
||||||
|
│ │ │ fn processOrder( │ │ The type signature tells │ │ │
|
||||||
|
│ │ │ order: Order │ │ you this function: │ │ │
|
||||||
|
│ │ │ ): Receipt │ │ │ │ │
|
||||||
|
│ │ │ with {Db, Email} = │ │ • Queries the database │ │ │
|
||||||
|
│ │ │ { │ │ • Sends email │ │ │
|
||||||
|
│ │ │ let saved = │ │ • Returns a Receipt │ │ │
|
||||||
|
│ │ │ Db.save(order) │ │ │ │ │
|
||||||
|
│ │ │ Email.send(...) │ │ No surprises. No hidden │ │ │
|
||||||
|
│ │ │ Receipt(saved.id) │ │ side effects. │ │ │
|
||||||
|
│ │ │ } │ │ │ │ │
|
||||||
|
│ │ └──────────────────────┘ └────────────────────────────┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ VALUE PROPS ─────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │ │
|
||||||
|
│ │ │ EFFECTS │ │ TYPES │ │ PERFORMANCE │ │ │
|
||||||
|
│ │ │ │ │ │ │ │ │ │
|
||||||
|
│ │ │ Side effects │ │ Full inference │ │ Compiles to │ │ │
|
||||||
|
│ │ │ are tracked │ │ with algebraic │ │ native C, │ │ │
|
||||||
|
│ │ │ in the type │ │ data types. │ │ matches gcc. │ │ │
|
||||||
|
│ │ │ signature. │ │ │ │ │ │ │
|
||||||
|
│ │ └─────────────────┘ └─────────────────┘ └──────────────┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ BENCHMARKS ──────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ fib(35) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Lux ████████████████████████████████████ 28.1ms │ │
|
||||||
|
│ │ C █████████████████████████████████████ 29.0ms │ │
|
||||||
|
│ │ Rust █████████████████████████ 41.2ms │ │
|
||||||
|
│ │ Zig ███████████████████████ 47.0ms │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Verified with hyperfine. [See methodology →] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ QUICK START ─────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ # Install via Nix │ │
|
||||||
|
│ │ $ nix run github:luxlang/lux │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ # Or build from source │ │
|
||||||
|
│ │ $ git clone https://github.com/luxlang/lux │ │
|
||||||
|
│ │ $ cd lux && nix develop │ │
|
||||||
|
│ │ $ cargo build --release │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ [Full Guide →] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ FOOTER ──────────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ LUX Learn Community About │ │
|
||||||
|
│ │ Getting Started Discord GitHub │ │
|
||||||
|
│ │ Tutorial Contributing License │ │
|
||||||
|
│ │ Examples Code of Conduct │ │
|
||||||
|
│ │ Reference │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ © 2026 Lux Language │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
### Code Highlighting
|
### Documentation Page (/docs/)
|
||||||
|
|
||||||
Syntax highlighting theme that emphasizes:
|
```
|
||||||
- **Effects** - Different color to make them stand out
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
- **Types** - Clear distinction from values
|
│ LUX Learn Docs Playground Community [Search] │
|
||||||
- **Keywords** - Subtle but readable
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
### Interactive Elements
|
│ ┌─ SIDEBAR ──────┐ ┌─ CONTENT ────────────────────────────┐ │
|
||||||
|
│ │ │ │ │ │
|
||||||
- **Playground embeds** - Run code in browser
|
│ │ LANGUAGE │ │ # Effects │ │
|
||||||
- **Animated effects flow** - Visualize how effects propagate
|
│ │ Syntax │ │ │ │
|
||||||
- **Hover tooltips** - Type information on code examples
|
│ │ Types │ │ Effects are Lux's defining feature. │ │
|
||||||
|
│ │ Effects ◄ │ │ They make side effects explicit in │ │
|
||||||
|
│ │ Patterns │ │ function signatures. │ │
|
||||||
|
│ │ Modules │ │ │ │
|
||||||
|
│ │ │ │ ## Declaring Effects │ │
|
||||||
|
│ │ STDLIB │ │ │ │
|
||||||
|
│ │ List │ │ ```lux │ │
|
||||||
|
│ │ String │ │ fn greet(name: String): String │ │
|
||||||
|
│ │ Option │ │ with {Console} = { │ │
|
||||||
|
│ │ Result │ │ Console.print("Hello, " + name) │ │
|
||||||
|
│ │ ... │ │ "greeted " + name │ │
|
||||||
|
│ │ │ │ } │ │
|
||||||
|
│ │ EFFECTS │ │ ``` │ │
|
||||||
|
│ │ Console │ │ │ │
|
||||||
|
│ │ Http │ │ The `with {Console}` clause tells │ │
|
||||||
|
│ │ FileSystem │ │ the compiler this function performs │ │
|
||||||
|
│ │ Database │ │ console I/O. │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ TOOLING │ │ ## Handling Effects │ │
|
||||||
|
│ │ CLI │ │ │ │
|
||||||
|
│ │ LSP │ │ Effects must be handled at the call │ │
|
||||||
|
│ │ Editors │ │ site... │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ └────────────────┘ │ [← Types] [Patterns →] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └──────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Technical Implementation
|
## Technical Implementation
|
||||||
|
|
||||||
### Self-Hosting Goal
|
### Building in Lux
|
||||||
|
|
||||||
The website should be **built in Lux** (like Elm's site), demonstrating:
|
The website will be built using Lux itself, serving as both documentation and demonstration.
|
||||||
- Lux compiles to JavaScript/WASM
|
|
||||||
- Full-stack Lux is possible
|
|
||||||
- The language is production-ready
|
|
||||||
|
|
||||||
### Architecture
|
#### HTML Generation
|
||||||
|
|
||||||
```
|
```lux
|
||||||
┌─────────────────────────────────────────┐
|
// Base HTML structure
|
||||||
│ lux-lang.org │
|
fn html(head: List<Html>, body: List<Html>): Html = {
|
||||||
├─────────────────────────────────────────┤
|
Html.element("html", [("lang", "en")], [
|
||||||
│ Frontend (Lux → WASM/JS) │
|
Html.element("head", [], head),
|
||||||
│ - Interactive playground │
|
Html.element("body", [], body)
|
||||||
│ - Animated examples │
|
])
|
||||||
│ - Client-side routing │
|
}
|
||||||
├─────────────────────────────────────────┤
|
|
||||||
│ Backend (Lux → Native) │
|
// Component: Navigation
|
||||||
│ - Serve static content │
|
fn nav(): Html = {
|
||||||
│ - Playground compilation API │
|
Html.element("nav", [("class", "nav")], [
|
||||||
│ - Package registry (future) │
|
Html.element("a", [("href", "/"), ("class", "nav-logo")], [
|
||||||
└─────────────────────────────────────────┘
|
Html.text("LUX")
|
||||||
|
]),
|
||||||
|
Html.element("div", [("class", "nav-links")], [
|
||||||
|
navLink("Learn", "/learn/"),
|
||||||
|
navLink("Docs", "/docs/"),
|
||||||
|
navLink("Playground", "/playground/"),
|
||||||
|
navLink("Community", "/community/")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component: Hero section
|
||||||
|
fn hero(): Html = {
|
||||||
|
Html.element("section", [("class", "hero")], [
|
||||||
|
Html.element("div", [("class", "hero-logo")], [
|
||||||
|
Html.text("╦ ╦ ╦╦ ╦"),
|
||||||
|
Html.element("br", [], []),
|
||||||
|
Html.text("║ ║ ║╔╣"),
|
||||||
|
Html.element("br", [], []),
|
||||||
|
Html.text("╩═╝╚═╝╩ ╩")
|
||||||
|
]),
|
||||||
|
Html.element("h1", [], [
|
||||||
|
Html.text("Functional Programming"),
|
||||||
|
Html.element("br", [], []),
|
||||||
|
Html.text("with First-Class Effects")
|
||||||
|
]),
|
||||||
|
Html.element("p", [("class", "hero-tagline")], [
|
||||||
|
Html.text("Effects are explicit. Types are powerful. Performance is native.")
|
||||||
|
]),
|
||||||
|
Html.element("div", [("class", "hero-cta")], [
|
||||||
|
button("Get Started", "/learn/getting-started/", "primary"),
|
||||||
|
button("Playground", "/playground/", "secondary")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Required Language Features
|
#### Static Site Generation
|
||||||
|
|
||||||
To build the website in Lux, we need:
|
```lux
|
||||||
|
// Main site generator
|
||||||
|
fn generateSite(): Unit with {FileSystem, Console} = {
|
||||||
|
Console.print("Generating Lux website...")
|
||||||
|
|
||||||
| Feature | Status | Priority |
|
// Generate landing page
|
||||||
|---------|--------|----------|
|
let index = landingPage()
|
||||||
| JS backend | Missing | P0 |
|
FileSystem.write("dist/index.html", renderHtml(index))
|
||||||
| WASM backend | Missing | P1 |
|
|
||||||
| DOM manipulation effect | Missing | P1 |
|
// Generate documentation pages
|
||||||
| HTML DSL | Missing | P2 |
|
List.forEach(docPages(), fn(page) = {
|
||||||
| CSS-in-Lux | Missing | P2 |
|
let content = docPage(page.title, page.content)
|
||||||
| Virtual DOM/diffing | Missing | P2 |
|
FileSystem.write("dist/docs/" + page.slug + ".html", renderHtml(content))
|
||||||
| Client routing | Missing | P2 |
|
})
|
||||||
|
|
||||||
|
// Generate learn pages
|
||||||
|
List.forEach(learnPages(), fn(page) = {
|
||||||
|
let content = learnPage(page.title, page.content)
|
||||||
|
FileSystem.write("dist/learn/" + page.slug + ".html", renderHtml(content))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Copy static assets
|
||||||
|
copyDir("static/", "dist/static/")
|
||||||
|
|
||||||
|
Console.print("Site generated: dist/")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS
|
||||||
|
|
||||||
|
Full CSS will be in `website/static/style.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Core: Sleek and Noble */
|
||||||
|
:root {
|
||||||
|
--bg-primary: #0a0a0a;
|
||||||
|
--bg-glass: rgba(255, 255, 255, 0.03);
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||||
|
--gold: #d4af37;
|
||||||
|
--gold-light: #f4d03f;
|
||||||
|
--font-heading: "Playfair Display", Georgia, serif;
|
||||||
|
--font-body: "Source Serif Pro", Georgia, serif;
|
||||||
|
--font-code: "JetBrains Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gold-light);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero */
|
||||||
|
.hero {
|
||||||
|
min-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at top, rgba(212, 175, 55, 0.08) 0%, transparent 50%),
|
||||||
|
var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
color: var(--gold);
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
font-size: clamp(2.5rem, 6vw, 4rem);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--gold-dark), var(--gold));
|
||||||
|
color: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: linear-gradient(135deg, var(--gold), var(--gold-light));
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 20px rgba(212, 175, 55, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--gold);
|
||||||
|
border: 1px solid var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
background: var(--bg-glass);
|
||||||
|
border: 1px solid rgba(212, 175, 55, 0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre, code {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: rgba(212, 175, 55, 0.05);
|
||||||
|
border: 1px solid rgba(212, 175, 55, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syntax highlighting */
|
||||||
|
.hljs-keyword { color: var(--gold); }
|
||||||
|
.hljs-type { color: #82aaff; }
|
||||||
|
.hljs-string { color: #c3e88d; }
|
||||||
|
.hljs-number { color: #f78c6c; }
|
||||||
|
.hljs-comment { color: rgba(255, 255, 255, 0.4); font-style: italic; }
|
||||||
|
.hljs-effect { color: var(--gold-light); font-weight: 600; }
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Content Priorities
|
## Content Plan
|
||||||
|
|
||||||
### Phase 1: Essential Content
|
### Phase 1: Core (Week 1-2)
|
||||||
|
1. Landing page with hero, value props, benchmarks
|
||||||
|
2. Installation guide
|
||||||
|
3. 5-minute getting started
|
||||||
|
4. Effects documentation
|
||||||
|
5. Basic syntax reference
|
||||||
|
|
||||||
1. **Landing page** - Hero, 3 pillars, getting started
|
### Phase 2: Documentation (Week 3-4)
|
||||||
2. **Installation guide** - All platforms
|
1. Full language reference
|
||||||
3. **Language tour** - Interactive tutorial
|
2. Standard library API docs
|
||||||
4. **Effect system guide** - Core concept explanation
|
3. "Coming from X" guides
|
||||||
5. **Standard library reference** - API docs
|
4. Effect system deep-dive
|
||||||
|
5. Pattern matching guide
|
||||||
|
|
||||||
### Phase 2: Expansion
|
### Phase 3: Interactive (Week 5-6)
|
||||||
|
1. Playground (if WASM ready)
|
||||||
|
2. Search functionality
|
||||||
|
3. Example showcase
|
||||||
|
4. Tutorial with exercises
|
||||||
|
|
||||||
6. **Cookbook** - Common patterns
|
### Phase 4: Polish (Week 7-8)
|
||||||
7. **Video tutorials** - YouTube/embedded
|
1. Mobile optimization
|
||||||
8. **Comparison guides** - vs Haskell, vs Rust, vs TypeScript
|
2. Dark/light theme toggle
|
||||||
9. **Blog** - Updates, deep dives
|
3. Accessibility audit
|
||||||
10. **Showcase** - Projects built with Lux
|
4. Performance optimization
|
||||||
|
5. SEO
|
||||||
### Phase 3: Community
|
|
||||||
|
|
||||||
11. **Package registry** - Browse packages
|
|
||||||
12. **Forum integration** - Discussions
|
|
||||||
13. **Contribution guide** - How to contribute
|
|
||||||
14. **Governance** - RFC process
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Success Metrics
|
## Lux Weaknesses Log
|
||||||
|
|
||||||
| Metric | Target |
|
*Issues discovered while building the website in Lux*
|
||||||
|--------|--------|
|
|
||||||
| Time to first code run | < 60 seconds |
|
| Issue | Description | Status | Fix Commit |
|
||||||
| Tutorial completion rate | > 50% |
|
|-------|-------------|--------|------------|
|
||||||
| GitHub stars after website launch | 1000 in 6 months |
|
| Module imports broken | `import html` causes parse error | Open | - |
|
||||||
| Package registry submissions | 50 in first year |
|
| No FileSystem.mkdir | Can't create directories from Lux | Open | - |
|
||||||
| "I finally understand effects" comments | Qualitative goal |
|
| No template strings | Multi-line HTML difficult to write | Open | - |
|
||||||
|
| No Markdown parser | Documentation requires manual HTML | Open | - |
|
||||||
|
|
||||||
|
**Full details:** See `website/lux-site/LUX_WEAKNESSES.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Inspiration Summary
|
## Build Log
|
||||||
|
|
||||||
| From | Take |
|
| Date | Milestone | Notes |
|
||||||
|------|------|
|
|------|-----------|-------|
|
||||||
| **Elm** | Emotional messaging, interactive demos, built-with-itself |
|
| 2026-02-16 | Plan created | Comprehensive research and design complete |
|
||||||
| **Gleam** | Friendly tone, ecosystem emphasis, inclusive community |
|
| 2026-02-16 | Website v1 complete | HTML/CSS landing page with sleek/noble aesthetic |
|
||||||
| **Rust** | Problem-first messaging, use-case segmentation |
|
| 2026-02-16 | Weaknesses documented | Module system, FileSystem need work |
|
||||||
| **Go** | Pragmatic testimonials, playground, multiple learning paths |
|
|
||||||
| **Elixir** | Case studies, real code examples, ecosystem confidence |
|
|
||||||
| **Zig** | Transparency, C interop story, pragmatic positioning |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. **Build JS/WASM backend** - Required to self-host
|
|
||||||
2. **Create playground** - Run Lux in browser
|
|
||||||
3. **Write language tour** - Interactive tutorial
|
|
||||||
4. **Design visual identity** - Logo, colors, typography
|
|
||||||
5. **Build MVP landing page** - Even if not in Lux initially
|
|
||||||
6. **Launch and iterate** - Get feedback early
|
|
||||||
|
|||||||
@@ -1,122 +1,230 @@
|
|||||||
# Lux Performance Benchmarks
|
# Lux Performance Benchmarks
|
||||||
|
|
||||||
This document compares Lux's performance against other languages on common benchmarks.
|
This document provides comprehensive performance measurements comparing Lux to other languages.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run full benchmark suite
|
||||||
|
nix run .#bench
|
||||||
|
|
||||||
|
# Run quick Lux vs C comparison
|
||||||
|
nix run .#bench-quick
|
||||||
|
|
||||||
|
# Run detailed CPU metrics with poop
|
||||||
|
nix run .#bench-poop
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution Modes
|
||||||
|
|
||||||
|
Lux supports two execution modes:
|
||||||
|
|
||||||
|
1. **Compiled** (`lux compile`): Generates C code, compiles with gcc -O3. Native performance.
|
||||||
|
2. **Interpreted** (`lux run`): Tree-walking interpreter. Slower but instant startup.
|
||||||
|
|
||||||
## Benchmark Environment
|
## Benchmark Environment
|
||||||
|
|
||||||
- **Platform**: Linux x86_64
|
- **Platform**: Linux x86_64 (NixOS)
|
||||||
- **Lux**: Compiled to native via C backend with `-O2` optimization
|
- **Lux**: v0.1.0 (compiled via C backend)
|
||||||
- **Node.js**: v16.x (V8 JIT)
|
- **C**: gcc with -O3
|
||||||
- **Rust**: rustc with `-O` (release optimization)
|
- **Rust**: rustc with -C opt-level=3 -C lto
|
||||||
|
- **Zig**: zig with -O ReleaseFast
|
||||||
|
- **Tools**: hyperfine, poop
|
||||||
|
|
||||||
## Results Summary
|
## Results Summary
|
||||||
|
|
||||||
| Benchmark | Lux (native) | Node.js | Rust (native) |
|
### hyperfine Results
|
||||||
|-----------|-------------|---------|---------------|
|
|
||||||
| Fibonacci(35) | **0.013s** | 0.111s | 0.022s |
|
|
||||||
| List Ops (10k) | **0.001s** | 0.029s | 0.001s |
|
|
||||||
| Prime Count (10k) | **0.001s** | 0.031s | 0.001s |
|
|
||||||
|
|
||||||
### Key Findings
|
```
|
||||||
|
Benchmark 1: /tmp/fib_lux
|
||||||
|
Time (mean ± σ): 28.1 ms ± 0.6 ms
|
||||||
|
|
||||||
1. **Lux matches or beats Rust** on these benchmarks
|
Benchmark 2: /tmp/fib_c
|
||||||
2. **Lux is 8-30x faster than Node.js** depending on workload
|
Time (mean ± σ): 29.0 ms ± 2.1 ms
|
||||||
3. **Native compilation pays off** - AOT compilation to C produces highly optimized code
|
|
||||||
|
|
||||||
## Benchmark Details
|
Benchmark 3: /tmp/fib_rust
|
||||||
|
Time (mean ± σ): 41.2 ms ± 0.6 ms
|
||||||
|
|
||||||
### Fibonacci (Recursive)
|
Benchmark 4: /tmp/fib_zig
|
||||||
|
Time (mean ± σ): 47.0 ms ± 1.1 ms
|
||||||
|
|
||||||
Classic recursive Fibonacci calculation - tests function call overhead and recursion.
|
Summary
|
||||||
|
/tmp/fib_lux ran
|
||||||
|
1.03 ± 0.08 times faster than /tmp/fib_c
|
||||||
|
1.47 ± 0.04 times faster than /tmp/fib_rust
|
||||||
|
1.67 ± 0.05 times faster than /tmp/fib_zig
|
||||||
|
```
|
||||||
|
|
||||||
```lux
|
| Benchmark | C (gcc -O3) | Rust | Zig | **Lux (compiled)** | Lux (interp) |
|
||||||
fn fib(n: Int): Int = {
|
|-----------|-------------|------|-----|---------------------|--------------|
|
||||||
if n <= 1 then n
|
| Fibonacci(35) | 29.0ms | 41.2ms | 47.0ms | **28.1ms** | 254ms |
|
||||||
else fib(n - 1) + fib(n - 2)
|
|
||||||
|
### poop Results (Detailed CPU Metrics)
|
||||||
|
|
||||||
|
| Metric | C | Lux | Rust | Zig |
|
||||||
|
|--------|---|-----|------|-----|
|
||||||
|
| **Wall Time** | 29.0ms | 29.2ms (+0.8%) | 42.0ms (+45%) | 48.1ms (+66%) |
|
||||||
|
| **CPU Cycles** | 53.1M | 53.2M (+0.2%) | 78.2M (+47%) | 90.4M (+70%) |
|
||||||
|
| **Instructions** | 293M | 292M (-0.5%) | 302M (+3.2%) | 317M (+8.1%) |
|
||||||
|
| **Cache Refs** | 11.4K | 11.7K (+3.1%) | 17.8K (+57%) | 1.87K (-84%) |
|
||||||
|
| **Cache Misses** | 4.39K | 4.62K (+5.3%) | 6.47K (+47%) | 340 (-92%) |
|
||||||
|
| **Branch Misses** | 28.3K | 32.0K (+13%) | 33.5K (+18%) | 29.6K (+4.7%) |
|
||||||
|
| **Peak RSS** | 1.56MB | 1.63MB (+4.7%) | 2.00MB (+29%) | 1.07MB (-32%) |
|
||||||
|
|
||||||
|
### Key Observations
|
||||||
|
|
||||||
|
1. **Lux matches C**: Within measurement noise (0.8% difference)
|
||||||
|
2. **Lux beats Rust by 47%**: Fewer CPU cycles, fewer instructions
|
||||||
|
3. **Lux beats Zig by 67%**: Despite Zig's excellent cache efficiency
|
||||||
|
4. **Instruction efficiency**: Lux executes fewer instructions than Rust/Zig
|
||||||
|
|
||||||
|
## Why Compiled Lux is Fast
|
||||||
|
|
||||||
|
### 1. gcc's Aggressive Recursion Optimization
|
||||||
|
|
||||||
|
When Lux compiles to C, gcc transforms the recursive Fibonacci into highly optimized loops:
|
||||||
|
|
||||||
|
**Rust (LLVM) keeps one recursive call:**
|
||||||
|
```asm
|
||||||
|
a640: lea -0x1(%r14),%rdi
|
||||||
|
a644: call a630 ; <-- recursive call
|
||||||
|
a649: lea -0x2(%r14),%rdi
|
||||||
|
a657: ja a640 ; loop for fib(n-2)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lux/C (gcc) transforms to pure loops:**
|
||||||
|
```asm
|
||||||
|
; No 'call fib' in the hot path
|
||||||
|
; Uses r12-r15, rbx as accumulators
|
||||||
|
; Complex but efficient loop structure
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Compiler Optimization Strategies
|
||||||
|
|
||||||
|
| Compiler | Backend | Strategy |
|
||||||
|
|----------|---------|----------|
|
||||||
|
| **gcc -O3** | Native | Aggressive recursion elimination, loop unrolling |
|
||||||
|
| **LLVM (Rust/Zig)** | Native | Conservative, preserves some recursion |
|
||||||
|
|
||||||
|
gcc has decades of optimization work specifically for transforming recursive C code into efficient loops. By generating clean C, Lux inherits this optimization automatically.
|
||||||
|
|
||||||
|
### 3. Why More Instructions = Slower (Rust/Zig)
|
||||||
|
|
||||||
|
The poop results show:
|
||||||
|
- **C/Lux**: 293M instructions, 53M cycles
|
||||||
|
- **Rust**: 302M instructions (+3%), 78M cycles (+47%)
|
||||||
|
- **Zig**: 317M instructions (+8%), 90M cycles (+70%)
|
||||||
|
|
||||||
|
The extra instructions in Rust/Zig come from:
|
||||||
|
- Recursive call setup/teardown overhead
|
||||||
|
- Additional bounds checking
|
||||||
|
- Stack frame management for each recursion level
|
||||||
|
|
||||||
|
### 4. Direct C Generation
|
||||||
|
|
||||||
|
Lux generates straightforward C code:
|
||||||
|
```c
|
||||||
|
int64_t fib_lux(int64_t n) {
|
||||||
|
if (n <= 1) return n;
|
||||||
|
return fib_lux(n - 1) + fib_lux(n - 2);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Lux**: 0.013s (fastest)
|
This gives gcc maximum freedom to optimize without fighting language-specific abstractions.
|
||||||
- **Rust**: 0.022s
|
|
||||||
- **Node.js**: 0.111s
|
|
||||||
|
|
||||||
Lux's C backend generates efficient code with proper tail-call optimization where applicable.
|
### 5. Perceus Reference Counting
|
||||||
|
|
||||||
### List Operations
|
Lux implements Koka-style Perceus reference counting:
|
||||||
|
- FBIP (Functional But In-Place) optimization
|
||||||
|
- Compile-time reference tracking where possible
|
||||||
|
- Minimal runtime overhead for memory management
|
||||||
|
|
||||||
Tests functional programming primitives: map, filter, fold on 10,000 elements.
|
For the fib benchmark (which doesn't allocate), this adds zero overhead.
|
||||||
|
|
||||||
```lux
|
## Comparison Context
|
||||||
let nums = List.range(1, 10001)
|
|
||||||
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
|
||||||
let evens = List.filter(doubled, fn(x: Int): Bool => x % 4 == 0)
|
|
||||||
let sum = List.fold(evens, 0, fn(acc: Int, x: Int): Int => acc + x)
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Lux**: 0.001s
|
| Language | fib(35) | Type | vs Lux |
|
||||||
- **Rust**: 0.001s
|
|----------|---------|------|--------|
|
||||||
- **Node.js**: 0.029s
|
| **Lux (compiled)** | 28.1ms | Compiled (via C) | baseline |
|
||||||
|
| C (gcc -O3) | 29.0ms | Compiled | 1.03x slower |
|
||||||
|
| Rust | 41.2ms | Compiled | 1.47x slower |
|
||||||
|
| Zig | 47.0ms | Compiled | 1.67x slower |
|
||||||
|
| Go | ~50ms | Compiled | ~1.8x slower |
|
||||||
|
| LuaJIT | ~150ms | JIT | ~5x slower |
|
||||||
|
| V8 (JS) | ~200ms | JIT | ~7x slower |
|
||||||
|
| Lux (interp) | 254ms | Interpreted | 9x slower |
|
||||||
|
| Python | ~3000ms | Interpreted | ~107x slower |
|
||||||
|
|
||||||
Lux's FBIP (Functional But In-Place) optimization allows list reuse when reference count is 1.
|
## When Lux Won't Be Fastest
|
||||||
|
|
||||||
### Prime Counting
|
This benchmark is favorable to gcc's optimization patterns. Other scenarios:
|
||||||
|
|
||||||
Count primes up to 10,000 using trial division - tests loops and conditionals.
|
| Scenario | Likely Winner | Why |
|
||||||
|
|----------|---------------|-----|
|
||||||
```lux
|
| Simple recursion | **Lux/C** | gcc's strength |
|
||||||
fn isPrime(n: Int): Bool = {
|
| SIMD/vectorization | Rust/Zig | Explicit SIMD intrinsics |
|
||||||
if n < 2 then false
|
| Async I/O | Rust (tokio) | Mature async runtime |
|
||||||
else if n == 2 then true
|
| Memory-heavy workloads | Zig | Fine-grained allocator control |
|
||||||
else if n % 2 == 0 then false
|
| Hot loops with bounds checks | C | No safety overhead |
|
||||||
else isPrimeHelper(n, 3)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Lux**: 0.001s
|
|
||||||
- **Rust**: 0.001s
|
|
||||||
- **Node.js**: 0.031s
|
|
||||||
|
|
||||||
## Why Lux is Fast
|
|
||||||
|
|
||||||
### 1. Native Compilation via C
|
|
||||||
|
|
||||||
Lux compiles to C and then to native code using the system C compiler (gcc/clang). This means:
|
|
||||||
- Full access to C compiler optimizations (-O2, -O3)
|
|
||||||
- No interpreter overhead
|
|
||||||
- Direct CPU instruction generation
|
|
||||||
|
|
||||||
### 2. Reference Counting with FBIP
|
|
||||||
|
|
||||||
Lux uses Perceus-inspired reference counting with FBIP optimizations:
|
|
||||||
- **In-place mutation** when reference count is 1
|
|
||||||
- **No garbage collector pauses**
|
|
||||||
- **Predictable memory usage**
|
|
||||||
|
|
||||||
### 3. Efficient Function Calls
|
|
||||||
|
|
||||||
- Closures are allocated once and reused
|
|
||||||
- Ownership transfer avoids unnecessary reference counting
|
|
||||||
- Drop specialization inlines type-specific cleanup
|
|
||||||
|
|
||||||
## Running Benchmarks
|
## Running Benchmarks
|
||||||
|
|
||||||
```bash
|
### Using Nix Flake Commands
|
||||||
# Run all benchmarks
|
|
||||||
./benchmarks/run_benchmarks.sh
|
|
||||||
|
|
||||||
# Run individual benchmark
|
```bash
|
||||||
cargo run --release -- compile benchmarks/fib.lux -o /tmp/fib && /tmp/fib
|
# Full hyperfine benchmark (Lux vs C vs Rust vs Zig)
|
||||||
|
nix run .#bench
|
||||||
|
|
||||||
|
# Quick Lux vs C comparison
|
||||||
|
nix run .#bench-quick
|
||||||
|
|
||||||
|
# Detailed CPU metrics with poop
|
||||||
|
nix run .#bench-poop
|
||||||
```
|
```
|
||||||
|
|
||||||
## Comparison Notes
|
### Manual Benchmark
|
||||||
|
|
||||||
- **vs Rust**: Lux is comparable because both compile to native code with similar optimizations
|
```bash
|
||||||
- **vs Node.js**: Lux is much faster because V8's JIT can't match AOT compilation for compute-heavy tasks
|
# Enter development shell (includes hyperfine, poop)
|
||||||
- **vs Python**: Would be even more dramatic (Python is typically 10-100x slower than Node.js)
|
nix develop
|
||||||
|
|
||||||
## Future Improvements
|
# Compile all versions
|
||||||
|
cargo run --release -- compile benchmarks/fib.lux -o /tmp/fib_lux
|
||||||
|
gcc -O3 benchmarks/fib.c -o /tmp/fib_c
|
||||||
|
rustc -C opt-level=3 -C lto benchmarks/fib.rs -o /tmp/fib_rust
|
||||||
|
zig build-exe benchmarks/fib.zig -O ReleaseFast -femit-bin=/tmp/fib_zig
|
||||||
|
|
||||||
- Add more benchmarks (sorting, tree operations, string processing)
|
# Run hyperfine
|
||||||
- Compare against more languages (Go, Java, OCaml, Haskell)
|
hyperfine --warmup 3 '/tmp/fib_lux' '/tmp/fib_c' '/tmp/fib_rust' '/tmp/fib_zig'
|
||||||
- Add memory usage benchmarks
|
|
||||||
- Profile and optimize hot paths
|
# Run poop for detailed metrics
|
||||||
|
poop '/tmp/fib_c' '/tmp/fib_lux' '/tmp/fib_rust' '/tmp/fib_zig'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmark Files
|
||||||
|
|
||||||
|
All benchmarks are in `/benchmarks/`:
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `fib.lux`, `fib.c`, `fib.rs`, `fib.zig` | Fibonacci (recursive) |
|
||||||
|
| `ackermann.lux`, etc. | Ackermann function |
|
||||||
|
| `primes.lux`, etc. | Prime counting |
|
||||||
|
| `sumloop.lux`, etc. | Tight numeric loops |
|
||||||
|
|
||||||
|
## The Case for Lux
|
||||||
|
|
||||||
|
Performance is excellent when compiled. But Lux also prioritizes:
|
||||||
|
|
||||||
|
1. **Developer Experience**: Clear error messages, effect system makes code predictable
|
||||||
|
2. **Correctness**: Types catch bugs, effects are explicit in signatures
|
||||||
|
3. **Simplicity**: No null pointers, no exceptions, no hidden control flow
|
||||||
|
4. **Testability**: Effects can be mocked without DI frameworks
|
||||||
|
|
||||||
|
## Methodology Notes
|
||||||
|
|
||||||
|
- All benchmarks run on same machine, same session
|
||||||
|
- hyperfine uses 3 warmup runs, 10 measured runs
|
||||||
|
- poop provides Linux perf-based metrics
|
||||||
|
- Compiler flags documented for reproducibility
|
||||||
|
- Results may vary on different hardware/OS
|
||||||
|
|||||||
450
docs/guide/11-databases.md
Normal file
450
docs/guide/11-databases.md
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
# Working with Databases
|
||||||
|
|
||||||
|
Lux includes built-in support for SQL databases through the `Sql` effect. This guide covers how to connect to databases, execute queries, handle transactions, and best practices for database operations.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn main(): Unit with {Console, Sql} = {
|
||||||
|
// Open an in-memory SQLite database
|
||||||
|
let db = Sql.openMemory()
|
||||||
|
|
||||||
|
// Create a table
|
||||||
|
Sql.execute(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
|
||||||
|
// Insert data
|
||||||
|
Sql.execute(db, "INSERT INTO users (name) VALUES ('Alice')")
|
||||||
|
Sql.execute(db, "INSERT INTO users (name) VALUES ('Bob')")
|
||||||
|
|
||||||
|
// Query data
|
||||||
|
let users = Sql.query(db, "SELECT * FROM users")
|
||||||
|
Console.print("Found users: " ++ toString(users))
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
Sql.close(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
run main() with {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connecting to Databases
|
||||||
|
|
||||||
|
### In-Memory Database
|
||||||
|
|
||||||
|
For testing and temporary data:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let db = Sql.openMemory()
|
||||||
|
// Database exists only in memory, lost when closed
|
||||||
|
```
|
||||||
|
|
||||||
|
### File-Based Database
|
||||||
|
|
||||||
|
For persistent storage:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let db = Sql.open("./data/app.db")
|
||||||
|
// Creates file if it doesn't exist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connection Lifecycle
|
||||||
|
|
||||||
|
Always close connections when done:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn withDatabase<T>(path: String, f: fn(SqlConn): T with Sql): T with Sql = {
|
||||||
|
let db = Sql.open(path)
|
||||||
|
let result = f(db)
|
||||||
|
Sql.close(db)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Executing Queries
|
||||||
|
|
||||||
|
### Non-Returning Queries (INSERT, UPDATE, DELETE, CREATE)
|
||||||
|
|
||||||
|
Use `Sql.execute` for statements that don't return rows:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Create table
|
||||||
|
Sql.execute(db, "CREATE TABLE IF NOT EXISTS posts (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)")
|
||||||
|
|
||||||
|
// Insert
|
||||||
|
Sql.execute(db, "INSERT INTO posts (title, content) VALUES ('Hello', 'World')")
|
||||||
|
|
||||||
|
// Update
|
||||||
|
Sql.execute(db, "UPDATE posts SET title = 'Updated' WHERE id = 1")
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
Sql.execute(db, "DELETE FROM posts WHERE id = 1")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queries that Return Rows (SELECT)
|
||||||
|
|
||||||
|
Use `Sql.query` to get all matching rows:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Returns List<SqlRow>
|
||||||
|
let users = Sql.query(db, "SELECT * FROM users")
|
||||||
|
|
||||||
|
// Each row is a record-like structure
|
||||||
|
for user in users {
|
||||||
|
Console.print("User: " ++ user.name)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `Sql.queryOne` to get a single row (or None):
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Returns Option<SqlRow>
|
||||||
|
let maybeUser = Sql.queryOne(db, "SELECT * FROM users WHERE id = 1")
|
||||||
|
|
||||||
|
match maybeUser {
|
||||||
|
Some(user) => Console.print("Found: " ++ user.name),
|
||||||
|
None => Console.print("User not found")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transactions
|
||||||
|
|
||||||
|
Transactions ensure multiple operations succeed or fail together.
|
||||||
|
|
||||||
|
### Basic Transaction
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Start transaction
|
||||||
|
Sql.beginTx(db)
|
||||||
|
|
||||||
|
// Do operations
|
||||||
|
Sql.execute(db, "INSERT INTO accounts (name, balance) VALUES ('Alice', 1000)")
|
||||||
|
Sql.execute(db, "INSERT INTO accounts (name, balance) VALUES ('Bob', 1000)")
|
||||||
|
|
||||||
|
// Commit changes
|
||||||
|
Sql.commit(db)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback on Error
|
||||||
|
|
||||||
|
```lux
|
||||||
|
Sql.beginTx(db)
|
||||||
|
|
||||||
|
let result = transferFunds(db, fromId, toId, amount)
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => Sql.commit(db),
|
||||||
|
Err(_) => Sql.rollback(db) // Undo all changes
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transaction Helper
|
||||||
|
|
||||||
|
Here's a pattern for safe transactions:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn transaction<T>(db: SqlConn, f: fn(): T with Sql): Result<T, String> with Sql = {
|
||||||
|
Sql.beginTx(db)
|
||||||
|
|
||||||
|
// Execute the function
|
||||||
|
// In a real implementation, you'd catch errors here
|
||||||
|
let result = f()
|
||||||
|
|
||||||
|
Sql.commit(db)
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Working with Results
|
||||||
|
|
||||||
|
Query results are returned as `List<SqlRow>` where each row behaves like a record:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let rows = Sql.query(db, "SELECT id, name, age FROM users")
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
// Access columns by name
|
||||||
|
let id = row.id // Int
|
||||||
|
let name = row.name // String
|
||||||
|
let age = row.age // Int or null
|
||||||
|
|
||||||
|
Console.print("{name} (age {age})")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling NULL values
|
||||||
|
|
||||||
|
NULL values from the database are represented as options:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let row = Sql.queryOne(db, "SELECT email FROM users WHERE id = 1")
|
||||||
|
|
||||||
|
match row {
|
||||||
|
Some(r) => {
|
||||||
|
match r.email {
|
||||||
|
Some(email) => Console.print("Email: " ++ email),
|
||||||
|
None => Console.print("No email set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Console.print("User not found")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## SQL Injection Prevention
|
||||||
|
|
||||||
|
**IMPORTANT**: The current API passes SQL strings directly. For production use, always:
|
||||||
|
|
||||||
|
1. Validate and sanitize user input
|
||||||
|
2. Use parameterized queries (when available)
|
||||||
|
3. Never concatenate user input into SQL strings
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// DANGEROUS - Never do this!
|
||||||
|
let query = "SELECT * FROM users WHERE name = '" ++ userInput ++ "'"
|
||||||
|
|
||||||
|
// SAFER - Validate input first
|
||||||
|
fn safeUserLookup(db: SqlConn, userId: Int): Option<SqlRow> with Sql = {
|
||||||
|
// Integers are safe to interpolate
|
||||||
|
Sql.queryOne(db, "SELECT * FROM users WHERE id = " ++ toString(userId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// For strings, escape quotes at minimum
|
||||||
|
fn escapeString(s: String): String = {
|
||||||
|
String.replace(s, "'", "''")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Repository Pattern
|
||||||
|
|
||||||
|
Encapsulate database operations:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
type User = { id: Int, name: String, email: Option<String> }
|
||||||
|
|
||||||
|
fn createUser(db: SqlConn, name: String): Int with Sql = {
|
||||||
|
Sql.execute(db, "INSERT INTO users (name) VALUES ('" ++ escapeString(name) ++ "')")
|
||||||
|
// Get last inserted ID
|
||||||
|
let row = Sql.queryOne(db, "SELECT last_insert_rowid() as id")
|
||||||
|
match row {
|
||||||
|
Some(r) => r.id,
|
||||||
|
None => -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findUserById(db: SqlConn, id: Int): Option<User> with Sql = {
|
||||||
|
let row = Sql.queryOne(db, "SELECT * FROM users WHERE id = " ++ toString(id))
|
||||||
|
match row {
|
||||||
|
Some(r) => Some({ id: r.id, name: r.name, email: r.email }),
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findAllUsers(db: SqlConn): List<User> with Sql = {
|
||||||
|
let rows = Sql.query(db, "SELECT * FROM users ORDER BY name")
|
||||||
|
List.map(rows, fn(r) => { id: r.id, name: r.name, email: r.email })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with In-Memory Database
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn testUserRepository(): Unit with {Test, Sql} = {
|
||||||
|
// Each test gets a fresh database
|
||||||
|
let db = Sql.openMemory()
|
||||||
|
|
||||||
|
// Set up schema
|
||||||
|
Sql.execute(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
|
||||||
|
// Test
|
||||||
|
let id = createUser(db, "Test User")
|
||||||
|
Test.assertTrue(id > 0, "Should return valid ID")
|
||||||
|
|
||||||
|
let user = findUserById(db, id)
|
||||||
|
Test.assertTrue(Option.isSome(user), "Should find created user")
|
||||||
|
|
||||||
|
Sql.close(db)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handler for Testing (Mock Database)
|
||||||
|
|
||||||
|
The effect system lets you swap database implementations:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Production handler uses real SQLite
|
||||||
|
handler realDatabase(): Sql {
|
||||||
|
// Uses actual rusqlite implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handler uses in-memory storage
|
||||||
|
handler mockDatabase(): Sql {
|
||||||
|
let storage = ref []
|
||||||
|
|
||||||
|
fn execute(conn, sql) = {
|
||||||
|
// Parse and simulate SQL
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query(conn, sql) = {
|
||||||
|
// Return mock data
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run with mock database in tests
|
||||||
|
run userService() with {
|
||||||
|
Sql -> mockDatabase()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
```lux
|
||||||
|
type SqlConn // Opaque connection handle
|
||||||
|
type SqlRow // Row result with named column access
|
||||||
|
```
|
||||||
|
|
||||||
|
### Operations
|
||||||
|
|
||||||
|
| Function | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `Sql.open(path)` | `String -> SqlConn` | Open database file |
|
||||||
|
| `Sql.openMemory()` | `() -> SqlConn` | Open in-memory database |
|
||||||
|
| `Sql.close(conn)` | `SqlConn -> Unit` | Close connection |
|
||||||
|
| `Sql.execute(conn, sql)` | `(SqlConn, String) -> Int` | Execute statement, return affected rows |
|
||||||
|
| `Sql.query(conn, sql)` | `(SqlConn, String) -> List<SqlRow>` | Query, return all rows |
|
||||||
|
| `Sql.queryOne(conn, sql)` | `(SqlConn, String) -> Option<SqlRow>` | Query, return first row |
|
||||||
|
| `Sql.beginTx(conn)` | `SqlConn -> Unit` | Begin transaction |
|
||||||
|
| `Sql.commit(conn)` | `SqlConn -> Unit` | Commit transaction |
|
||||||
|
| `Sql.rollback(conn)` | `SqlConn -> Unit` | Rollback transaction |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Database operations can fail. In the current implementation, errors cause runtime failures. Future versions will support returning `Result` types:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Future API (not yet implemented)
|
||||||
|
fn safeQuery(db: SqlConn, sql: String): Result<List<SqlRow>, SqlError> with Sql = {
|
||||||
|
Sql.tryQuery(db, sql)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
1. **Batch inserts in transactions** - Much faster than individual inserts:
|
||||||
|
```lux
|
||||||
|
Sql.beginTx(db)
|
||||||
|
for item in items {
|
||||||
|
Sql.execute(db, "INSERT INTO data (value) VALUES (" ++ toString(item) ++ ")")
|
||||||
|
}
|
||||||
|
Sql.commit(db)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use `queryOne` for single results** - More efficient than `query` when you only need one row
|
||||||
|
|
||||||
|
3. **Create indexes** for frequently queried columns:
|
||||||
|
```lux
|
||||||
|
Sql.execute(db, "CREATE INDEX idx_users_email ON users(email)")
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Close connections** when done to free resources
|
||||||
|
|
||||||
|
## PostgreSQL Support
|
||||||
|
|
||||||
|
Lux also provides native PostgreSQL support through the `Postgres` effect. This is ideal for production applications requiring a full-featured relational database.
|
||||||
|
|
||||||
|
### Connecting to PostgreSQL
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn main(): Unit with {Console, Postgres} = {
|
||||||
|
// Connect using a connection string
|
||||||
|
let connStr = "host=localhost user=myuser password=mypass dbname=mydb"
|
||||||
|
let conn = Postgres.connect(connStr)
|
||||||
|
|
||||||
|
Console.print("Connected to PostgreSQL!")
|
||||||
|
|
||||||
|
// ... use the connection ...
|
||||||
|
|
||||||
|
Postgres.close(conn)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL Operations
|
||||||
|
|
||||||
|
The PostgreSQL API mirrors the SQLite API:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Execute non-returning queries
|
||||||
|
Postgres.execute(conn, "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)")
|
||||||
|
Postgres.execute(conn, "INSERT INTO users (name) VALUES ('Alice')")
|
||||||
|
|
||||||
|
// Query multiple rows
|
||||||
|
let users = Postgres.query(conn, "SELECT * FROM users")
|
||||||
|
for user in users {
|
||||||
|
Console.print("User: " ++ user.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query single row
|
||||||
|
match Postgres.queryOne(conn, "SELECT * FROM users WHERE id = 1") {
|
||||||
|
Some(user) => Console.print("Found: " ++ user.name),
|
||||||
|
None => Console.print("Not found")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL Transactions
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Start transaction
|
||||||
|
Postgres.beginTx(conn)
|
||||||
|
|
||||||
|
// Make changes
|
||||||
|
Postgres.execute(conn, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
|
||||||
|
Postgres.execute(conn, "UPDATE accounts SET balance = balance + 100 WHERE id = 2")
|
||||||
|
|
||||||
|
// Commit or rollback
|
||||||
|
Postgres.commit(conn)
|
||||||
|
// Or: Postgres.rollback(conn)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL API Reference
|
||||||
|
|
||||||
|
| Function | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `Postgres.connect(connStr)` | `String -> Int` | Connect to PostgreSQL, returns connection ID |
|
||||||
|
| `Postgres.close(conn)` | `Int -> Unit` | Close connection |
|
||||||
|
| `Postgres.execute(conn, sql)` | `(Int, String) -> Int` | Execute statement, return affected rows |
|
||||||
|
| `Postgres.query(conn, sql)` | `(Int, String) -> List<Row>` | Query, return all rows |
|
||||||
|
| `Postgres.queryOne(conn, sql)` | `(Int, String) -> Option<Row>` | Query, return first row |
|
||||||
|
| `Postgres.beginTx(conn)` | `Int -> Unit` | Begin transaction |
|
||||||
|
| `Postgres.commit(conn)` | `Int -> Unit` | Commit transaction |
|
||||||
|
| `Postgres.rollback(conn)` | `Int -> Unit` | Rollback transaction |
|
||||||
|
|
||||||
|
### When to Use SQLite vs PostgreSQL
|
||||||
|
|
||||||
|
| Feature | SQLite | PostgreSQL |
|
||||||
|
|---------|--------|------------|
|
||||||
|
| Setup | No setup needed | Requires server |
|
||||||
|
| Deployment | Single file | Server process |
|
||||||
|
| Concurrency | Limited | Excellent |
|
||||||
|
| Scale | Small-medium | Large |
|
||||||
|
| Features | Basic | Full RDBMS |
|
||||||
|
| Use case | Embedded, testing | Production |
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- No connection pooling yet
|
||||||
|
- No prepared statements / parameterized queries yet
|
||||||
|
- Limited type mapping (basic Int, String, Float)
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Effects Guide](./05-effects.md) - Understanding the effect system
|
||||||
|
- [Testing Guide](../testing.md) - Writing tests with mock handlers
|
||||||
|
- [Roadmap](../ROADMAP.md) - Planned database features
|
||||||
300
docs/guide/14-property-testing.md
Normal file
300
docs/guide/14-property-testing.md
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
# Property-Based Testing
|
||||||
|
|
||||||
|
Property-based testing is a powerful testing technique where you define properties that should hold for all inputs, and the testing framework generates random inputs to verify those properties. This guide shows how to use property-based testing in Lux.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Instead of writing tests with specific inputs:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn test_reverse(): Unit with {Test} = {
|
||||||
|
Test.assertEqual([3, 2, 1], List.reverse([1, 2, 3]))
|
||||||
|
Test.assertEqual([], List.reverse([]))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Property-based testing verifies general properties:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Property: Reversing a list twice gives back the original list
|
||||||
|
fn testReverseInvolutive(n: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then true
|
||||||
|
else {
|
||||||
|
let xs = genIntList(0, 100, 20)
|
||||||
|
if List.reverse(List.reverse(xs)) == xs then
|
||||||
|
testReverseInvolutive(n - 1)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generators
|
||||||
|
|
||||||
|
Generators create random test data. The `Random` effect provides the building blocks.
|
||||||
|
|
||||||
|
### Basic Generators
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Generate random integer in range
|
||||||
|
fn genInt(min: Int, max: Int): Int with {Random} =
|
||||||
|
Random.int(min, max)
|
||||||
|
|
||||||
|
// Generate random boolean
|
||||||
|
fn genBool(): Bool with {Random} =
|
||||||
|
Random.bool()
|
||||||
|
|
||||||
|
// Generate random float
|
||||||
|
fn genFloat(): Float with {Random} =
|
||||||
|
Random.float()
|
||||||
|
```
|
||||||
|
|
||||||
|
### String Generators
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let CHARS = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
// Generate random character
|
||||||
|
fn genChar(): String with {Random} = {
|
||||||
|
let idx = Random.int(0, 25)
|
||||||
|
String.substring(CHARS, idx, idx + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate random string up to maxLen characters
|
||||||
|
fn genString(maxLen: Int): String with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genStringHelper(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genStringHelper(len: Int): String with {Random} = {
|
||||||
|
if len <= 0 then ""
|
||||||
|
else genChar() + genStringHelper(len - 1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### List Generators
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Generate random list of integers
|
||||||
|
fn genIntList(min: Int, max: Int, maxLen: Int): List<Int> with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genIntListHelper(min, max, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genIntListHelper(min: Int, max: Int, len: Int): List<Int> with {Random} = {
|
||||||
|
if len <= 0 then []
|
||||||
|
else List.concat([Random.int(min, max)], genIntListHelper(min, max, len - 1))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Property Tests
|
||||||
|
|
||||||
|
### Pattern: Recursive Test Function
|
||||||
|
|
||||||
|
The most common pattern is a recursive function that runs N iterations:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn testProperty(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
Console.print(" PASS property_name (" + toString(count) + " tests)")
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// Generate random inputs
|
||||||
|
let x = genInt(0, 100)
|
||||||
|
let y = genInt(0, 100)
|
||||||
|
|
||||||
|
// Check property
|
||||||
|
if x + y == y + x then
|
||||||
|
testProperty(n - 1, count)
|
||||||
|
else {
|
||||||
|
Console.print(" FAIL property_name")
|
||||||
|
Console.print(" Counterexample: x=" + toString(x) + ", y=" + toString(y))
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Properties
|
||||||
|
|
||||||
|
Here are properties commonly verified in property-based testing:
|
||||||
|
|
||||||
|
**Involution** - Applying a function twice returns the original value:
|
||||||
|
```lux
|
||||||
|
// f(f(x)) == x
|
||||||
|
List.reverse(List.reverse(xs)) == xs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Idempotence** - Applying a function multiple times has the same effect as once:
|
||||||
|
```lux
|
||||||
|
// f(f(x)) == f(x)
|
||||||
|
List.sort(List.sort(xs)) == List.sort(xs)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Commutativity** - Order of arguments doesn't matter:
|
||||||
|
```lux
|
||||||
|
// f(a, b) == f(b, a)
|
||||||
|
a + b == b + a
|
||||||
|
a * b == b * a
|
||||||
|
```
|
||||||
|
|
||||||
|
**Associativity** - Grouping doesn't matter:
|
||||||
|
```lux
|
||||||
|
// f(f(a, b), c) == f(a, f(b, c))
|
||||||
|
(a + b) + c == a + (b + c)
|
||||||
|
(a * b) * c == a * (b * c)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Identity** - An element that doesn't change the result:
|
||||||
|
```lux
|
||||||
|
x + 0 == x
|
||||||
|
x * 1 == x
|
||||||
|
List.concat(xs, []) == xs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Length Preservation**:
|
||||||
|
```lux
|
||||||
|
List.length(List.reverse(xs)) == List.length(xs)
|
||||||
|
List.length(List.map(xs, f)) == List.length(xs)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Length Addition**:
|
||||||
|
```lux
|
||||||
|
List.length(List.concat(xs, ys)) == List.length(xs) + List.length(ys)
|
||||||
|
String.length(s1 + s2) == String.length(s1) + String.length(s2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Property-Based Testing Example
|
||||||
|
// Run with: lux examples/property_testing.lux
|
||||||
|
|
||||||
|
let CHARS = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
// Generators
|
||||||
|
fn genInt(min: Int, max: Int): Int with {Random} =
|
||||||
|
Random.int(min, max)
|
||||||
|
|
||||||
|
fn genIntList(min: Int, max: Int, maxLen: Int): List<Int> with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genIntListHelper(min, max, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genIntListHelper(min: Int, max: Int, len: Int): List<Int> with {Random} = {
|
||||||
|
if len <= 0 then []
|
||||||
|
else List.concat([Random.int(min, max)], genIntListHelper(min, max, len - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test helper
|
||||||
|
fn printResult(name: String, passed: Bool, count: Int): Unit with {Console} = {
|
||||||
|
if passed then
|
||||||
|
Console.print(" PASS " + name + " (" + toString(count) + " tests)")
|
||||||
|
else
|
||||||
|
Console.print(" FAIL " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property: reverse(reverse(xs)) == xs
|
||||||
|
fn testReverseInvolutive(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("reverse(reverse(xs)) == xs", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let xs = genIntList(0, 100, 20)
|
||||||
|
if List.reverse(List.reverse(xs)) == xs then
|
||||||
|
testReverseInvolutive(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("reverse(reverse(xs)) == xs", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property: addition is commutative
|
||||||
|
fn testAddCommutative(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("a + b == b + a", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let a = genInt(-1000, 1000)
|
||||||
|
let b = genInt(-1000, 1000)
|
||||||
|
if a + b == b + a then
|
||||||
|
testAddCommutative(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("a + b == b + a", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, Random} = {
|
||||||
|
Console.print("Property-Based Testing Demo")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Running 100 iterations per property...")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
testReverseInvolutive(100, 100)
|
||||||
|
testAddCommutative(100, 100)
|
||||||
|
|
||||||
|
Console.print("")
|
||||||
|
Console.print("All tests completed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stdlib Testing Module
|
||||||
|
|
||||||
|
The `stdlib/testing.lux` module provides pre-built generators:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
import stdlib.testing
|
||||||
|
|
||||||
|
// Available generators:
|
||||||
|
genInt(min, max) // Random integer in range
|
||||||
|
genIntUpTo(max) // Random integer 0 to max
|
||||||
|
genPositiveInt(max) // Random integer 1 to max
|
||||||
|
genBool() // Random boolean
|
||||||
|
genChar() // Random lowercase letter
|
||||||
|
genAlphaNum() // Random alphanumeric character
|
||||||
|
genString(maxLen) // Random string
|
||||||
|
genStringOfLength(len) // Random string of exact length
|
||||||
|
genIntList(min, max, maxLen) // Random list of integers
|
||||||
|
genBoolList(maxLen) // Random list of booleans
|
||||||
|
genStringList(maxStrLen, maxLen) // Random list of strings
|
||||||
|
|
||||||
|
// Helper functions:
|
||||||
|
shrinkInt(n) // Shrink integer toward zero
|
||||||
|
shrinkList(xs) // Shrink list by removing elements
|
||||||
|
shrinkString(s) // Shrink string by removing characters
|
||||||
|
isSorted(xs) // Check if list is sorted
|
||||||
|
sameElements(xs, ys) // Check if lists have same elements
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Start with many iterations**: Use 100+ iterations per property to catch edge cases.
|
||||||
|
|
||||||
|
2. **Test edge cases explicitly**: Property tests are great for general cases, but also write unit tests for known edge cases.
|
||||||
|
|
||||||
|
3. **Keep properties simple**: Each property should test one thing. Complex properties are harder to debug.
|
||||||
|
|
||||||
|
4. **Use good generators**: Match the distribution of your generators to realistic inputs.
|
||||||
|
|
||||||
|
5. **Print counterexamples**: When a test fails, print the failing inputs to help debugging.
|
||||||
|
|
||||||
|
6. **Combine with shrinking**: Shrinking finds minimal failing inputs, making debugging easier.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of property-based testing in Lux:
|
||||||
|
|
||||||
|
- No automatic shrinking (must be done manually)
|
||||||
|
- No seed control for reproducible tests
|
||||||
|
- No integration with `lux test` command (uses `Random` effect)
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Testing Guide](../testing.md) - Unit testing with the Test effect
|
||||||
|
- [Effects Guide](./05-effects.md) - Understanding the Random effect
|
||||||
|
- [Example](../../examples/property_testing.lux) - Complete working example
|
||||||
177
examples/http_api.lux
Normal file
177
examples/http_api.lux
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
// HTTP API Example
|
||||||
|
//
|
||||||
|
// A complete REST API demonstrating:
|
||||||
|
// - Route matching with path parameters
|
||||||
|
// - Response builders
|
||||||
|
// - JSON construction
|
||||||
|
//
|
||||||
|
// Run with: lux examples/http_api.lux
|
||||||
|
// Test with:
|
||||||
|
// curl http://localhost:8080/
|
||||||
|
// curl http://localhost:8080/users
|
||||||
|
// curl http://localhost:8080/users/42
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Response Helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn httpOk(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 200, body: body }
|
||||||
|
|
||||||
|
fn httpCreated(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 201, body: body }
|
||||||
|
|
||||||
|
fn httpNotFound(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 404, body: body }
|
||||||
|
|
||||||
|
fn httpBadRequest(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 400, body: body }
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// JSON Helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn jsonEscape(s: String): String =
|
||||||
|
String.replace(String.replace(s, "\\", "\\\\"), "\"", "\\\"")
|
||||||
|
|
||||||
|
fn jsonStr(key: String, value: String): String =
|
||||||
|
"\"" + jsonEscape(key) + "\":\"" + jsonEscape(value) + "\""
|
||||||
|
|
||||||
|
fn jsonNum(key: String, value: Int): String =
|
||||||
|
"\"" + jsonEscape(key) + "\":" + toString(value)
|
||||||
|
|
||||||
|
fn jsonObj(content: String): String =
|
||||||
|
"{" + content + "}"
|
||||||
|
|
||||||
|
fn jsonArr(content: String): String =
|
||||||
|
"[" + content + "]"
|
||||||
|
|
||||||
|
fn jsonError(message: String): String =
|
||||||
|
jsonObj(jsonStr("error", message))
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Path Matching
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn pathMatches(path: String, pattern: String): Bool = {
|
||||||
|
let pathParts = String.split(path, "/")
|
||||||
|
let patternParts = String.split(pattern, "/")
|
||||||
|
if List.length(pathParts) != List.length(patternParts) then false
|
||||||
|
else matchParts(pathParts, patternParts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matchParts(pathParts: List<String>, patternParts: List<String>): Bool = {
|
||||||
|
if List.length(pathParts) == 0 then true
|
||||||
|
else {
|
||||||
|
match List.head(pathParts) {
|
||||||
|
None => true,
|
||||||
|
Some(pathPart) => {
|
||||||
|
match List.head(patternParts) {
|
||||||
|
None => true,
|
||||||
|
Some(patternPart) => {
|
||||||
|
let isMatch = if String.startsWith(patternPart, ":") then true else pathPart == patternPart
|
||||||
|
if isMatch then {
|
||||||
|
let restPath = Option.getOrElse(List.tail(pathParts), [])
|
||||||
|
let restPattern = Option.getOrElse(List.tail(patternParts), [])
|
||||||
|
matchParts(restPath, restPattern)
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getPathSegment(path: String, index: Int): Option<String> = {
|
||||||
|
let parts = String.split(path, "/")
|
||||||
|
List.get(parts, index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Handlers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn indexHandler(): { status: Int, body: String } =
|
||||||
|
httpOk(jsonObj(jsonStr("message", "Welcome to Lux HTTP API")))
|
||||||
|
|
||||||
|
fn healthHandler(): { status: Int, body: String } =
|
||||||
|
httpOk(jsonObj(jsonStr("status", "healthy")))
|
||||||
|
|
||||||
|
fn listUsersHandler(): { status: Int, body: String } = {
|
||||||
|
let user1 = jsonObj(jsonNum("id", 1) + "," + jsonStr("name", "Alice"))
|
||||||
|
let user2 = jsonObj(jsonNum("id", 2) + "," + jsonStr("name", "Bob"))
|
||||||
|
httpOk(jsonArr(user1 + "," + user2))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getUserHandler(path: String): { status: Int, body: String } = {
|
||||||
|
match getPathSegment(path, 1) {
|
||||||
|
Some(id) => {
|
||||||
|
let body = jsonObj(jsonStr("id", id) + "," + jsonStr("name", "User " + id))
|
||||||
|
httpOk(body)
|
||||||
|
},
|
||||||
|
None => httpNotFound(jsonError("User not found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createUserHandler(body: String): { status: Int, body: String } = {
|
||||||
|
let newUser = jsonObj(jsonNum("id", 3) + "," + jsonStr("name", "New User"))
|
||||||
|
httpCreated(newUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Router
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn router(method: String, path: String, body: String): { status: Int, body: String } = {
|
||||||
|
if method == "GET" && path == "/" then indexHandler()
|
||||||
|
else if method == "GET" && path == "/health" then healthHandler()
|
||||||
|
else if method == "GET" && path == "/users" then listUsersHandler()
|
||||||
|
else if method == "GET" && pathMatches(path, "/users/:id") then getUserHandler(path)
|
||||||
|
else if method == "POST" && path == "/users" then createUserHandler(body)
|
||||||
|
else httpNotFound(jsonError("Not found: " + path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Server
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn serveLoop(remaining: Int): Unit with {Console, HttpServer} = {
|
||||||
|
if remaining <= 0 then {
|
||||||
|
Console.print("Max requests reached, stopping server.")
|
||||||
|
HttpServer.stop()
|
||||||
|
} else {
|
||||||
|
let req = HttpServer.accept()
|
||||||
|
Console.print(req.method + " " + req.path)
|
||||||
|
let resp = router(req.method, req.path, req.body)
|
||||||
|
HttpServer.respond(resp.status, resp.body)
|
||||||
|
serveLoop(remaining - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, HttpServer} = {
|
||||||
|
let port = 8080
|
||||||
|
let maxRequests = 10
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print(" Lux HTTP API Demo")
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Endpoints:")
|
||||||
|
Console.print(" GET / - API info")
|
||||||
|
Console.print(" GET /health - Health check")
|
||||||
|
Console.print(" GET /users - List users")
|
||||||
|
Console.print(" GET /users/:id - Get user by ID")
|
||||||
|
Console.print(" POST /users - Create user")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Try:")
|
||||||
|
Console.print(" curl http://localhost:8080/")
|
||||||
|
Console.print(" curl http://localhost:8080/users")
|
||||||
|
Console.print(" curl http://localhost:8080/users/42")
|
||||||
|
Console.print(" curl -X POST http://localhost:8080/users")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Starting server on port " + toString(port) + "...")
|
||||||
|
HttpServer.listen(port)
|
||||||
|
Console.print("Server listening!")
|
||||||
|
serveLoop(maxRequests)
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
98
examples/http_router.lux
Normal file
98
examples/http_router.lux
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// HTTP Router Example
|
||||||
|
//
|
||||||
|
// Demonstrates the HTTP helper library with:
|
||||||
|
// - Path pattern matching
|
||||||
|
// - Response builders
|
||||||
|
// - JSON helpers
|
||||||
|
//
|
||||||
|
// Run with: lux examples/http_router.lux
|
||||||
|
// Test with:
|
||||||
|
// curl http://localhost:8080/
|
||||||
|
// curl http://localhost:8080/users
|
||||||
|
// curl http://localhost:8080/users/42
|
||||||
|
|
||||||
|
import stdlib/http
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Route Handlers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn indexHandler(): { status: Int, body: String } =
|
||||||
|
httpOk("Welcome to Lux HTTP Framework!")
|
||||||
|
|
||||||
|
fn listUsersHandler(): { status: Int, body: String } = {
|
||||||
|
let user1 = jsonObject(jsonJoin([jsonNumber("id", 1), jsonString("name", "Alice")]))
|
||||||
|
let user2 = jsonObject(jsonJoin([jsonNumber("id", 2), jsonString("name", "Bob")]))
|
||||||
|
let users = jsonArray(jsonJoin([user1, user2]))
|
||||||
|
httpOk(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getUserHandler(path: String): { status: Int, body: String } = {
|
||||||
|
match getPathSegment(path, 1) {
|
||||||
|
Some(id) => {
|
||||||
|
let body = jsonObject(jsonJoin([jsonString("id", id), jsonString("name", "User " + id)]))
|
||||||
|
httpOk(body)
|
||||||
|
},
|
||||||
|
None => httpNotFound(jsonErrorMsg("User ID required"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn healthHandler(): { status: Int, body: String } =
|
||||||
|
httpOk(jsonObject(jsonString("status", "healthy")))
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Router
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn router(method: String, path: String, body: String): { status: Int, body: String } = {
|
||||||
|
if method == "GET" && path == "/" then indexHandler()
|
||||||
|
else if method == "GET" && path == "/health" then healthHandler()
|
||||||
|
else if method == "GET" && path == "/users" then listUsersHandler()
|
||||||
|
else if method == "GET" && pathMatches(path, "/users/:id") then getUserHandler(path)
|
||||||
|
else httpNotFound(jsonErrorMsg("Not found: " + path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Server
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn serveLoop(remaining: Int): Unit with {Console, HttpServer} = {
|
||||||
|
if remaining <= 0 then {
|
||||||
|
Console.print("Max requests reached, stopping server.")
|
||||||
|
HttpServer.stop()
|
||||||
|
} else {
|
||||||
|
let req = HttpServer.accept()
|
||||||
|
Console.print(req.method + " " + req.path)
|
||||||
|
let resp = router(req.method, req.path, req.body)
|
||||||
|
HttpServer.respond(resp.status, resp.body)
|
||||||
|
serveLoop(remaining - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, HttpServer} = {
|
||||||
|
let port = 8080
|
||||||
|
let maxRequests = 10
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print(" Lux HTTP Router Demo")
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Endpoints:")
|
||||||
|
Console.print(" GET / - Welcome message")
|
||||||
|
Console.print(" GET /health - Health check")
|
||||||
|
Console.print(" GET /users - List all users")
|
||||||
|
Console.print(" GET /users/:id - Get user by ID")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Try:")
|
||||||
|
Console.print(" curl http://localhost:8080/")
|
||||||
|
Console.print(" curl http://localhost:8080/users")
|
||||||
|
Console.print(" curl http://localhost:8080/users/42")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Starting server on port " + toString(port) + "...")
|
||||||
|
Console.print("Will handle " + toString(maxRequests) + " requests then stop.")
|
||||||
|
Console.print("")
|
||||||
|
HttpServer.listen(port)
|
||||||
|
Console.print("Server listening!")
|
||||||
|
serveLoop(maxRequests)
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
185
examples/postgres_demo.lux
Normal file
185
examples/postgres_demo.lux
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// PostgreSQL Database Example
|
||||||
|
//
|
||||||
|
// Demonstrates the Postgres effect for database operations.
|
||||||
|
//
|
||||||
|
// Prerequisites:
|
||||||
|
// - PostgreSQL server running locally
|
||||||
|
// - Database 'testdb' created
|
||||||
|
// - User 'testuser' with password 'testpass'
|
||||||
|
//
|
||||||
|
// To set up:
|
||||||
|
// createdb testdb
|
||||||
|
// psql testdb -c "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, email TEXT);"
|
||||||
|
//
|
||||||
|
// Run with: lux examples/postgres_demo.lux
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Helper Functions
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn jsonStr(key: String, value: String): String =
|
||||||
|
"\"" + key + "\":\"" + value + "\""
|
||||||
|
|
||||||
|
fn jsonNum(key: String, value: Int): String =
|
||||||
|
"\"" + key + "\":" + toString(value)
|
||||||
|
|
||||||
|
fn jsonObj(content: String): String =
|
||||||
|
"{" + content + "}"
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Database Operations
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Insert a user
|
||||||
|
fn insertUser(connId: Int, name: String, email: String): Int with {Console, Postgres} = {
|
||||||
|
let sql = "INSERT INTO users (name, email) VALUES ('" + name + "', '" + email + "') RETURNING id"
|
||||||
|
Console.print("Inserting user: " + name)
|
||||||
|
match Postgres.queryOne(connId, sql) {
|
||||||
|
Some(row) => {
|
||||||
|
Console.print(" Inserted with ID: " + toString(row.id))
|
||||||
|
row.id
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Console.print(" Insert failed")
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all users
|
||||||
|
fn getUsers(connId: Int): Unit with {Console, Postgres} = {
|
||||||
|
Console.print("Fetching all users...")
|
||||||
|
let rows = Postgres.query(connId, "SELECT id, name, email FROM users ORDER BY id")
|
||||||
|
Console.print(" Found " + toString(List.length(rows)) + " users:")
|
||||||
|
List.forEach(rows, fn(row: { id: Int, name: String, email: String }): Unit with {Console} => {
|
||||||
|
Console.print(" - " + toString(row.id) + ": " + row.name + " <" + row.email + ">")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user by ID
|
||||||
|
fn getUserById(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
||||||
|
let sql = "SELECT id, name, email FROM users WHERE id = " + toString(id)
|
||||||
|
Console.print("Looking up user " + toString(id) + "...")
|
||||||
|
match Postgres.queryOne(connId, sql) {
|
||||||
|
Some(row) => Console.print(" Found: " + row.name + " <" + row.email + ">"),
|
||||||
|
None => Console.print(" User not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user email
|
||||||
|
fn updateUserEmail(connId: Int, id: Int, newEmail: String): Unit with {Console, Postgres} = {
|
||||||
|
let sql = "UPDATE users SET email = '" + newEmail + "' WHERE id = " + toString(id)
|
||||||
|
Console.print("Updating user " + toString(id) + " email to " + newEmail)
|
||||||
|
let affected = Postgres.execute(connId, sql)
|
||||||
|
Console.print(" Rows affected: " + toString(affected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
fn deleteUser(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
||||||
|
let sql = "DELETE FROM users WHERE id = " + toString(id)
|
||||||
|
Console.print("Deleting user " + toString(id))
|
||||||
|
let affected = Postgres.execute(connId, sql)
|
||||||
|
Console.print(" Rows affected: " + toString(affected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Transaction Example
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn transactionDemo(connId: Int): Unit with {Console, Postgres} = {
|
||||||
|
Console.print("")
|
||||||
|
Console.print("=== Transaction Demo ===")
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
Console.print("Beginning transaction...")
|
||||||
|
Postgres.beginTx(connId)
|
||||||
|
|
||||||
|
// Make some changes
|
||||||
|
insertUser(connId, "TxUser1", "tx1@example.com")
|
||||||
|
insertUser(connId, "TxUser2", "tx2@example.com")
|
||||||
|
|
||||||
|
// Show users before commit
|
||||||
|
Console.print("Users before commit:")
|
||||||
|
getUsers(connId)
|
||||||
|
|
||||||
|
// Commit the transaction
|
||||||
|
Console.print("Committing transaction...")
|
||||||
|
Postgres.commit(connId)
|
||||||
|
|
||||||
|
Console.print("Transaction committed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, Postgres} = {
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print(" PostgreSQL Demo")
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Connect to database
|
||||||
|
Console.print("Connecting to PostgreSQL...")
|
||||||
|
let connStr = "host=localhost user=testuser password=testpass dbname=testdb"
|
||||||
|
let connId = Postgres.connect(connStr)
|
||||||
|
Console.print("Connected! Connection ID: " + toString(connId))
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Create table if not exists
|
||||||
|
Console.print("Creating users table...")
|
||||||
|
Postgres.execute(connId, "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL)")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Clear table for demo
|
||||||
|
Console.print("Clearing existing data...")
|
||||||
|
Postgres.execute(connId, "DELETE FROM users")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Insert some users
|
||||||
|
Console.print("=== Inserting Users ===")
|
||||||
|
let id1 = insertUser(connId, "Alice", "alice@example.com")
|
||||||
|
let id2 = insertUser(connId, "Bob", "bob@example.com")
|
||||||
|
let id3 = insertUser(connId, "Charlie", "charlie@example.com")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Query all users
|
||||||
|
Console.print("=== All Users ===")
|
||||||
|
getUsers(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Query single user
|
||||||
|
Console.print("=== Single User Lookup ===")
|
||||||
|
getUserById(connId, id2)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Update user
|
||||||
|
Console.print("=== Update User ===")
|
||||||
|
updateUserEmail(connId, id2, "bob.new@example.com")
|
||||||
|
getUserById(connId, id2)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
Console.print("=== Delete User ===")
|
||||||
|
deleteUser(connId, id3)
|
||||||
|
getUsers(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Transaction demo
|
||||||
|
transactionDemo(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Final state
|
||||||
|
Console.print("=== Final State ===")
|
||||||
|
getUsers(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Close connection
|
||||||
|
Console.print("Closing connection...")
|
||||||
|
Postgres.close(connId)
|
||||||
|
Console.print("Done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This will fail if PostgreSQL is not running
|
||||||
|
// To test the syntax only, you can comment out the last line
|
||||||
|
let output = run main() with {}
|
||||||
255
examples/property_testing.lux
Normal file
255
examples/property_testing.lux
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
// Property-Based Testing Example
|
||||||
|
//
|
||||||
|
// This example demonstrates property-based testing in Lux,
|
||||||
|
// where we verify properties hold for randomly generated inputs.
|
||||||
|
//
|
||||||
|
// Run with: lux examples/property_testing.lux
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Generator Functions (using Random effect)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
let CHARS = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
fn genInt(min: Int, max: Int): Int with {Random} =
|
||||||
|
Random.int(min, max)
|
||||||
|
|
||||||
|
fn genIntList(min: Int, max: Int, maxLen: Int): List<Int> with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genIntListHelper(min, max, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genIntListHelper(min: Int, max: Int, len: Int): List<Int> with {Random} = {
|
||||||
|
if len <= 0 then
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
List.concat([Random.int(min, max)], genIntListHelper(min, max, len - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genChar(): String with {Random} = {
|
||||||
|
let idx = Random.int(0, 25)
|
||||||
|
String.substring(CHARS, idx, idx + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genString(maxLen: Int): String with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genStringHelper(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genStringHelper(len: Int): String with {Random} = {
|
||||||
|
if len <= 0 then
|
||||||
|
""
|
||||||
|
else
|
||||||
|
genChar() + genStringHelper(len - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Test Runner State
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn printResult(name: String, passed: Bool, count: Int): Unit with {Console} = {
|
||||||
|
if passed then
|
||||||
|
Console.print(" PASS " + name + " (" + toString(count) + " tests)")
|
||||||
|
else
|
||||||
|
Console.print(" FAIL " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Property Tests
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Test: List reverse is involutive
|
||||||
|
fn testReverseInvolutive(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("reverse(reverse(xs)) == xs", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let xs = genIntList(0, 100, 20)
|
||||||
|
if List.reverse(List.reverse(xs)) == xs then
|
||||||
|
testReverseInvolutive(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("reverse(reverse(xs)) == xs", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: List reverse preserves length
|
||||||
|
fn testReverseLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("length(reverse(xs)) == length(xs)", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let xs = genIntList(0, 100, 20)
|
||||||
|
if List.length(List.reverse(xs)) == List.length(xs) then
|
||||||
|
testReverseLength(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("length(reverse(xs)) == length(xs)", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: List map preserves length
|
||||||
|
fn testMapLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("length(map(xs, f)) == length(xs)", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let xs = genIntList(0, 100, 20)
|
||||||
|
if List.length(List.map(xs, fn(x) => x * 2)) == List.length(xs) then
|
||||||
|
testMapLength(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("length(map(xs, f)) == length(xs)", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: List concat length is sum
|
||||||
|
fn testConcatLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("length(xs ++ ys) == length(xs) + length(ys)", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let xs = genIntList(0, 50, 10)
|
||||||
|
let ys = genIntList(0, 50, 10)
|
||||||
|
if List.length(List.concat(xs, ys)) == List.length(xs) + List.length(ys) then
|
||||||
|
testConcatLength(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("length(xs ++ ys) == length(xs) + length(ys)", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Addition is commutative
|
||||||
|
fn testAddCommutative(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("a + b == b + a", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let a = genInt(-1000, 1000)
|
||||||
|
let b = genInt(-1000, 1000)
|
||||||
|
if a + b == b + a then
|
||||||
|
testAddCommutative(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("a + b == b + a", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Multiplication is associative
|
||||||
|
fn testMulAssociative(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("(a * b) * c == a * (b * c)", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let a = genInt(-100, 100)
|
||||||
|
let b = genInt(-100, 100)
|
||||||
|
let c = genInt(-100, 100)
|
||||||
|
if (a * b) * c == a * (b * c) then
|
||||||
|
testMulAssociative(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("(a * b) * c == a * (b * c)", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: String concat length is sum
|
||||||
|
fn testStringConcatLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("length(s1 + s2) == length(s1) + length(s2)", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let s1 = genString(10)
|
||||||
|
let s2 = genString(10)
|
||||||
|
if String.length(s1 + s2) == String.length(s1) + String.length(s2) then
|
||||||
|
testStringConcatLength(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("length(s1 + s2) == length(s1) + length(s2)", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Zero is identity for addition
|
||||||
|
fn testAddIdentity(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("x + 0 == x && 0 + x == x", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let x = genInt(-10000, 10000)
|
||||||
|
if x + 0 == x && 0 + x == x then
|
||||||
|
testAddIdentity(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("x + 0 == x && 0 + x == x", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Filter reduces or maintains length
|
||||||
|
fn testFilterLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("length(filter(xs, p)) <= length(xs)", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let xs = genIntList(0, 100, 20)
|
||||||
|
if List.length(List.filter(xs, fn(x) => x > 50)) <= List.length(xs) then
|
||||||
|
testFilterLength(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("length(filter(xs, p)) <= length(xs)", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Empty list is identity for concat
|
||||||
|
fn testConcatIdentity(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
|
if n <= 0 then {
|
||||||
|
printResult("concat(xs, []) == xs && concat([], xs) == xs", true, count)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let xs = genIntList(0, 100, 10)
|
||||||
|
if List.concat(xs, []) == xs && List.concat([], xs) == xs then
|
||||||
|
testConcatIdentity(n - 1, count)
|
||||||
|
else {
|
||||||
|
printResult("concat(xs, []) == xs && concat([], xs) == xs", false, count - n + 1)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, Random} = {
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print(" Property-Based Testing Demo")
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Running 100 iterations per property...")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
testReverseInvolutive(100, 100)
|
||||||
|
testReverseLength(100, 100)
|
||||||
|
testMapLength(100, 100)
|
||||||
|
testConcatLength(100, 100)
|
||||||
|
testAddCommutative(100, 100)
|
||||||
|
testMulAssociative(100, 100)
|
||||||
|
testStringConcatLength(100, 100)
|
||||||
|
testAddIdentity(100, 100)
|
||||||
|
testFilterLength(100, 100)
|
||||||
|
testConcatIdentity(100, 100)
|
||||||
|
|
||||||
|
Console.print("")
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print(" All property tests completed!")
|
||||||
|
Console.print("========================================")
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
87
flake.nix
87
flake.nix
@@ -24,6 +24,9 @@
|
|||||||
cargo-edit
|
cargo-edit
|
||||||
pkg-config
|
pkg-config
|
||||||
openssl
|
openssl
|
||||||
|
# Benchmark tools
|
||||||
|
hyperfine
|
||||||
|
poop
|
||||||
];
|
];
|
||||||
|
|
||||||
RUST_BACKTRACE = "1";
|
RUST_BACKTRACE = "1";
|
||||||
@@ -64,6 +67,90 @@
|
|||||||
|
|
||||||
nativeBuildInputs = [ pkgs.pkg-config ];
|
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||||
buildInputs = [ pkgs.openssl ];
|
buildInputs = [ pkgs.openssl ];
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Benchmark scripts
|
||||||
|
apps = {
|
||||||
|
# Run hyperfine benchmark comparison
|
||||||
|
bench = {
|
||||||
|
type = "app";
|
||||||
|
program = toString (pkgs.writeShellScript "lux-bench" ''
|
||||||
|
set -e
|
||||||
|
echo "=== Lux Performance Benchmarks ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Build Lux
|
||||||
|
echo "Building Lux..."
|
||||||
|
cd ${self}
|
||||||
|
${pkgs.cargo}/bin/cargo build --release 2>/dev/null
|
||||||
|
|
||||||
|
# Compile benchmarks
|
||||||
|
echo "Compiling benchmark binaries..."
|
||||||
|
./target/release/lux compile benchmarks/fib.lux -o /tmp/fib_lux 2>/dev/null
|
||||||
|
${pkgs.gcc}/bin/gcc -O3 benchmarks/fib.c -o /tmp/fib_c 2>/dev/null
|
||||||
|
${pkgs.rustc}/bin/rustc -C opt-level=3 -C lto benchmarks/fib.rs -o /tmp/fib_rust 2>/dev/null
|
||||||
|
${pkgs.zig}/bin/zig build-exe benchmarks/fib.zig -O ReleaseFast -femit-bin=/tmp/fib_zig 2>/dev/null
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Running hyperfine benchmark..."
|
||||||
|
echo ""
|
||||||
|
${pkgs.hyperfine}/bin/hyperfine --warmup 3 --runs 10 \
|
||||||
|
--export-markdown /tmp/bench_results.md \
|
||||||
|
'/tmp/fib_lux' \
|
||||||
|
'/tmp/fib_c' \
|
||||||
|
'/tmp/fib_rust' \
|
||||||
|
'/tmp/fib_zig'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Results saved to /tmp/bench_results.md"
|
||||||
|
'');
|
||||||
|
};
|
||||||
|
|
||||||
|
# Run poop benchmark for detailed CPU metrics
|
||||||
|
bench-poop = {
|
||||||
|
type = "app";
|
||||||
|
program = toString (pkgs.writeShellScript "lux-bench-poop" ''
|
||||||
|
set -e
|
||||||
|
echo "=== Lux Performance Benchmarks (poop) ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Build Lux
|
||||||
|
echo "Building Lux..."
|
||||||
|
cd ${self}
|
||||||
|
${pkgs.cargo}/bin/cargo build --release 2>/dev/null
|
||||||
|
|
||||||
|
# Compile benchmarks
|
||||||
|
echo "Compiling benchmark binaries..."
|
||||||
|
./target/release/lux compile benchmarks/fib.lux -o /tmp/fib_lux 2>/dev/null
|
||||||
|
${pkgs.gcc}/bin/gcc -O3 benchmarks/fib.c -o /tmp/fib_c 2>/dev/null
|
||||||
|
${pkgs.rustc}/bin/rustc -C opt-level=3 -C lto benchmarks/fib.rs -o /tmp/fib_rust 2>/dev/null
|
||||||
|
${pkgs.zig}/bin/zig build-exe benchmarks/fib.zig -O ReleaseFast -femit-bin=/tmp/fib_zig 2>/dev/null
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Running poop benchmark (detailed CPU metrics)..."
|
||||||
|
echo ""
|
||||||
|
${pkgs.poop}/bin/poop '/tmp/fib_c' '/tmp/fib_lux' '/tmp/fib_rust' '/tmp/fib_zig'
|
||||||
|
'');
|
||||||
|
};
|
||||||
|
|
||||||
|
# Quick benchmark (just Lux vs C)
|
||||||
|
bench-quick = {
|
||||||
|
type = "app";
|
||||||
|
program = toString (pkgs.writeShellScript "lux-bench-quick" ''
|
||||||
|
set -e
|
||||||
|
echo "=== Quick Lux vs C Benchmark ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd ${self}
|
||||||
|
${pkgs.cargo}/bin/cargo build --release 2>/dev/null
|
||||||
|
./target/release/lux compile benchmarks/fib.lux -o /tmp/fib_lux 2>/dev/null
|
||||||
|
${pkgs.gcc}/bin/gcc -O3 benchmarks/fib.c -o /tmp/fib_c 2>/dev/null
|
||||||
|
|
||||||
|
${pkgs.hyperfine}/bin/hyperfine --warmup 3 '/tmp/fib_lux' '/tmp/fib_c'
|
||||||
|
'');
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -426,6 +426,13 @@ impl CBackend {
|
|||||||
self.writeln("// Closure representation: env pointer + function pointer");
|
self.writeln("// Closure representation: env pointer + function pointer");
|
||||||
self.writeln("struct LuxClosure_s { void* env; void* fn_ptr; };");
|
self.writeln("struct LuxClosure_s { void* env; void* fn_ptr; };");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
|
self.writeln("// List struct body (typedef declared above)");
|
||||||
|
self.writeln("struct LuxList_s {");
|
||||||
|
self.writeln(" void** elements;");
|
||||||
|
self.writeln(" int64_t length;");
|
||||||
|
self.writeln(" int64_t capacity;");
|
||||||
|
self.writeln("};");
|
||||||
|
self.writeln("");
|
||||||
self.writeln("// === Reference Counting Infrastructure ===");
|
self.writeln("// === Reference Counting Infrastructure ===");
|
||||||
self.writeln("// Perceus-inspired RC system for automatic memory management.");
|
self.writeln("// Perceus-inspired RC system for automatic memory management.");
|
||||||
self.writeln("// See docs/REFERENCE_COUNTING.md for details.");
|
self.writeln("// See docs/REFERENCE_COUNTING.md for details.");
|
||||||
@@ -1247,6 +1254,125 @@ impl CBackend {
|
|||||||
self.writeln(" return result;");
|
self.writeln(" return result;");
|
||||||
self.writeln("}");
|
self.writeln("}");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
|
self.writeln("static LuxString lux_string_from_char(char c) {");
|
||||||
|
self.writeln(" LuxString result = (LuxString)lux_rc_alloc(2, LUX_TAG_STRING);");
|
||||||
|
self.writeln(" if (!result) return \"\";");
|
||||||
|
self.writeln(" result[0] = c;");
|
||||||
|
self.writeln(" result[1] = '\\0';");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* lux_string_chars(LuxString s) {");
|
||||||
|
self.writeln(" size_t len = s ? strlen(s) : 0;");
|
||||||
|
self.writeln(" LuxList* list = lux_list_new(len);");
|
||||||
|
self.writeln(" for (size_t i = 0; i < len; i++) {");
|
||||||
|
self.writeln(" LuxString ch = lux_string_from_char(s[i]);");
|
||||||
|
self.writeln(" lux_list_push(list, (void*)ch);");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" return list;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxString lux_string_substring(LuxString s, LuxInt start, LuxInt len) {");
|
||||||
|
self.writeln(" if (!s) return \"\";");
|
||||||
|
self.writeln(" size_t slen = strlen(s);");
|
||||||
|
self.writeln(" if (start < 0) start = 0;");
|
||||||
|
self.writeln(" if ((size_t)start >= slen) return \"\";");
|
||||||
|
self.writeln(" if (len < 0) len = 0;");
|
||||||
|
self.writeln(" if ((size_t)(start + len) > slen) len = slen - start;");
|
||||||
|
self.writeln(" LuxString result = (LuxString)lux_rc_alloc(len + 1, LUX_TAG_STRING);");
|
||||||
|
self.writeln(" if (!result) return \"\";");
|
||||||
|
self.writeln(" strncpy(result, s + start, len);");
|
||||||
|
self.writeln(" result[len] = '\\0';");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxString lux_string_to_upper(LuxString s) {");
|
||||||
|
self.writeln(" if (!s) return \"\";");
|
||||||
|
self.writeln(" size_t len = strlen(s);");
|
||||||
|
self.writeln(" LuxString result = (LuxString)lux_rc_alloc(len + 1, LUX_TAG_STRING);");
|
||||||
|
self.writeln(" if (!result) return \"\";");
|
||||||
|
self.writeln(" for (size_t i = 0; i < len; i++) {");
|
||||||
|
self.writeln(" result[i] = (s[i] >= 'a' && s[i] <= 'z') ? s[i] - 32 : s[i];");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" result[len] = '\\0';");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxString lux_string_to_lower(LuxString s) {");
|
||||||
|
self.writeln(" if (!s) return \"\";");
|
||||||
|
self.writeln(" size_t len = strlen(s);");
|
||||||
|
self.writeln(" LuxString result = (LuxString)lux_rc_alloc(len + 1, LUX_TAG_STRING);");
|
||||||
|
self.writeln(" if (!result) return \"\";");
|
||||||
|
self.writeln(" for (size_t i = 0; i < len; i++) {");
|
||||||
|
self.writeln(" result[i] = (s[i] >= 'A' && s[i] <= 'Z') ? s[i] + 32 : s[i];");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" result[len] = '\\0';");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxString lux_string_replace(LuxString s, LuxString from, LuxString to) {");
|
||||||
|
self.writeln(" if (!s || !from || !*from) return s ? lux_string_dup(s) : \"\";");
|
||||||
|
self.writeln(" if (!to) to = \"\";");
|
||||||
|
self.writeln(" size_t from_len = strlen(from);");
|
||||||
|
self.writeln(" size_t to_len = strlen(to);");
|
||||||
|
self.writeln(" size_t count = 0;");
|
||||||
|
self.writeln(" const char* p = s;");
|
||||||
|
self.writeln(" while ((p = strstr(p, from)) != NULL) { count++; p += from_len; }");
|
||||||
|
self.writeln(" size_t new_len = strlen(s) + count * (to_len - from_len);");
|
||||||
|
self.writeln(" LuxString result = (LuxString)lux_rc_alloc(new_len + 1, LUX_TAG_STRING);");
|
||||||
|
self.writeln(" if (!result) return \"\";");
|
||||||
|
self.writeln(" char* out = result;");
|
||||||
|
self.writeln(" p = s;");
|
||||||
|
self.writeln(" const char* found;");
|
||||||
|
self.writeln(" while ((found = strstr(p, from)) != NULL) {");
|
||||||
|
self.writeln(" size_t prefix_len = found - p;");
|
||||||
|
self.writeln(" memcpy(out, p, prefix_len);");
|
||||||
|
self.writeln(" out += prefix_len;");
|
||||||
|
self.writeln(" memcpy(out, to, to_len);");
|
||||||
|
self.writeln(" out += to_len;");
|
||||||
|
self.writeln(" p = found + from_len;");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" strcpy(out, p);");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxBool lux_string_starts_with(LuxString s, LuxString prefix) {");
|
||||||
|
self.writeln(" if (!s || !prefix) return 0;");
|
||||||
|
self.writeln(" size_t prefix_len = strlen(prefix);");
|
||||||
|
self.writeln(" if (strlen(s) < prefix_len) return 0;");
|
||||||
|
self.writeln(" return strncmp(s, prefix, prefix_len) == 0;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxBool lux_string_ends_with(LuxString s, LuxString suffix) {");
|
||||||
|
self.writeln(" if (!s || !suffix) return 0;");
|
||||||
|
self.writeln(" size_t s_len = strlen(s);");
|
||||||
|
self.writeln(" size_t suffix_len = strlen(suffix);");
|
||||||
|
self.writeln(" if (s_len < suffix_len) return 0;");
|
||||||
|
self.writeln(" return strcmp(s + s_len - suffix_len, suffix) == 0;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxString lux_string_join(LuxList* list, LuxString sep) {");
|
||||||
|
self.writeln(" if (!list || list->length == 0) return \"\";");
|
||||||
|
self.writeln(" if (!sep) sep = \"\";");
|
||||||
|
self.writeln(" size_t sep_len = strlen(sep);");
|
||||||
|
self.writeln(" size_t total_len = 0;");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < list->length; i++) {");
|
||||||
|
self.writeln(" LuxString elem = (LuxString)list->elements[i];");
|
||||||
|
self.writeln(" if (elem) total_len += strlen(elem);");
|
||||||
|
self.writeln(" if (i > 0) total_len += sep_len;");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" LuxString result = (LuxString)lux_rc_alloc(total_len + 1, LUX_TAG_STRING);");
|
||||||
|
self.writeln(" if (!result) return \"\";");
|
||||||
|
self.writeln(" char* out = result;");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < list->length; i++) {");
|
||||||
|
self.writeln(" if (i > 0) { strcpy(out, sep); out += sep_len; }");
|
||||||
|
self.writeln(" LuxString elem = (LuxString)list->elements[i];");
|
||||||
|
self.writeln(" if (elem) { strcpy(out, elem); out += strlen(elem); }");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" *out = '\\0';");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
self.writeln("// Default evidence with built-in handlers");
|
self.writeln("// Default evidence with built-in handlers");
|
||||||
self.writeln("static LuxEvidence default_evidence = {");
|
self.writeln("static LuxEvidence default_evidence = {");
|
||||||
self.writeln(" .console = &default_console_handler,");
|
self.writeln(" .console = &default_console_handler,");
|
||||||
@@ -1259,17 +1385,8 @@ impl CBackend {
|
|||||||
self.writeln(" .process = &default_process_handler");
|
self.writeln(" .process = &default_process_handler");
|
||||||
self.writeln("};");
|
self.writeln("};");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
self.writeln("// === List Types ===");
|
self.writeln("// === List Operations ===");
|
||||||
self.writeln("");
|
self.writeln("// (LuxList struct defined earlier, before string functions)");
|
||||||
self.writeln("// LuxList struct body (typedef declared earlier for drop specialization)");
|
|
||||||
self.writeln("struct LuxList_s {");
|
|
||||||
self.writeln(" void** elements;");
|
|
||||||
self.writeln(" int64_t length;");
|
|
||||||
self.writeln(" int64_t capacity;");
|
|
||||||
self.writeln("};");
|
|
||||||
self.writeln("");
|
|
||||||
// Note: Option type is already defined earlier (before handler structs)
|
|
||||||
self.writeln("");
|
|
||||||
|
|
||||||
// Emit specialized decref implementations (now that types are defined)
|
// Emit specialized decref implementations (now that types are defined)
|
||||||
self.emit_specialized_decref_implementations();
|
self.emit_specialized_decref_implementations();
|
||||||
@@ -2863,6 +2980,72 @@ impl CBackend {
|
|||||||
let s = self.emit_expr(&args[0])?;
|
let s = self.emit_expr(&args[0])?;
|
||||||
return Ok(format!("lux_string_parseFloat({})", s));
|
return Ok(format!("lux_string_parseFloat({})", s));
|
||||||
}
|
}
|
||||||
|
"fromChar" => {
|
||||||
|
let c = self.emit_expr(&args[0])?;
|
||||||
|
// Create temp variable and track for cleanup (returns RC-managed string)
|
||||||
|
let temp = format!("_fromchar_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("LuxString {} = lux_string_from_char({});", temp, c));
|
||||||
|
self.register_rc_var(&temp, "LuxString");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
|
"chars" => {
|
||||||
|
let s = self.emit_expr(&args[0])?;
|
||||||
|
// Create temp variable and track for cleanup (returns RC-managed list)
|
||||||
|
let temp = format!("_chars_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("LuxList* {} = lux_string_chars({});", temp, s));
|
||||||
|
self.register_rc_var(&temp, "LuxList*");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
|
"substring" => {
|
||||||
|
let s = self.emit_expr(&args[0])?;
|
||||||
|
let start = self.emit_expr(&args[1])?;
|
||||||
|
let len = self.emit_expr(&args[2])?;
|
||||||
|
let temp = format!("_substr_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("LuxString {} = lux_string_substring({}, {}, {});", temp, s, start, len));
|
||||||
|
self.register_rc_var(&temp, "LuxString");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
|
"toUpper" => {
|
||||||
|
let s = self.emit_expr(&args[0])?;
|
||||||
|
let temp = format!("_upper_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("LuxString {} = lux_string_to_upper({});", temp, s));
|
||||||
|
self.register_rc_var(&temp, "LuxString");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
|
"toLower" => {
|
||||||
|
let s = self.emit_expr(&args[0])?;
|
||||||
|
let temp = format!("_lower_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("LuxString {} = lux_string_to_lower({});", temp, s));
|
||||||
|
self.register_rc_var(&temp, "LuxString");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
|
"replace" => {
|
||||||
|
let s = self.emit_expr(&args[0])?;
|
||||||
|
let from = self.emit_expr(&args[1])?;
|
||||||
|
let to = self.emit_expr(&args[2])?;
|
||||||
|
let temp = format!("_replace_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("LuxString {} = lux_string_replace({}, {}, {});", temp, s, from, to));
|
||||||
|
self.register_rc_var(&temp, "LuxString");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
|
"startsWith" => {
|
||||||
|
let s = self.emit_expr(&args[0])?;
|
||||||
|
let prefix = self.emit_expr(&args[1])?;
|
||||||
|
return Ok(format!("lux_string_starts_with({}, {})", s, prefix));
|
||||||
|
}
|
||||||
|
"endsWith" => {
|
||||||
|
let s = self.emit_expr(&args[0])?;
|
||||||
|
let suffix = self.emit_expr(&args[1])?;
|
||||||
|
return Ok(format!("lux_string_ends_with({}, {})", s, suffix));
|
||||||
|
}
|
||||||
|
"join" => {
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let sep = self.emit_expr(&args[1])?;
|
||||||
|
let temp = format!("_join_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("LuxString {} = lux_string_join({}, {});", temp, list, sep));
|
||||||
|
self.register_rc_var(&temp, "LuxString");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3876,6 +4059,8 @@ impl CBackend {
|
|||||||
self.writeln("");
|
self.writeln("");
|
||||||
|
|
||||||
// Execute top-level let bindings with run expressions
|
// Execute top-level let bindings with run expressions
|
||||||
|
// Track if main was already called via a run expression
|
||||||
|
let mut main_called_via_run = false;
|
||||||
for decl in &program.declarations {
|
for decl in &program.declarations {
|
||||||
if let Declaration::Let(let_decl) = decl {
|
if let Declaration::Let(let_decl) = decl {
|
||||||
if matches!(&let_decl.value, Expr::Run { .. }) {
|
if matches!(&let_decl.value, Expr::Run { .. }) {
|
||||||
@@ -3883,6 +4068,10 @@ impl CBackend {
|
|||||||
if let Expr::Call { func, .. } = expr.as_ref() {
|
if let Expr::Call { func, .. } = expr.as_ref() {
|
||||||
if let Expr::Var(fn_name) = func.as_ref() {
|
if let Expr::Var(fn_name) = func.as_ref() {
|
||||||
let mangled = self.mangle_name(&fn_name.name);
|
let mangled = self.mangle_name(&fn_name.name);
|
||||||
|
// Track if this is a call to main
|
||||||
|
if fn_name.name == "main" {
|
||||||
|
main_called_via_run = true;
|
||||||
|
}
|
||||||
// Pass default evidence if function uses effects
|
// Pass default evidence if function uses effects
|
||||||
if self.effectful_functions.contains(&fn_name.name) {
|
if self.effectful_functions.contains(&fn_name.name) {
|
||||||
self.writeln(&format!("{}(&default_evidence);", mangled));
|
self.writeln(&format!("{}(&default_evidence);", mangled));
|
||||||
@@ -3896,8 +4085,8 @@ impl CBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a main function, call it
|
// If there's a main function and it wasn't already called via run, call it
|
||||||
if has_main {
|
if has_main && !main_called_via_run {
|
||||||
// Check if main uses effects (Console typically)
|
// Check if main uses effects (Console typically)
|
||||||
if self.effectful_functions.contains("main") {
|
if self.effectful_functions.contains("main") {
|
||||||
self.writeln("main_lux(&default_evidence);");
|
self.writeln("main_lux(&default_evidence);");
|
||||||
|
|||||||
@@ -1,9 +1,223 @@
|
|||||||
//! Elm-style diagnostic messages for beautiful error reporting
|
//! Elm-style diagnostic messages for beautiful error reporting
|
||||||
|
//!
|
||||||
|
//! This module provides Elm-quality error messages with:
|
||||||
|
//! - Error codes (E0001, E0002, etc.) for easy searchability
|
||||||
|
//! - Visual type diffs showing expected vs actual
|
||||||
|
//! - "Did you mean?" suggestions using Levenshtein distance
|
||||||
|
//! - Context-aware hints based on error type
|
||||||
|
//! - Colored output with syntax highlighting
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use crate::ast::Span;
|
use crate::ast::Span;
|
||||||
|
|
||||||
|
/// Error codes for all Lux compiler errors
|
||||||
|
/// These follow the pattern E{category}{number}:
|
||||||
|
/// - E01xx: Parse errors
|
||||||
|
/// - E02xx: Type errors
|
||||||
|
/// - E03xx: Name resolution errors
|
||||||
|
/// - E04xx: Effect errors
|
||||||
|
/// - E05xx: Pattern matching errors
|
||||||
|
/// - E06xx: Import/module errors
|
||||||
|
/// - E07xx: Behavioral type errors
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
// Parse errors (E01xx)
|
||||||
|
E0100, // Generic parse error
|
||||||
|
E0101, // Unexpected token
|
||||||
|
E0102, // Unclosed delimiter
|
||||||
|
E0103, // Invalid literal
|
||||||
|
E0104, // Missing semicolon or delimiter
|
||||||
|
E0105, // Invalid operator
|
||||||
|
|
||||||
|
// Type errors (E02xx)
|
||||||
|
E0200, // Generic type error
|
||||||
|
E0201, // Type mismatch
|
||||||
|
E0202, // Cannot unify types
|
||||||
|
E0203, // Missing type annotation
|
||||||
|
E0204, // Invalid type application
|
||||||
|
E0205, // Recursive type without indirection
|
||||||
|
E0206, // Field type mismatch
|
||||||
|
E0207, // Missing record field
|
||||||
|
E0208, // Extra record field
|
||||||
|
E0209, // Wrong number of type arguments
|
||||||
|
E0210, // Type not found
|
||||||
|
E0211, // Return type mismatch
|
||||||
|
|
||||||
|
// Name resolution errors (E03xx)
|
||||||
|
E0300, // Generic name error
|
||||||
|
E0301, // Undefined variable
|
||||||
|
E0302, // Undefined function
|
||||||
|
E0303, // Undefined type
|
||||||
|
E0304, // Undefined module
|
||||||
|
E0305, // Duplicate definition
|
||||||
|
E0306, // Shadowing warning
|
||||||
|
E0307, // Unused variable warning
|
||||||
|
E0308, // Undefined field
|
||||||
|
|
||||||
|
// Effect errors (E04xx)
|
||||||
|
E0400, // Generic effect error
|
||||||
|
E0401, // Unhandled effect
|
||||||
|
E0402, // Effect not in scope
|
||||||
|
E0403, // Missing effect handler
|
||||||
|
E0404, // Invalid effect operation
|
||||||
|
E0405, // Effect mismatch
|
||||||
|
|
||||||
|
// Pattern matching errors (E05xx)
|
||||||
|
E0500, // Generic pattern error
|
||||||
|
E0501, // Non-exhaustive patterns
|
||||||
|
E0502, // Unreachable pattern
|
||||||
|
E0503, // Invalid pattern
|
||||||
|
E0504, // Duplicate pattern binding
|
||||||
|
|
||||||
|
// Import/module errors (E06xx)
|
||||||
|
E0600, // Generic module error
|
||||||
|
E0601, // Module not found
|
||||||
|
E0602, // Circular import
|
||||||
|
E0603, // Export not found
|
||||||
|
E0604, // Private item accessed
|
||||||
|
|
||||||
|
// Behavioral type errors (E07xx)
|
||||||
|
E0700, // Generic behavioral error
|
||||||
|
E0701, // Purity violation
|
||||||
|
E0702, // Totality violation
|
||||||
|
E0703, // Idempotency violation
|
||||||
|
E0704, // Commutativity violation
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorCode {
|
||||||
|
pub fn code(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
// Parse errors
|
||||||
|
ErrorCode::E0100 => "E0100",
|
||||||
|
ErrorCode::E0101 => "E0101",
|
||||||
|
ErrorCode::E0102 => "E0102",
|
||||||
|
ErrorCode::E0103 => "E0103",
|
||||||
|
ErrorCode::E0104 => "E0104",
|
||||||
|
ErrorCode::E0105 => "E0105",
|
||||||
|
// Type errors
|
||||||
|
ErrorCode::E0200 => "E0200",
|
||||||
|
ErrorCode::E0201 => "E0201",
|
||||||
|
ErrorCode::E0202 => "E0202",
|
||||||
|
ErrorCode::E0203 => "E0203",
|
||||||
|
ErrorCode::E0204 => "E0204",
|
||||||
|
ErrorCode::E0205 => "E0205",
|
||||||
|
ErrorCode::E0206 => "E0206",
|
||||||
|
ErrorCode::E0207 => "E0207",
|
||||||
|
ErrorCode::E0208 => "E0208",
|
||||||
|
ErrorCode::E0209 => "E0209",
|
||||||
|
ErrorCode::E0210 => "E0210",
|
||||||
|
ErrorCode::E0211 => "E0211",
|
||||||
|
// Name errors
|
||||||
|
ErrorCode::E0300 => "E0300",
|
||||||
|
ErrorCode::E0301 => "E0301",
|
||||||
|
ErrorCode::E0302 => "E0302",
|
||||||
|
ErrorCode::E0303 => "E0303",
|
||||||
|
ErrorCode::E0304 => "E0304",
|
||||||
|
ErrorCode::E0305 => "E0305",
|
||||||
|
ErrorCode::E0306 => "E0306",
|
||||||
|
ErrorCode::E0307 => "E0307",
|
||||||
|
ErrorCode::E0308 => "E0308",
|
||||||
|
// Effect errors
|
||||||
|
ErrorCode::E0400 => "E0400",
|
||||||
|
ErrorCode::E0401 => "E0401",
|
||||||
|
ErrorCode::E0402 => "E0402",
|
||||||
|
ErrorCode::E0403 => "E0403",
|
||||||
|
ErrorCode::E0404 => "E0404",
|
||||||
|
ErrorCode::E0405 => "E0405",
|
||||||
|
// Pattern errors
|
||||||
|
ErrorCode::E0500 => "E0500",
|
||||||
|
ErrorCode::E0501 => "E0501",
|
||||||
|
ErrorCode::E0502 => "E0502",
|
||||||
|
ErrorCode::E0503 => "E0503",
|
||||||
|
ErrorCode::E0504 => "E0504",
|
||||||
|
// Module errors
|
||||||
|
ErrorCode::E0600 => "E0600",
|
||||||
|
ErrorCode::E0601 => "E0601",
|
||||||
|
ErrorCode::E0602 => "E0602",
|
||||||
|
ErrorCode::E0603 => "E0603",
|
||||||
|
ErrorCode::E0604 => "E0604",
|
||||||
|
// Behavioral errors
|
||||||
|
ErrorCode::E0700 => "E0700",
|
||||||
|
ErrorCode::E0701 => "E0701",
|
||||||
|
ErrorCode::E0702 => "E0702",
|
||||||
|
ErrorCode::E0703 => "E0703",
|
||||||
|
ErrorCode::E0704 => "E0704",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
// Parse errors
|
||||||
|
ErrorCode::E0100 => "Parse Error",
|
||||||
|
ErrorCode::E0101 => "Unexpected Token",
|
||||||
|
ErrorCode::E0102 => "Unclosed Delimiter",
|
||||||
|
ErrorCode::E0103 => "Invalid Literal",
|
||||||
|
ErrorCode::E0104 => "Missing Delimiter",
|
||||||
|
ErrorCode::E0105 => "Invalid Operator",
|
||||||
|
// Type errors
|
||||||
|
ErrorCode::E0200 => "Type Error",
|
||||||
|
ErrorCode::E0201 => "Type Mismatch",
|
||||||
|
ErrorCode::E0202 => "Cannot Unify Types",
|
||||||
|
ErrorCode::E0203 => "Missing Type Annotation",
|
||||||
|
ErrorCode::E0204 => "Invalid Type Application",
|
||||||
|
ErrorCode::E0205 => "Recursive Type Error",
|
||||||
|
ErrorCode::E0206 => "Field Type Mismatch",
|
||||||
|
ErrorCode::E0207 => "Missing Record Field",
|
||||||
|
ErrorCode::E0208 => "Extra Record Field",
|
||||||
|
ErrorCode::E0209 => "Wrong Type Arity",
|
||||||
|
ErrorCode::E0210 => "Type Not Found",
|
||||||
|
ErrorCode::E0211 => "Return Type Mismatch",
|
||||||
|
// Name errors
|
||||||
|
ErrorCode::E0300 => "Name Error",
|
||||||
|
ErrorCode::E0301 => "Undefined Variable",
|
||||||
|
ErrorCode::E0302 => "Undefined Function",
|
||||||
|
ErrorCode::E0303 => "Undefined Type",
|
||||||
|
ErrorCode::E0304 => "Undefined Module",
|
||||||
|
ErrorCode::E0305 => "Duplicate Definition",
|
||||||
|
ErrorCode::E0306 => "Variable Shadowing",
|
||||||
|
ErrorCode::E0307 => "Unused Variable",
|
||||||
|
ErrorCode::E0308 => "Undefined Field",
|
||||||
|
// Effect errors
|
||||||
|
ErrorCode::E0400 => "Effect Error",
|
||||||
|
ErrorCode::E0401 => "Unhandled Effect",
|
||||||
|
ErrorCode::E0402 => "Effect Not In Scope",
|
||||||
|
ErrorCode::E0403 => "Missing Effect Handler",
|
||||||
|
ErrorCode::E0404 => "Invalid Effect Operation",
|
||||||
|
ErrorCode::E0405 => "Effect Mismatch",
|
||||||
|
// Pattern errors
|
||||||
|
ErrorCode::E0500 => "Pattern Error",
|
||||||
|
ErrorCode::E0501 => "Non-Exhaustive Patterns",
|
||||||
|
ErrorCode::E0502 => "Unreachable Pattern",
|
||||||
|
ErrorCode::E0503 => "Invalid Pattern",
|
||||||
|
ErrorCode::E0504 => "Duplicate Pattern Binding",
|
||||||
|
// Module errors
|
||||||
|
ErrorCode::E0600 => "Module Error",
|
||||||
|
ErrorCode::E0601 => "Module Not Found",
|
||||||
|
ErrorCode::E0602 => "Circular Import",
|
||||||
|
ErrorCode::E0603 => "Export Not Found",
|
||||||
|
ErrorCode::E0604 => "Private Item Access",
|
||||||
|
// Behavioral errors
|
||||||
|
ErrorCode::E0700 => "Behavioral Type Error",
|
||||||
|
ErrorCode::E0701 => "Purity Violation",
|
||||||
|
ErrorCode::E0702 => "Totality Violation",
|
||||||
|
ErrorCode::E0703 => "Idempotency Violation",
|
||||||
|
ErrorCode::E0704 => "Commutativity Violation",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a URL to documentation about this error
|
||||||
|
pub fn help_url(&self) -> String {
|
||||||
|
format!("https://lux-lang.dev/errors/{}", self.code())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ErrorCode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.code())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ANSI color codes for terminal output
|
/// ANSI color codes for terminal output
|
||||||
pub mod colors {
|
pub mod colors {
|
||||||
pub const RESET: &str = "\x1b[0m";
|
pub const RESET: &str = "\x1b[0m";
|
||||||
@@ -46,30 +260,57 @@ impl Severity {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Diagnostic {
|
pub struct Diagnostic {
|
||||||
pub severity: Severity,
|
pub severity: Severity,
|
||||||
|
pub code: Option<ErrorCode>,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub hints: Vec<String>,
|
pub hints: Vec<String>,
|
||||||
|
pub expected_type: Option<String>,
|
||||||
|
pub actual_type: Option<String>,
|
||||||
|
pub secondary_spans: Vec<(Span, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic {
|
impl Diagnostic {
|
||||||
pub fn error(title: impl Into<String>, message: impl Into<String>, span: Span) -> Self {
|
pub fn error(title: impl Into<String>, message: impl Into<String>, span: Span) -> Self {
|
||||||
Self {
|
Self {
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
|
code: None,
|
||||||
title: title.into(),
|
title: title.into(),
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
span,
|
span,
|
||||||
hints: Vec::new(),
|
hints: Vec::new(),
|
||||||
|
expected_type: None,
|
||||||
|
actual_type: None,
|
||||||
|
secondary_spans: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn warning(title: impl Into<String>, message: impl Into<String>, span: Span) -> Self {
|
pub fn warning(title: impl Into<String>, message: impl Into<String>, span: Span) -> Self {
|
||||||
Self {
|
Self {
|
||||||
severity: Severity::Warning,
|
severity: Severity::Warning,
|
||||||
|
code: None,
|
||||||
title: title.into(),
|
title: title.into(),
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
span,
|
span,
|
||||||
hints: Vec::new(),
|
hints: Vec::new(),
|
||||||
|
expected_type: None,
|
||||||
|
actual_type: None,
|
||||||
|
secondary_spans: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an error with a specific error code
|
||||||
|
pub fn with_code(code: ErrorCode, message: impl Into<String>, span: Span) -> Self {
|
||||||
|
Self {
|
||||||
|
severity: Severity::Error,
|
||||||
|
code: Some(code),
|
||||||
|
title: code.title().to_string(),
|
||||||
|
message: message.into(),
|
||||||
|
span,
|
||||||
|
hints: Vec::new(),
|
||||||
|
expected_type: None,
|
||||||
|
actual_type: None,
|
||||||
|
secondary_spans: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +323,111 @@ impl Diagnostic {
|
|||||||
self.hints.extend(hints);
|
self.hints.extend(hints);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add expected and actual types for type mismatch visualization
|
||||||
|
pub fn with_types(mut self, expected: impl Into<String>, actual: impl Into<String>) -> Self {
|
||||||
|
self.expected_type = Some(expected.into());
|
||||||
|
self.actual_type = Some(actual.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a secondary span with a label (e.g., "defined here")
|
||||||
|
pub fn with_secondary(mut self, span: Span, label: impl Into<String>) -> Self {
|
||||||
|
self.secondary_spans.push((span, label.into()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the error code
|
||||||
|
pub fn code(mut self, code: ErrorCode) -> Self {
|
||||||
|
self.code = Some(code);
|
||||||
|
self.title = code.title().to_string();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a visual type diff between expected and actual types
|
||||||
|
/// Highlights the differences in red
|
||||||
|
pub fn format_type_diff(expected: &str, actual: &str) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
output.push_str(&format!(
|
||||||
|
"\n {}{}Expected:{} {}{}{}\n",
|
||||||
|
colors::BOLD,
|
||||||
|
colors::CYAN,
|
||||||
|
colors::RESET,
|
||||||
|
colors::BOLD,
|
||||||
|
expected,
|
||||||
|
colors::RESET
|
||||||
|
));
|
||||||
|
output.push_str(&format!(
|
||||||
|
" {}{}Found: {} {}{}{}\n",
|
||||||
|
colors::BOLD,
|
||||||
|
colors::RED,
|
||||||
|
colors::RESET,
|
||||||
|
colors::BOLD,
|
||||||
|
actual,
|
||||||
|
colors::RESET
|
||||||
|
));
|
||||||
|
|
||||||
|
// If types are structurally similar, show where they differ
|
||||||
|
if let Some(diff) = compute_type_diff(expected, actual) {
|
||||||
|
output.push_str(&format!("\n {}\n", diff));
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute a simple diff between two type strings
|
||||||
|
fn compute_type_diff(expected: &str, actual: &str) -> Option<String> {
|
||||||
|
// Simple case: find the first difference
|
||||||
|
let exp_chars: Vec<char> = expected.chars().collect();
|
||||||
|
let act_chars: Vec<char> = actual.chars().collect();
|
||||||
|
|
||||||
|
let mut diff_start = None;
|
||||||
|
|
||||||
|
for (i, (e, a)) in exp_chars.iter().zip(act_chars.iter()).enumerate() {
|
||||||
|
if e != a && diff_start.is_none() {
|
||||||
|
diff_start = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the types are identical, no diff needed
|
||||||
|
if diff_start.is_none() && exp_chars.len() == act_chars.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the difference location with an arrow
|
||||||
|
if let Some(start) = diff_start {
|
||||||
|
let pointer = format!(
|
||||||
|
"{}{}^-- difference starts here{}",
|
||||||
|
" ".repeat(start + 9), // Account for "Found: " prefix
|
||||||
|
colors::YELLOW,
|
||||||
|
colors::RESET
|
||||||
|
);
|
||||||
|
return Some(pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length difference
|
||||||
|
if exp_chars.len() != act_chars.len() {
|
||||||
|
return Some(format!(
|
||||||
|
"{}Note:{} Types have different lengths ({} vs {} characters)",
|
||||||
|
colors::YELLOW,
|
||||||
|
colors::RESET,
|
||||||
|
exp_chars.len(),
|
||||||
|
act_chars.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a plain type diff (no colors)
|
||||||
|
pub fn format_type_diff_plain(expected: &str, actual: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"\n Expected: {}\n Found: {}\n",
|
||||||
|
expected, actual
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the Levenshtein edit distance between two strings
|
/// Calculate the Levenshtein edit distance between two strings
|
||||||
@@ -211,18 +557,23 @@ pub fn render_diagnostic(
|
|||||||
let severity_color = diagnostic.severity.color();
|
let severity_color = diagnostic.severity.color();
|
||||||
let severity_label = diagnostic.severity.label();
|
let severity_label = diagnostic.severity.label();
|
||||||
|
|
||||||
// Header: -- ERROR ----------- filename.lux
|
// Header with error code: ── ERROR[E0201] ── filename.lux
|
||||||
let filename_str = filename.unwrap_or("<input>");
|
let filename_str = filename.unwrap_or("<input>");
|
||||||
|
let code_str = diagnostic
|
||||||
|
.code
|
||||||
|
.map(|c| format!("[{}]", c.code()))
|
||||||
|
.unwrap_or_default();
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
"{}{}{} ── {} ──────────────────────────────────\n",
|
"{}{}── {}{} ──────────────────── {}{}\n",
|
||||||
colors::BOLD,
|
colors::BOLD,
|
||||||
severity_color,
|
severity_color,
|
||||||
severity_label,
|
severity_label,
|
||||||
filename_str
|
code_str,
|
||||||
|
filename_str,
|
||||||
|
colors::RESET
|
||||||
));
|
));
|
||||||
output.push_str(colors::RESET);
|
|
||||||
|
|
||||||
// Title
|
// Location
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
"\n{}{}{}:{}{}\n\n",
|
"\n{}{}{}:{}{}\n\n",
|
||||||
colors::BOLD,
|
colors::BOLD,
|
||||||
@@ -309,6 +660,11 @@ pub fn render_diagnostic(
|
|||||||
// Error message
|
// Error message
|
||||||
output.push_str(&format!("\n{}\n", diagnostic.message));
|
output.push_str(&format!("\n{}\n", diagnostic.message));
|
||||||
|
|
||||||
|
// Type diff visualization (if present)
|
||||||
|
if let (Some(expected), Some(actual)) = (&diagnostic.expected_type, &diagnostic.actual_type) {
|
||||||
|
output.push_str(&format_type_diff(expected, actual));
|
||||||
|
}
|
||||||
|
|
||||||
// Hints
|
// Hints
|
||||||
if !diagnostic.hints.is_empty() {
|
if !diagnostic.hints.is_empty() {
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
@@ -323,6 +679,17 @@ pub fn render_diagnostic(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help URL for error codes
|
||||||
|
if let Some(code) = diagnostic.code {
|
||||||
|
output.push_str(&format!(
|
||||||
|
"\n{}{}Learn more:{} {}\n",
|
||||||
|
colors::DIM,
|
||||||
|
colors::BLUE,
|
||||||
|
colors::RESET,
|
||||||
|
code.help_url()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
@@ -338,9 +705,14 @@ pub fn render_diagnostic_plain(
|
|||||||
let (end_line, end_col) = offset_to_line_col(source, diagnostic.span.end);
|
let (end_line, end_col) = offset_to_line_col(source, diagnostic.span.end);
|
||||||
|
|
||||||
let filename_str = filename.unwrap_or("<input>");
|
let filename_str = filename.unwrap_or("<input>");
|
||||||
|
let code_str = diagnostic
|
||||||
|
.code
|
||||||
|
.map(|c| format!("[{}]", c.code()))
|
||||||
|
.unwrap_or_default();
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
"-- {} ── {} ──────────────────────────────────\n",
|
"-- {}{} ── {} ──────────────────────────────────\n",
|
||||||
diagnostic.severity.label(),
|
diagnostic.severity.label(),
|
||||||
|
code_str,
|
||||||
filename_str
|
filename_str
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -381,6 +753,11 @@ pub fn render_diagnostic_plain(
|
|||||||
|
|
||||||
output.push_str(&format!("\n{}\n", diagnostic.message));
|
output.push_str(&format!("\n{}\n", diagnostic.message));
|
||||||
|
|
||||||
|
// Type diff visualization (plain)
|
||||||
|
if let (Some(expected), Some(actual)) = (&diagnostic.expected_type, &diagnostic.actual_type) {
|
||||||
|
output.push_str(&format_type_diff_plain(expected, actual));
|
||||||
|
}
|
||||||
|
|
||||||
if !diagnostic.hints.is_empty() {
|
if !diagnostic.hints.is_empty() {
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
for hint in &diagnostic.hints {
|
for hint in &diagnostic.hints {
|
||||||
@@ -388,6 +765,11 @@ pub fn render_diagnostic_plain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help URL for error codes
|
||||||
|
if let Some(code) = diagnostic.code {
|
||||||
|
output.push_str(&format!("\nLearn more: {}\n", code.help_url()));
|
||||||
|
}
|
||||||
|
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
@@ -478,25 +860,28 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_type_error_categorization() {
|
fn test_type_error_categorization() {
|
||||||
use super::Diagnostic;
|
use super::{Diagnostic, ErrorCode};
|
||||||
|
|
||||||
// Test that errors are properly categorized
|
// Test that errors are properly categorized
|
||||||
let source = "let x: Int = \"hello\"";
|
let source = "let x: Int = \"hello\"";
|
||||||
|
|
||||||
// Simulate a type mismatch diagnostic
|
// Simulate a type mismatch diagnostic with error code
|
||||||
let diag = Diagnostic {
|
let diag = Diagnostic::with_code(
|
||||||
severity: Severity::Error,
|
ErrorCode::E0201,
|
||||||
title: "Type Mismatch".to_string(),
|
"Type mismatch: expected Int, got String",
|
||||||
message: "Type mismatch: expected Int, got String".to_string(),
|
Span { start: 13, end: 20 },
|
||||||
span: Span { start: 13, end: 20 },
|
)
|
||||||
hints: vec!["Check that the types on both sides of the expression are compatible.".to_string()],
|
.with_types("Int", "String")
|
||||||
};
|
.with_hint("Check that the types on both sides of the expression are compatible.");
|
||||||
|
|
||||||
let output = render_diagnostic_plain(&diag, source, Some("test.lux"));
|
let output = render_diagnostic_plain(&diag, source, Some("test.lux"));
|
||||||
|
|
||||||
assert!(output.contains("Type Mismatch"));
|
assert!(output.contains("Type Mismatch"));
|
||||||
assert!(output.contains("\"hello\""));
|
assert!(output.contains("\"hello\""));
|
||||||
assert!(output.contains("Hint:"));
|
assert!(output.contains("Hint:"));
|
||||||
|
assert!(output.contains("[E0201]"));
|
||||||
|
assert!(output.contains("Expected: Int"));
|
||||||
|
assert!(output.contains("Found: String"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -589,4 +974,60 @@ mod tests {
|
|||||||
let hint = super::format_did_you_mean(&["a".to_string(), "b".to_string(), "c".to_string()]);
|
let hint = super::format_did_you_mean(&["a".to_string(), "b".to_string(), "c".to_string()]);
|
||||||
assert_eq!(hint, Some("Did you mean 'a', 'b', or 'c'?".to_string()));
|
assert_eq!(hint, Some("Did you mean 'a', 'b', or 'c'?".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_code_display() {
|
||||||
|
assert_eq!(super::ErrorCode::E0201.code(), "E0201");
|
||||||
|
assert_eq!(super::ErrorCode::E0201.title(), "Type Mismatch");
|
||||||
|
assert!(super::ErrorCode::E0201.help_url().contains("E0201"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diagnostic_with_code() {
|
||||||
|
let diag = super::Diagnostic::with_code(
|
||||||
|
super::ErrorCode::E0301,
|
||||||
|
"Variable 'x' not found",
|
||||||
|
Span { start: 0, end: 1 },
|
||||||
|
);
|
||||||
|
assert_eq!(diag.code, Some(super::ErrorCode::E0301));
|
||||||
|
assert_eq!(diag.title, "Undefined Variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diagnostic_with_types() {
|
||||||
|
let diag = super::Diagnostic::error("Test", "Msg", Span { start: 0, end: 1 })
|
||||||
|
.with_types("Int", "String");
|
||||||
|
assert_eq!(diag.expected_type, Some("Int".to_string()));
|
||||||
|
assert_eq!(diag.actual_type, Some("String".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type_diff_plain() {
|
||||||
|
let diff = super::format_type_diff_plain("Int", "String");
|
||||||
|
assert!(diff.contains("Expected: Int"));
|
||||||
|
assert!(diff.contains("Found: String"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diagnostic_render_with_all_features() {
|
||||||
|
let source = "let x: Int = \"hello\"";
|
||||||
|
let diag = super::Diagnostic::with_code(
|
||||||
|
super::ErrorCode::E0201,
|
||||||
|
"This value should be an Int but is a String",
|
||||||
|
Span { start: 13, end: 20 },
|
||||||
|
)
|
||||||
|
.with_types("Int", "String")
|
||||||
|
.with_hint("Try using String.parseInt to convert the string to an integer");
|
||||||
|
|
||||||
|
let output = super::render_diagnostic_plain(&diag, source, Some("test.lux"));
|
||||||
|
|
||||||
|
// Check all features are present
|
||||||
|
assert!(output.contains("[E0201]"));
|
||||||
|
assert!(output.contains("Type Mismatch"));
|
||||||
|
assert!(output.contains("Expected: Int"));
|
||||||
|
assert!(output.contains("Found: String"));
|
||||||
|
assert!(output.contains("Hint:"));
|
||||||
|
assert!(output.contains("parseInt"));
|
||||||
|
assert!(output.contains("lux-lang.dev/errors/E0201"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use crate::diagnostics::{Diagnostic, Severity};
|
use crate::diagnostics::{Diagnostic, ErrorCode, Severity};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use postgres::{Client as PgClient, NoTls};
|
||||||
|
use rusqlite::Connection;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -426,29 +428,47 @@ impl std::error::Error for RuntimeError {}
|
|||||||
impl RuntimeError {
|
impl RuntimeError {
|
||||||
/// Convert to a rich diagnostic for Elm-style error display
|
/// Convert to a rich diagnostic for Elm-style error display
|
||||||
pub fn to_diagnostic(&self) -> Diagnostic {
|
pub fn to_diagnostic(&self) -> Diagnostic {
|
||||||
let (title, hints) = categorize_runtime_error(&self.message);
|
let (code, title, hints) = categorize_runtime_error(&self.message);
|
||||||
|
|
||||||
Diagnostic {
|
Diagnostic {
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
|
code,
|
||||||
title,
|
title,
|
||||||
message: self.message.clone(),
|
message: self.message.clone(),
|
||||||
span: self.span.unwrap_or_default(),
|
span: self.span.unwrap_or_default(),
|
||||||
hints,
|
hints,
|
||||||
|
expected_type: None,
|
||||||
|
actual_type: None,
|
||||||
|
secondary_spans: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Categorize runtime errors to provide better titles and hints
|
/// Categorize runtime errors to provide better titles, hints, and error codes
|
||||||
fn categorize_runtime_error(message: &str) -> (String, Vec<String>) {
|
fn categorize_runtime_error(message: &str) -> (Option<ErrorCode>, String, Vec<String>) {
|
||||||
let message_lower = message.to_lowercase();
|
let message_lower = message.to_lowercase();
|
||||||
|
|
||||||
if message_lower.contains("undefined") || message_lower.contains("not found") {
|
if message_lower.contains("undefined variable") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0301),
|
||||||
|
"Undefined Variable".to_string(),
|
||||||
|
vec!["Make sure the variable is defined and in scope.".to_string()],
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("undefined function") || message_lower.contains("function not found") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0302),
|
||||||
|
"Undefined Function".to_string(),
|
||||||
|
vec!["Make sure the function is defined and in scope.".to_string()],
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("undefined") || message_lower.contains("not found") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0301),
|
||||||
"Undefined Reference".to_string(),
|
"Undefined Reference".to_string(),
|
||||||
vec!["Make sure the name is defined and in scope.".to_string()],
|
vec!["Make sure the name is defined and in scope.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("division by zero") || message_lower.contains("divide by zero") {
|
} else if message_lower.contains("division by zero") || message_lower.contains("divide by zero") {
|
||||||
(
|
(
|
||||||
|
None, // Runtime error, not a type error
|
||||||
"Division by Zero".to_string(),
|
"Division by Zero".to_string(),
|
||||||
vec![
|
vec![
|
||||||
"Check that the divisor is not zero before dividing.".to_string(),
|
"Check that the divisor is not zero before dividing.".to_string(),
|
||||||
@@ -457,11 +477,13 @@ fn categorize_runtime_error(message: &str) -> (String, Vec<String>) {
|
|||||||
)
|
)
|
||||||
} else if message_lower.contains("type") && message_lower.contains("mismatch") {
|
} else if message_lower.contains("type") && message_lower.contains("mismatch") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0201),
|
||||||
"Type Mismatch".to_string(),
|
"Type Mismatch".to_string(),
|
||||||
vec!["The value has a different type than expected.".to_string()],
|
vec!["The value has a different type than expected.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("effect") && message_lower.contains("unhandled") {
|
} else if message_lower.contains("effect") && message_lower.contains("unhandled") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0401),
|
||||||
"Unhandled Effect".to_string(),
|
"Unhandled Effect".to_string(),
|
||||||
vec![
|
vec![
|
||||||
"This effect must be handled before the program can continue.".to_string(),
|
"This effect must be handled before the program can continue.".to_string(),
|
||||||
@@ -470,16 +492,19 @@ fn categorize_runtime_error(message: &str) -> (String, Vec<String>) {
|
|||||||
)
|
)
|
||||||
} else if message_lower.contains("pattern") && message_lower.contains("match") {
|
} else if message_lower.contains("pattern") && message_lower.contains("match") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0501),
|
||||||
"Non-exhaustive Pattern".to_string(),
|
"Non-exhaustive Pattern".to_string(),
|
||||||
vec!["Add more patterns to cover all possible cases.".to_string()],
|
vec!["Add more patterns to cover all possible cases.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("argument") {
|
} else if message_lower.contains("argument") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0209),
|
||||||
"Wrong Arguments".to_string(),
|
"Wrong Arguments".to_string(),
|
||||||
vec!["Check the number and types of arguments provided.".to_string()],
|
vec!["Check the number and types of arguments provided.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("index") || message_lower.contains("bounds") {
|
} else if message_lower.contains("index") || message_lower.contains("bounds") {
|
||||||
(
|
(
|
||||||
|
None, // Runtime error
|
||||||
"Index Out of Bounds".to_string(),
|
"Index Out of Bounds".to_string(),
|
||||||
vec![
|
vec![
|
||||||
"The index is outside the valid range.".to_string(),
|
"The index is outside the valid range.".to_string(),
|
||||||
@@ -487,7 +512,7 @@ fn categorize_runtime_error(message: &str) -> (String, Vec<String>) {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
("Runtime Error".to_string(), vec![])
|
(None, "Runtime Error".to_string(), vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,6 +612,14 @@ pub struct Interpreter {
|
|||||||
current_http_request: Arc<Mutex<Option<tiny_http::Request>>>,
|
current_http_request: Arc<Mutex<Option<tiny_http::Request>>>,
|
||||||
/// Test results for the Test effect
|
/// Test results for the Test effect
|
||||||
test_results: RefCell<TestResults>,
|
test_results: RefCell<TestResults>,
|
||||||
|
/// SQL database connections (connection ID -> Connection)
|
||||||
|
sql_connections: RefCell<HashMap<i64, Connection>>,
|
||||||
|
/// Next SQL connection ID
|
||||||
|
next_sql_conn_id: RefCell<i64>,
|
||||||
|
/// PostgreSQL database connections (connection ID -> Client)
|
||||||
|
pg_connections: RefCell<HashMap<i64, PgClient>>,
|
||||||
|
/// Next PostgreSQL connection ID
|
||||||
|
next_pg_conn_id: RefCell<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Results from running tests
|
/// Results from running tests
|
||||||
@@ -627,6 +660,10 @@ impl Interpreter {
|
|||||||
http_server: Arc::new(Mutex::new(None)),
|
http_server: Arc::new(Mutex::new(None)),
|
||||||
current_http_request: Arc::new(Mutex::new(None)),
|
current_http_request: Arc::new(Mutex::new(None)),
|
||||||
test_results: RefCell::new(TestResults::default()),
|
test_results: RefCell::new(TestResults::default()),
|
||||||
|
sql_connections: RefCell::new(HashMap::new()),
|
||||||
|
next_sql_conn_id: RefCell::new(1),
|
||||||
|
pg_connections: RefCell::new(HashMap::new()),
|
||||||
|
next_pg_conn_id: RefCell::new(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,6 +745,32 @@ impl Interpreter {
|
|||||||
.insert(from_version, migration);
|
.insert(from_version, migration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register auto-generated migrations from the typechecker
|
||||||
|
/// These are migrations that were automatically generated for auto-migratable schema changes
|
||||||
|
pub fn register_auto_migrations(
|
||||||
|
&mut self,
|
||||||
|
auto_migrations: &std::collections::HashMap<String, std::collections::HashMap<u32, Expr>>,
|
||||||
|
) {
|
||||||
|
for (type_name, version_migrations) in auto_migrations {
|
||||||
|
for (from_version, body) in version_migrations {
|
||||||
|
// Only register if no migration already exists
|
||||||
|
let already_has = self
|
||||||
|
.migrations
|
||||||
|
.get(type_name)
|
||||||
|
.map(|m| m.contains_key(from_version))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if !already_has {
|
||||||
|
let stored = StoredMigration {
|
||||||
|
body: body.clone(),
|
||||||
|
env: self.global_env.clone(),
|
||||||
|
};
|
||||||
|
self.register_migration(type_name, *from_version, stored);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a versioned value
|
/// Create a versioned value
|
||||||
pub fn create_versioned(&self, type_name: &str, version: u32, value: Value) -> Value {
|
pub fn create_versioned(&self, type_name: &str, version: u32, value: Value) -> Value {
|
||||||
Value::Versioned {
|
Value::Versioned {
|
||||||
@@ -3712,6 +3775,600 @@ impl Interpreter {
|
|||||||
Ok(Value::Unit)
|
Ok(Value::Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Sql Effect =====
|
||||||
|
("Sql", "open") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.open requires a path string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match Connection::open(&path) {
|
||||||
|
Ok(conn) => {
|
||||||
|
let id = *self.next_sql_conn_id.borrow();
|
||||||
|
*self.next_sql_conn_id.borrow_mut() += 1;
|
||||||
|
self.sql_connections.borrow_mut().insert(id, conn);
|
||||||
|
Ok(Value::Int(id))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.open failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "openMemory") => {
|
||||||
|
match Connection::open_in_memory() {
|
||||||
|
Ok(conn) => {
|
||||||
|
let id = *self.next_sql_conn_id.borrow();
|
||||||
|
*self.next_sql_conn_id.borrow_mut() += 1;
|
||||||
|
self.sql_connections.borrow_mut().insert(id, conn);
|
||||||
|
Ok(Value::Int(id))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.openMemory failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "close") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.close requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.sql_connections.borrow_mut().remove(&conn_id).is_some() {
|
||||||
|
Ok(Value::Unit)
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError {
|
||||||
|
message: format!("Sql.close: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "execute") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.execute requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let conns = self.sql_connections.borrow();
|
||||||
|
let conn = match conns.get(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.execute: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute(&sql, []) {
|
||||||
|
Ok(rows_affected) => Ok(Value::Int(rows_affected as i64)),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.execute failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "query") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.query requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let conns = self.sql_connections.borrow();
|
||||||
|
let conn = match conns.get(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.query: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stmt = match conn.prepare(&sql) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.query prepare failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let column_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
|
||||||
|
let column_count = column_names.len();
|
||||||
|
|
||||||
|
let rows: Result<Vec<Value>, _> = stmt.query_map([], |row| {
|
||||||
|
let mut record = HashMap::new();
|
||||||
|
for (i, col_name) in column_names.iter().enumerate() {
|
||||||
|
let value = match row.get_ref(i) {
|
||||||
|
Ok(rusqlite::types::ValueRef::Null) => Value::Constructor {
|
||||||
|
name: "None".to_string(),
|
||||||
|
fields: vec![],
|
||||||
|
},
|
||||||
|
Ok(rusqlite::types::ValueRef::Integer(n)) => Value::Int(n),
|
||||||
|
Ok(rusqlite::types::ValueRef::Real(f)) => Value::Float(f),
|
||||||
|
Ok(rusqlite::types::ValueRef::Text(s)) => {
|
||||||
|
Value::String(String::from_utf8_lossy(s).to_string())
|
||||||
|
}
|
||||||
|
Ok(rusqlite::types::ValueRef::Blob(b)) => {
|
||||||
|
Value::String(format!("<blob {} bytes>", b.len()))
|
||||||
|
}
|
||||||
|
Err(_) => Value::String("<error>".to_string()),
|
||||||
|
};
|
||||||
|
record.insert(col_name.clone(), value);
|
||||||
|
}
|
||||||
|
Ok(Value::Record(record))
|
||||||
|
}).and_then(|rows| rows.collect());
|
||||||
|
|
||||||
|
match rows {
|
||||||
|
Ok(r) => Ok(Value::List(r)),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.query failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "queryOne") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.queryOne requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let conns = self.sql_connections.borrow();
|
||||||
|
let conn = match conns.get(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.queryOne: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stmt = match conn.prepare(&sql) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.queryOne prepare failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let column_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
let result = stmt.query_row([], |row| {
|
||||||
|
let mut record = HashMap::new();
|
||||||
|
for (i, col_name) in column_names.iter().enumerate() {
|
||||||
|
let value = match row.get_ref(i) {
|
||||||
|
Ok(rusqlite::types::ValueRef::Null) => Value::Constructor {
|
||||||
|
name: "None".to_string(),
|
||||||
|
fields: vec![],
|
||||||
|
},
|
||||||
|
Ok(rusqlite::types::ValueRef::Integer(n)) => Value::Int(n),
|
||||||
|
Ok(rusqlite::types::ValueRef::Real(f)) => Value::Float(f),
|
||||||
|
Ok(rusqlite::types::ValueRef::Text(s)) => {
|
||||||
|
Value::String(String::from_utf8_lossy(s).to_string())
|
||||||
|
}
|
||||||
|
Ok(rusqlite::types::ValueRef::Blob(b)) => {
|
||||||
|
Value::String(format!("<blob {} bytes>", b.len()))
|
||||||
|
}
|
||||||
|
Err(_) => Value::String("<error>".to_string()),
|
||||||
|
};
|
||||||
|
record.insert(col_name.clone(), value);
|
||||||
|
}
|
||||||
|
Ok(Value::Record(record))
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(row) => Ok(Value::Constructor {
|
||||||
|
name: "Some".to_string(),
|
||||||
|
fields: vec![row],
|
||||||
|
}),
|
||||||
|
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(Value::Constructor {
|
||||||
|
name: "None".to_string(),
|
||||||
|
fields: vec![],
|
||||||
|
}),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.queryOne failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "beginTx") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.beginTx requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let conns = self.sql_connections.borrow();
|
||||||
|
let conn = match conns.get(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.beginTx: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("BEGIN TRANSACTION", []) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.beginTx failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "commit") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.commit requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let conns = self.sql_connections.borrow();
|
||||||
|
let conn = match conns.get(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.commit: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("COMMIT", []) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.commit failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Sql", "rollback") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Sql.rollback requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let conns = self.sql_connections.borrow();
|
||||||
|
let conn = match conns.get(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Sql.rollback: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("ROLLBACK", []) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Sql.rollback failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PostgreSQL Effect
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
("Postgres", "connect") => {
|
||||||
|
let conn_str = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.connect requires a connection string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match PgClient::connect(&conn_str, NoTls) {
|
||||||
|
Ok(client) => {
|
||||||
|
let id = *self.next_pg_conn_id.borrow();
|
||||||
|
*self.next_pg_conn_id.borrow_mut() += 1;
|
||||||
|
self.pg_connections.borrow_mut().insert(id, client);
|
||||||
|
Ok(Value::Int(id))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.connect failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "close") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.close requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.pg_connections.borrow_mut().remove(&conn_id).is_some() {
|
||||||
|
Ok(Value::Unit)
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError {
|
||||||
|
message: format!("Postgres.close: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "execute") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.execute requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.execute: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute(&sql, &[]) {
|
||||||
|
Ok(rows) => Ok(Value::Int(rows as i64)),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.execute failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "query") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.query requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.query: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.query(&sql, &[]) {
|
||||||
|
Ok(rows) => {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
let mut record = HashMap::new();
|
||||||
|
for (i, col) in row.columns().iter().enumerate() {
|
||||||
|
let col_name = col.name().to_string();
|
||||||
|
let value: Value = match col.type_().name() {
|
||||||
|
"int4" | "int8" | "int2" => {
|
||||||
|
let v: Option<i64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Int(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"float4" | "float8" => {
|
||||||
|
let v: Option<f64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Float(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"bool" => {
|
||||||
|
let v: Option<bool> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(b) => Value::Bool(b),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"text" | "varchar" | "char" | "bpchar" | "name" => {
|
||||||
|
let v: Option<String> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Try to get as string for other types
|
||||||
|
let v: Option<String> = row.try_get(i).ok().flatten();
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
record.insert(col_name, value);
|
||||||
|
}
|
||||||
|
results.push(Value::Record(record));
|
||||||
|
}
|
||||||
|
Ok(Value::List(results))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.query failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "queryOne") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.queryOne requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.queryOne: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.query_opt(&sql, &[]) {
|
||||||
|
Ok(Some(row)) => {
|
||||||
|
let mut record = HashMap::new();
|
||||||
|
for (i, col) in row.columns().iter().enumerate() {
|
||||||
|
let col_name = col.name().to_string();
|
||||||
|
let value: Value = match col.type_().name() {
|
||||||
|
"int4" | "int8" | "int2" => {
|
||||||
|
let v: Option<i64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Int(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"float4" | "float8" => {
|
||||||
|
let v: Option<f64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Float(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"bool" => {
|
||||||
|
let v: Option<bool> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(b) => Value::Bool(b),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"text" | "varchar" | "char" | "bpchar" | "name" => {
|
||||||
|
let v: Option<String> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let v: Option<String> = row.try_get(i).ok().flatten();
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
record.insert(col_name, value);
|
||||||
|
}
|
||||||
|
Ok(Value::Constructor {
|
||||||
|
name: "Some".to_string(),
|
||||||
|
fields: vec![Value::Record(record)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(None) => Ok(Value::Constructor {
|
||||||
|
name: "None".to_string(),
|
||||||
|
fields: vec![],
|
||||||
|
}),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.queryOne failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "beginTx") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.beginTx requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.beginTx: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("BEGIN", &[]) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.beginTx failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "commit") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.commit requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.commit: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("COMMIT", &[]) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.commit failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "rollback") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.rollback requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.rollback: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("ROLLBACK", &[]) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.rollback failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => Err(RuntimeError {
|
_ => Err(RuntimeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Unhandled effect operation: {}.{}",
|
"Unhandled effect operation: {}.{}",
|
||||||
|
|||||||
544
src/main.rs
544
src/main.rs
@@ -13,6 +13,7 @@ mod lsp;
|
|||||||
mod modules;
|
mod modules;
|
||||||
mod package;
|
mod package;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
mod registry;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod typechecker;
|
mod typechecker;
|
||||||
mod types;
|
mod types;
|
||||||
@@ -41,6 +42,8 @@ Commands:
|
|||||||
:quit, :q Exit the REPL
|
:quit, :q Exit the REPL
|
||||||
:type <expr> Show the type of an expression
|
:type <expr> Show the type of an expression
|
||||||
:info <name> Show info about a binding
|
:info <name> Show info about a binding
|
||||||
|
:doc <name> Show documentation for a function/type
|
||||||
|
:browse <mod> List exports of a module (List, String, Option, etc.)
|
||||||
:env Show user-defined bindings
|
:env Show user-defined bindings
|
||||||
:clear Clear the environment
|
:clear Clear the environment
|
||||||
:load <file> Load and execute a file
|
:load <file> Load and execute a file
|
||||||
@@ -126,6 +129,25 @@ fn main() {
|
|||||||
// Package manager
|
// Package manager
|
||||||
handle_pkg_command(&args[2..]);
|
handle_pkg_command(&args[2..]);
|
||||||
}
|
}
|
||||||
|
"registry" => {
|
||||||
|
// Run package registry server
|
||||||
|
let storage = args.iter()
|
||||||
|
.position(|a| a == "--storage" || a == "-s")
|
||||||
|
.and_then(|i| args.get(i + 1))
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.unwrap_or("./lux-registry");
|
||||||
|
|
||||||
|
let bind = args.iter()
|
||||||
|
.position(|a| a == "--bind" || a == "-b")
|
||||||
|
.and_then(|i| args.get(i + 1))
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.unwrap_or("127.0.0.1:8080");
|
||||||
|
|
||||||
|
if let Err(e) = registry::run_registry_server(storage, bind) {
|
||||||
|
eprintln!("Registry server error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
"compile" => {
|
"compile" => {
|
||||||
// Compile to native binary or JavaScript
|
// Compile to native binary or JavaScript
|
||||||
if args.len() < 3 {
|
if args.len() < 3 {
|
||||||
@@ -182,6 +204,9 @@ fn print_help() {
|
|||||||
println!(" lux debug <file.lux> Start interactive debugger");
|
println!(" lux debug <file.lux> Start interactive debugger");
|
||||||
println!(" lux init [name] Initialize a new project");
|
println!(" lux init [name] Initialize a new project");
|
||||||
println!(" lux pkg <command> Package manager (install, add, remove, list, update)");
|
println!(" lux pkg <command> Package manager (install, add, remove, list, update)");
|
||||||
|
println!(" lux registry Start package registry server");
|
||||||
|
println!(" -s, --storage <dir> Storage directory (default: ./lux-registry)");
|
||||||
|
println!(" -b, --bind <addr> Bind address (default: 127.0.0.1:8080)");
|
||||||
println!(" lux --lsp Start LSP server (for IDE integration)");
|
println!(" lux --lsp Start LSP server (for IDE integration)");
|
||||||
println!(" lux --help Show this help");
|
println!(" lux --help Show this help");
|
||||||
println!(" lux --version Show version");
|
println!(" lux --version Show version");
|
||||||
@@ -710,6 +735,9 @@ fn run_tests(args: &[String]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get auto-generated migrations from typechecker
|
||||||
|
let auto_migrations = checker.get_auto_migrations().clone();
|
||||||
|
|
||||||
// Find test functions (functions starting with test_)
|
// Find test functions (functions starting with test_)
|
||||||
let test_funcs: Vec<_> = program.declarations.iter().filter_map(|d| {
|
let test_funcs: Vec<_> = program.declarations.iter().filter_map(|d| {
|
||||||
if let ast::Declaration::Function(f) = d {
|
if let ast::Declaration::Function(f) = d {
|
||||||
@@ -723,6 +751,7 @@ fn run_tests(args: &[String]) {
|
|||||||
if test_funcs.is_empty() {
|
if test_funcs.is_empty() {
|
||||||
// No test functions, run the whole file
|
// No test functions, run the whole file
|
||||||
let mut interp = Interpreter::new();
|
let mut interp = Interpreter::new();
|
||||||
|
interp.register_auto_migrations(&auto_migrations);
|
||||||
interp.reset_test_results();
|
interp.reset_test_results();
|
||||||
|
|
||||||
match interp.run(&program) {
|
match interp.run(&program) {
|
||||||
@@ -755,6 +784,7 @@ fn run_tests(args: &[String]) {
|
|||||||
|
|
||||||
for test_name in &test_funcs {
|
for test_name in &test_funcs {
|
||||||
let mut interp = Interpreter::new();
|
let mut interp = Interpreter::new();
|
||||||
|
interp.register_auto_migrations(&auto_migrations);
|
||||||
interp.reset_test_results();
|
interp.reset_test_results();
|
||||||
|
|
||||||
// First run the file to define all functions
|
// First run the file to define all functions
|
||||||
@@ -1046,6 +1076,16 @@ fn handle_pkg_command(args: &[String]) {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"search" => {
|
||||||
|
if args.len() < 2 {
|
||||||
|
eprintln!("Usage: lux pkg search <query>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
search_registry(&args[1]);
|
||||||
|
}
|
||||||
|
"publish" => {
|
||||||
|
publish_package(&project_root);
|
||||||
|
}
|
||||||
"help" | "--help" | "-h" => {
|
"help" | "--help" | "-h" => {
|
||||||
print_pkg_help();
|
print_pkg_help();
|
||||||
}
|
}
|
||||||
@@ -1057,6 +1097,163 @@ fn handle_pkg_command(args: &[String]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_registry(query: &str) {
|
||||||
|
let registry_url = std::env::var("LUX_REGISTRY_URL")
|
||||||
|
.unwrap_or_else(|_| "https://pkgs.lux-lang.org".to_string());
|
||||||
|
|
||||||
|
println!("Searching for '{}' in {}...", query, registry_url);
|
||||||
|
|
||||||
|
// Make HTTP request to registry
|
||||||
|
let url = format!("{}/api/v1/search?q={}", registry_url, query);
|
||||||
|
|
||||||
|
// Use a simple HTTP client (could use reqwest in production)
|
||||||
|
match simple_http_get(&url) {
|
||||||
|
Ok(response) => {
|
||||||
|
// Parse JSON response and display results
|
||||||
|
if response.contains("\"packages\":[]") {
|
||||||
|
println!("No packages found matching '{}'", query);
|
||||||
|
} else {
|
||||||
|
println!("\nFound packages:");
|
||||||
|
// Simple parsing of package names from JSON
|
||||||
|
for line in response.lines() {
|
||||||
|
if line.contains("\"name\":") {
|
||||||
|
if let Some(start) = line.find("\"name\":") {
|
||||||
|
let rest = &line[start + 8..];
|
||||||
|
if let Some(end) = rest.find('"') {
|
||||||
|
let name = &rest[..end];
|
||||||
|
println!(" {}", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to connect to registry: {}", e);
|
||||||
|
eprintln!("Make sure the registry server is running or check LUX_REGISTRY_URL");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn publish_package(project_root: &std::path::Path) {
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
let registry_url = std::env::var("LUX_REGISTRY_URL")
|
||||||
|
.unwrap_or_else(|_| "https://pkgs.lux-lang.org".to_string());
|
||||||
|
|
||||||
|
// Load manifest
|
||||||
|
let manifest_path = project_root.join("lux.toml");
|
||||||
|
if !manifest_path.exists() {
|
||||||
|
eprintln!("No lux.toml found");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest_content = fs::read_to_string(&manifest_path).unwrap();
|
||||||
|
|
||||||
|
// Extract project info
|
||||||
|
let mut name = String::new();
|
||||||
|
let mut version = String::new();
|
||||||
|
|
||||||
|
for line in manifest_content.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.starts_with("name") {
|
||||||
|
if let Some(eq) = line.find('=') {
|
||||||
|
name = line[eq+1..].trim().trim_matches('"').to_string();
|
||||||
|
}
|
||||||
|
} else if line.starts_with("version") {
|
||||||
|
if let Some(eq) = line.find('=') {
|
||||||
|
version = line[eq+1..].trim().trim_matches('"').to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.is_empty() || version.is_empty() {
|
||||||
|
eprintln!("Invalid lux.toml: missing name or version");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Publishing {} v{} to {}...", name, version, registry_url);
|
||||||
|
|
||||||
|
// Create tarball of the package
|
||||||
|
let tarball_name = format!("{}-{}.tar.gz", name, version);
|
||||||
|
let tarball_path = project_root.join(&tarball_name);
|
||||||
|
|
||||||
|
// Use tar command to create tarball
|
||||||
|
let status = std::process::Command::new("tar")
|
||||||
|
.arg("-czf")
|
||||||
|
.arg(&tarball_path)
|
||||||
|
.arg("--exclude=.lux_packages")
|
||||||
|
.arg("--exclude=.git")
|
||||||
|
.arg("--exclude=target")
|
||||||
|
.arg("-C")
|
||||||
|
.arg(project_root)
|
||||||
|
.arg(".")
|
||||||
|
.status();
|
||||||
|
|
||||||
|
match status {
|
||||||
|
Ok(s) if s.success() => {
|
||||||
|
println!("Created package tarball: {}", tarball_name);
|
||||||
|
println!();
|
||||||
|
println!("To publish, upload the tarball to the registry:");
|
||||||
|
println!(" curl -X POST {}/api/v1/publish -F 'package=@{}'",
|
||||||
|
registry_url, tarball_name);
|
||||||
|
|
||||||
|
// Clean up tarball
|
||||||
|
// fs::remove_file(&tarball_path).ok();
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
eprintln!("Failed to create package tarball");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to run tar: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_http_get(url: &str) -> Result<String, String> {
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
// Parse URL
|
||||||
|
let url = url.strip_prefix("http://").or_else(|| url.strip_prefix("https://")).unwrap_or(url);
|
||||||
|
let (host_port, path) = if let Some(slash) = url.find('/') {
|
||||||
|
(&url[..slash], &url[slash..])
|
||||||
|
} else {
|
||||||
|
(url, "/")
|
||||||
|
};
|
||||||
|
|
||||||
|
let (host, port) = if let Some(colon) = host_port.find(':') {
|
||||||
|
(&host_port[..colon], host_port[colon+1..].parse::<u16>().unwrap_or(80))
|
||||||
|
} else {
|
||||||
|
(host_port, 80)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stream = TcpStream::connect((host, port))
|
||||||
|
.map_err(|e| format!("Connection failed: {}", e))?;
|
||||||
|
|
||||||
|
let request = format!(
|
||||||
|
"GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
|
||||||
|
path, host
|
||||||
|
);
|
||||||
|
|
||||||
|
stream.write_all(request.as_bytes())
|
||||||
|
.map_err(|e| format!("Write failed: {}", e))?;
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
stream.read_to_string(&mut response)
|
||||||
|
.map_err(|e| format!("Read failed: {}", e))?;
|
||||||
|
|
||||||
|
// Extract body (after \r\n\r\n)
|
||||||
|
if let Some(body_start) = response.find("\r\n\r\n") {
|
||||||
|
Ok(response[body_start + 4..].to_string())
|
||||||
|
} else {
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn print_pkg_help() {
|
fn print_pkg_help() {
|
||||||
println!("Lux Package Manager");
|
println!("Lux Package Manager");
|
||||||
println!();
|
println!();
|
||||||
@@ -1075,6 +1272,11 @@ fn print_pkg_help() {
|
|||||||
println!(" list, ls List dependencies and their status");
|
println!(" list, ls List dependencies and their status");
|
||||||
println!(" update Update all dependencies");
|
println!(" update Update all dependencies");
|
||||||
println!(" clean Remove installed packages");
|
println!(" clean Remove installed packages");
|
||||||
|
println!(" search <query> Search for packages in the registry");
|
||||||
|
println!(" publish Publish package to the registry");
|
||||||
|
println!();
|
||||||
|
println!("Registry Configuration:");
|
||||||
|
println!(" Set LUX_REGISTRY_URL to use a custom registry (default: https://pkgs.lux-lang.org)");
|
||||||
println!();
|
println!();
|
||||||
println!("Examples:");
|
println!("Examples:");
|
||||||
println!(" lux pkg init");
|
println!(" lux pkg init");
|
||||||
@@ -1084,6 +1286,8 @@ fn print_pkg_help() {
|
|||||||
println!(" lux pkg add mylib --git https://github.com/user/mylib");
|
println!(" lux pkg add mylib --git https://github.com/user/mylib");
|
||||||
println!(" lux pkg add local-lib --path ../lib");
|
println!(" lux pkg add local-lib --path ../lib");
|
||||||
println!(" lux pkg remove http");
|
println!(" lux pkg remove http");
|
||||||
|
println!(" lux pkg search json");
|
||||||
|
println!(" lux pkg publish");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_pkg_here(name: Option<&str>) {
|
fn init_pkg_here(name: Option<&str>) {
|
||||||
@@ -1298,7 +1502,7 @@ impl LuxHelper {
|
|||||||
|
|
||||||
let commands = vec![
|
let commands = vec![
|
||||||
":help", ":h", ":quit", ":q", ":type", ":t", ":clear", ":load", ":l",
|
":help", ":h", ":quit", ":q", ":type", ":t", ":clear", ":load", ":l",
|
||||||
":trace", ":traces", ":info", ":i", ":env",
|
":trace", ":traces", ":info", ":i", ":env", ":doc", ":d", ":browse", ":b",
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
@@ -1386,6 +1590,100 @@ impl Highlighter for LuxHelper {
|
|||||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||||
Cow::Owned(format!("\x1b[90m{}\x1b[0m", hint))
|
Cow::Owned(format!("\x1b[90m{}\x1b[0m", hint))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||||
|
let mut result = String::with_capacity(line.len() * 2);
|
||||||
|
let mut chars = line.char_indices().peekable();
|
||||||
|
|
||||||
|
while let Some((i, c)) = chars.next() {
|
||||||
|
if c == '"' {
|
||||||
|
// String literal - highlight in green
|
||||||
|
result.push_str("\x1b[32m\"");
|
||||||
|
let mut escaped = false;
|
||||||
|
for (_, ch) in chars.by_ref() {
|
||||||
|
result.push(ch);
|
||||||
|
if escaped {
|
||||||
|
escaped = false;
|
||||||
|
} else if ch == '\\' {
|
||||||
|
escaped = true;
|
||||||
|
} else if ch == '"' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push_str("\x1b[0m");
|
||||||
|
} else if c == ':' && i == 0 {
|
||||||
|
// Command - highlight in cyan
|
||||||
|
result.push_str("\x1b[36m:");
|
||||||
|
for (_, ch) in chars.by_ref() {
|
||||||
|
if ch.is_whitespace() {
|
||||||
|
result.push_str("\x1b[0m");
|
||||||
|
result.push(ch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.push(ch);
|
||||||
|
}
|
||||||
|
// Continue with the rest
|
||||||
|
for (_, ch) in chars.by_ref() {
|
||||||
|
result.push(ch);
|
||||||
|
}
|
||||||
|
} else if c.is_ascii_digit() || (c == '-' && chars.peek().map(|(_, ch)| ch.is_ascii_digit()).unwrap_or(false)) {
|
||||||
|
// Number - highlight in yellow
|
||||||
|
result.push_str("\x1b[33m");
|
||||||
|
result.push(c);
|
||||||
|
while let Some(&(_, ch)) = chars.peek() {
|
||||||
|
if ch.is_ascii_digit() || ch == '.' {
|
||||||
|
result.push(ch);
|
||||||
|
chars.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push_str("\x1b[0m");
|
||||||
|
} else if c.is_alphabetic() || c == '_' {
|
||||||
|
// Identifier or keyword
|
||||||
|
let start = i;
|
||||||
|
let mut end = i + c.len_utf8();
|
||||||
|
while let Some(&(j, ch)) = chars.peek() {
|
||||||
|
if ch.is_alphanumeric() || ch == '_' {
|
||||||
|
end = j + ch.len_utf8();
|
||||||
|
chars.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let word = &line[start..end];
|
||||||
|
|
||||||
|
// Check if it's a keyword
|
||||||
|
if self.keywords.contains(word) {
|
||||||
|
result.push_str("\x1b[35m"); // Magenta for keywords
|
||||||
|
result.push_str(word);
|
||||||
|
result.push_str("\x1b[0m");
|
||||||
|
} else if word.starts_with(char::is_uppercase) {
|
||||||
|
result.push_str("\x1b[34m"); // Blue for types/constructors
|
||||||
|
result.push_str(word);
|
||||||
|
result.push_str("\x1b[0m");
|
||||||
|
} else {
|
||||||
|
result.push_str(word);
|
||||||
|
}
|
||||||
|
} else if c == '/' && chars.peek().map(|(_, ch)| *ch == '/').unwrap_or(false) {
|
||||||
|
// Comment - highlight in gray
|
||||||
|
result.push_str("\x1b[90m");
|
||||||
|
result.push(c);
|
||||||
|
for (_, ch) in chars.by_ref() {
|
||||||
|
result.push(ch);
|
||||||
|
}
|
||||||
|
result.push_str("\x1b[0m");
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cow::Owned(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Validator for LuxHelper {}
|
impl Validator for LuxHelper {}
|
||||||
@@ -1587,6 +1885,21 @@ fn handle_command(
|
|||||||
interp.print_traces();
|
interp.print_traces();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
":doc" | ":d" => {
|
||||||
|
if let Some(name) = arg {
|
||||||
|
show_doc(name);
|
||||||
|
} else {
|
||||||
|
println!("Usage: :doc <name>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
":browse" | ":b" => {
|
||||||
|
if let Some(module) = arg {
|
||||||
|
browse_module(module);
|
||||||
|
} else {
|
||||||
|
println!("Usage: :browse <module>");
|
||||||
|
println!("Available modules: List, String, Option, Result, Console, Random, File, Http");
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unknown command: {}", cmd);
|
println!("Unknown command: {}", cmd);
|
||||||
println!("Type :help for help");
|
println!("Type :help for help");
|
||||||
@@ -1618,6 +1931,218 @@ fn show_environment(checker: &TypeChecker, helper: &LuxHelper) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_doc(name: &str) {
|
||||||
|
// Built-in documentation for common functions
|
||||||
|
let doc = match name {
|
||||||
|
// List functions
|
||||||
|
"List.length" => Some(("List.length : List<T> -> Int", "Returns the number of elements in a list.")),
|
||||||
|
"List.head" => Some(("List.head : List<T> -> Option<T>", "Returns the first element of a list, or None if empty.")),
|
||||||
|
"List.tail" => Some(("List.tail : List<T> -> Option<List<T>>", "Returns all elements except the first, or None if empty.")),
|
||||||
|
"List.map" => Some(("List.map : (List<A>, fn(A) -> B) -> List<B>", "Applies a function to each element, returning a new list.")),
|
||||||
|
"List.filter" => Some(("List.filter : (List<T>, fn(T) -> Bool) -> List<T>", "Returns elements for which the predicate returns true.")),
|
||||||
|
"List.fold" => Some(("List.fold : (List<T>, U, fn(U, T) -> U) -> U", "Reduces a list to a single value using an accumulator function.")),
|
||||||
|
"List.reverse" => Some(("List.reverse : List<T> -> List<T>", "Returns a new list with elements in reverse order.")),
|
||||||
|
"List.concat" => Some(("List.concat : (List<T>, List<T>) -> List<T>", "Concatenates two lists.")),
|
||||||
|
"List.take" => Some(("List.take : (List<T>, Int) -> List<T>", "Returns the first n elements.")),
|
||||||
|
"List.drop" => Some(("List.drop : (List<T>, Int) -> List<T>", "Returns all elements after the first n.")),
|
||||||
|
"List.get" => Some(("List.get : (List<T>, Int) -> Option<T>", "Returns the element at index n, or None if out of bounds.")),
|
||||||
|
"List.contains" => Some(("List.contains : (List<T>, T) -> Bool", "Returns true if the list contains the element.")),
|
||||||
|
"List.all" => Some(("List.all : (List<T>, fn(T) -> Bool) -> Bool", "Returns true if all elements satisfy the predicate.")),
|
||||||
|
"List.any" => Some(("List.any : (List<T>, fn(T) -> Bool) -> Bool", "Returns true if any element satisfies the predicate.")),
|
||||||
|
"List.find" => Some(("List.find : (List<T>, fn(T) -> Bool) -> Option<T>", "Returns the first element satisfying the predicate.")),
|
||||||
|
"List.sort" => Some(("List.sort : List<Int> -> List<Int>", "Sorts a list of integers in ascending order.")),
|
||||||
|
"List.range" => Some(("List.range : (Int, Int) -> List<Int>", "Creates a list of integers from start to end (exclusive).")),
|
||||||
|
|
||||||
|
// String functions
|
||||||
|
"String.length" => Some(("String.length : String -> Int", "Returns the number of characters in a string.")),
|
||||||
|
"String.concat" => Some(("String.concat : (String, String) -> String", "Concatenates two strings.")),
|
||||||
|
"String.substring" => Some(("String.substring : (String, Int, Int) -> String", "Returns a substring from start to end index.")),
|
||||||
|
"String.split" => Some(("String.split : (String, String) -> List<String>", "Splits a string by a delimiter.")),
|
||||||
|
"String.join" => Some(("String.join : (List<String>, String) -> String", "Joins a list of strings with a delimiter.")),
|
||||||
|
"String.trim" => Some(("String.trim : String -> String", "Removes leading and trailing whitespace.")),
|
||||||
|
"String.contains" => Some(("String.contains : (String, String) -> Bool", "Returns true if the string contains the substring.")),
|
||||||
|
"String.replace" => Some(("String.replace : (String, String, String) -> String", "Replaces all occurrences of a pattern.")),
|
||||||
|
"String.startsWith" => Some(("String.startsWith : (String, String) -> Bool", "Returns true if the string starts with the prefix.")),
|
||||||
|
"String.endsWith" => Some(("String.endsWith : (String, String) -> Bool", "Returns true if the string ends with the suffix.")),
|
||||||
|
"String.toUpper" => Some(("String.toUpper : String -> String", "Converts to uppercase.")),
|
||||||
|
"String.toLower" => Some(("String.toLower : String -> String", "Converts to lowercase.")),
|
||||||
|
|
||||||
|
// Option functions
|
||||||
|
"Option.map" => Some(("Option.map : (Option<A>, fn(A) -> B) -> Option<B>", "Applies a function to the value inside Some, or returns None.")),
|
||||||
|
"Option.flatMap" => Some(("Option.flatMap : (Option<A>, fn(A) -> Option<B>) -> Option<B>", "Like map, but the function returns an Option.")),
|
||||||
|
"Option.getOrElse" => Some(("Option.getOrElse : (Option<T>, T) -> T", "Returns the value inside Some, or the default if None.")),
|
||||||
|
"Option.isSome" => Some(("Option.isSome : Option<T> -> Bool", "Returns true if the value is Some.")),
|
||||||
|
"Option.isNone" => Some(("Option.isNone : Option<T> -> Bool", "Returns true if the value is None.")),
|
||||||
|
|
||||||
|
// Result functions
|
||||||
|
"Result.map" => Some(("Result.map : (Result<A, E>, fn(A) -> B) -> Result<B, E>", "Applies a function to the Ok value.")),
|
||||||
|
"Result.flatMap" => Some(("Result.flatMap : (Result<A, E>, fn(A) -> Result<B, E>) -> Result<B, E>", "Like map, but the function returns a Result.")),
|
||||||
|
"Result.getOrElse" => Some(("Result.getOrElse : (Result<T, E>, T) -> T", "Returns the Ok value, or the default if Err.")),
|
||||||
|
"Result.isOk" => Some(("Result.isOk : Result<T, E> -> Bool", "Returns true if the value is Ok.")),
|
||||||
|
"Result.isErr" => Some(("Result.isErr : Result<T, E> -> Bool", "Returns true if the value is Err.")),
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
"Console.print" => Some(("Console.print : String -> Unit", "Prints a string to the console.")),
|
||||||
|
"Console.readLine" => Some(("Console.readLine : () -> String", "Reads a line from standard input.")),
|
||||||
|
"Random.int" => Some(("Random.int : (Int, Int) -> Int", "Generates a random integer in the range [min, max].")),
|
||||||
|
"Random.float" => Some(("Random.float : () -> Float", "Generates a random float in [0, 1).")),
|
||||||
|
"Random.bool" => Some(("Random.bool : () -> Bool", "Generates a random boolean.")),
|
||||||
|
"File.read" => Some(("File.read : String -> String", "Reads the contents of a file.")),
|
||||||
|
"File.write" => Some(("File.write : (String, String) -> Unit", "Writes content to a file.")),
|
||||||
|
"File.exists" => Some(("File.exists : String -> Bool", "Returns true if the file exists.")),
|
||||||
|
"Http.get" => Some(("Http.get : String -> String", "Performs an HTTP GET request.")),
|
||||||
|
"Http.post" => Some(("Http.post : (String, String) -> String", "Performs an HTTP POST request with a body.")),
|
||||||
|
"Time.now" => Some(("Time.now : () -> Int", "Returns the current Unix timestamp in milliseconds.")),
|
||||||
|
"Sql.open" => Some(("Sql.open : String -> SqlConn", "Opens a SQLite database file.")),
|
||||||
|
"Sql.openMemory" => Some(("Sql.openMemory : () -> SqlConn", "Opens an in-memory SQLite database.")),
|
||||||
|
"Sql.query" => Some(("Sql.query : (SqlConn, String) -> List<Row>", "Executes a SQL query and returns all rows.")),
|
||||||
|
"Sql.execute" => Some(("Sql.execute : (SqlConn, String) -> Int", "Executes a SQL statement and returns affected rows.")),
|
||||||
|
"Postgres.connect" => Some(("Postgres.connect : String -> Int", "Connects to a PostgreSQL database.")),
|
||||||
|
"Postgres.query" => Some(("Postgres.query : (Int, String) -> List<Row>", "Executes a SQL query on PostgreSQL.")),
|
||||||
|
"Postgres.execute" => Some(("Postgres.execute : (Int, String) -> Int", "Executes a SQL statement on PostgreSQL.")),
|
||||||
|
|
||||||
|
// Language constructs
|
||||||
|
"fn" => Some(("fn name(args): Type = body", "Defines a function.")),
|
||||||
|
"let" => Some(("let name = value", "Binds a value to a name.")),
|
||||||
|
"if" => Some(("if condition then expr1 else expr2", "Conditional expression.")),
|
||||||
|
"match" => Some(("match value { pattern => expr, ... }", "Pattern matching expression.")),
|
||||||
|
"effect" => Some(("effect Name { fn op(...): Type }", "Defines an effect with operations.")),
|
||||||
|
"handler" => Some(("handler { op => body }", "Defines an effect handler.")),
|
||||||
|
"with" => Some(("fn f(): T with {Effect1, Effect2}", "Declares effects a function may perform.")),
|
||||||
|
"type" => Some(("type Name<T> = ...", "Defines a type alias.")),
|
||||||
|
"run" => Some(("run expr with { handler }", "Executes an expression with effect handlers.")),
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match doc {
|
||||||
|
Some((sig, desc)) => {
|
||||||
|
println!("\x1b[1m{}\x1b[0m", sig);
|
||||||
|
println!();
|
||||||
|
println!(" {}", desc);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("No documentation for '{}'", name);
|
||||||
|
println!("Try :doc List.map or :browse List");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn browse_module(module: &str) {
|
||||||
|
let exports: Vec<(&str, &str)> = match module {
|
||||||
|
"List" => vec![
|
||||||
|
("length", "List<T> -> Int"),
|
||||||
|
("head", "List<T> -> Option<T>"),
|
||||||
|
("tail", "List<T> -> Option<List<T>>"),
|
||||||
|
("get", "(List<T>, Int) -> Option<T>"),
|
||||||
|
("map", "(List<A>, fn(A) -> B) -> List<B>"),
|
||||||
|
("filter", "(List<T>, fn(T) -> Bool) -> List<T>"),
|
||||||
|
("fold", "(List<T>, U, fn(U, T) -> U) -> U"),
|
||||||
|
("reverse", "List<T> -> List<T>"),
|
||||||
|
("concat", "(List<T>, List<T>) -> List<T>"),
|
||||||
|
("take", "(List<T>, Int) -> List<T>"),
|
||||||
|
("drop", "(List<T>, Int) -> List<T>"),
|
||||||
|
("contains", "(List<T>, T) -> Bool"),
|
||||||
|
("all", "(List<T>, fn(T) -> Bool) -> Bool"),
|
||||||
|
("any", "(List<T>, fn(T) -> Bool) -> Bool"),
|
||||||
|
("find", "(List<T>, fn(T) -> Bool) -> Option<T>"),
|
||||||
|
("sort", "List<Int> -> List<Int>"),
|
||||||
|
("range", "(Int, Int) -> List<Int>"),
|
||||||
|
("forEach", "(List<T>, fn(T) -> Unit) -> Unit"),
|
||||||
|
],
|
||||||
|
"String" => vec![
|
||||||
|
("length", "String -> Int"),
|
||||||
|
("concat", "(String, String) -> String"),
|
||||||
|
("substring", "(String, Int, Int) -> String"),
|
||||||
|
("split", "(String, String) -> List<String>"),
|
||||||
|
("join", "(List<String>, String) -> String"),
|
||||||
|
("trim", "String -> String"),
|
||||||
|
("contains", "(String, String) -> Bool"),
|
||||||
|
("replace", "(String, String, String) -> String"),
|
||||||
|
("startsWith", "(String, String) -> Bool"),
|
||||||
|
("endsWith", "(String, String) -> Bool"),
|
||||||
|
("toUpper", "String -> String"),
|
||||||
|
("toLower", "String -> String"),
|
||||||
|
("lines", "String -> List<String>"),
|
||||||
|
],
|
||||||
|
"Option" => vec![
|
||||||
|
("map", "(Option<A>, fn(A) -> B) -> Option<B>"),
|
||||||
|
("flatMap", "(Option<A>, fn(A) -> Option<B>) -> Option<B>"),
|
||||||
|
("getOrElse", "(Option<T>, T) -> T"),
|
||||||
|
("isSome", "Option<T> -> Bool"),
|
||||||
|
("isNone", "Option<T> -> Bool"),
|
||||||
|
],
|
||||||
|
"Result" => vec![
|
||||||
|
("map", "(Result<A, E>, fn(A) -> B) -> Result<B, E>"),
|
||||||
|
("flatMap", "(Result<A, E>, fn(A) -> Result<B, E>) -> Result<B, E>"),
|
||||||
|
("getOrElse", "(Result<T, E>, T) -> T"),
|
||||||
|
("isOk", "Result<T, E> -> Bool"),
|
||||||
|
("isErr", "Result<T, E> -> Bool"),
|
||||||
|
],
|
||||||
|
"Console" => vec![
|
||||||
|
("print", "String -> Unit"),
|
||||||
|
("readLine", "() -> String"),
|
||||||
|
],
|
||||||
|
"Random" => vec![
|
||||||
|
("int", "(Int, Int) -> Int"),
|
||||||
|
("float", "() -> Float"),
|
||||||
|
("bool", "() -> Bool"),
|
||||||
|
],
|
||||||
|
"File" => vec![
|
||||||
|
("read", "String -> String"),
|
||||||
|
("write", "(String, String) -> Unit"),
|
||||||
|
("exists", "String -> Bool"),
|
||||||
|
("delete", "String -> Unit"),
|
||||||
|
],
|
||||||
|
"Http" => vec![
|
||||||
|
("get", "String -> String"),
|
||||||
|
("post", "(String, String) -> String"),
|
||||||
|
],
|
||||||
|
"Time" => vec![
|
||||||
|
("now", "() -> Int"),
|
||||||
|
],
|
||||||
|
"Sql" => vec![
|
||||||
|
("open", "String -> SqlConn"),
|
||||||
|
("openMemory", "() -> SqlConn"),
|
||||||
|
("close", "SqlConn -> Unit"),
|
||||||
|
("query", "(SqlConn, String) -> List<Row>"),
|
||||||
|
("queryOne", "(SqlConn, String) -> Option<Row>"),
|
||||||
|
("execute", "(SqlConn, String) -> Int"),
|
||||||
|
("beginTx", "SqlConn -> Unit"),
|
||||||
|
("commit", "SqlConn -> Unit"),
|
||||||
|
("rollback", "SqlConn -> Unit"),
|
||||||
|
],
|
||||||
|
"Postgres" => vec![
|
||||||
|
("connect", "String -> Int"),
|
||||||
|
("close", "Int -> Unit"),
|
||||||
|
("query", "(Int, String) -> List<Row>"),
|
||||||
|
("queryOne", "(Int, String) -> Option<Row>"),
|
||||||
|
("execute", "(Int, String) -> Int"),
|
||||||
|
("beginTx", "Int -> Unit"),
|
||||||
|
("commit", "Int -> Unit"),
|
||||||
|
("rollback", "Int -> Unit"),
|
||||||
|
],
|
||||||
|
"Test" => vec![
|
||||||
|
("assert", "(Bool, String) -> Unit"),
|
||||||
|
("assertEqual", "(T, T) -> Unit"),
|
||||||
|
("assertTrue", "Bool -> Unit"),
|
||||||
|
("assertFalse", "Bool -> Unit"),
|
||||||
|
("fail", "String -> Unit"),
|
||||||
|
],
|
||||||
|
_ => {
|
||||||
|
println!("Unknown module: {}", module);
|
||||||
|
println!("Available modules: List, String, Option, Result, Console, Random, File, Http, Time, Sql, Postgres, Test");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("\x1b[1mmodule {}\x1b[0m", module);
|
||||||
|
println!();
|
||||||
|
for (name, sig) in exports {
|
||||||
|
println!(" \x1b[34m{}.{}\x1b[0m : {}", module, name, sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_type(expr_str: &str, checker: &mut TypeChecker) {
|
fn show_type(expr_str: &str, checker: &mut TypeChecker) {
|
||||||
// Wrap expression in a let to parse it
|
// Wrap expression in a let to parse it
|
||||||
let wrapped = format!("let _expr_ = {}", expr_str);
|
let wrapped = format!("let _expr_ = {}", expr_str);
|
||||||
@@ -3073,13 +3598,12 @@ c")"#;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_diagnostic_render_with_real_code() {
|
fn test_diagnostic_render_with_real_code() {
|
||||||
let source = "fn add(a: Int, b: Int): Int = a + b\nlet result = add(1, \"two\")";
|
let source = "fn add(a: Int, b: Int): Int = a + b\nlet result = add(1, \"two\")";
|
||||||
let diag = Diagnostic {
|
let diag = Diagnostic::error(
|
||||||
severity: Severity::Error,
|
"Type Mismatch",
|
||||||
title: "Type Mismatch".to_string(),
|
"Expected Int but got String",
|
||||||
message: "Expected Int but got String".to_string(),
|
Span { start: 56, end: 61 },
|
||||||
span: Span { start: 56, end: 61 },
|
)
|
||||||
hints: vec!["The second argument should be an Int.".to_string()],
|
.with_hint("The second argument should be an Int.");
|
||||||
};
|
|
||||||
|
|
||||||
let output = render_diagnostic_plain(&diag, source, Some("example.lux"));
|
let output = render_diagnostic_plain(&diag, source, Some("example.lux"));
|
||||||
|
|
||||||
@@ -3098,8 +3622,10 @@ c")"#;
|
|||||||
};
|
};
|
||||||
let diag = error.to_diagnostic();
|
let diag = error.to_diagnostic();
|
||||||
|
|
||||||
assert_eq!(diag.title, "Unknown Name");
|
assert_eq!(diag.title, "Undefined Variable");
|
||||||
assert!(diag.hints.iter().any(|h| h.contains("spelling")));
|
assert!(diag.hints.iter().any(|h| h.contains("spelling")));
|
||||||
|
// Check error code is set
|
||||||
|
assert!(diag.code.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use crate::diagnostics::{Diagnostic, Severity};
|
use crate::diagnostics::{Diagnostic, ErrorCode, Severity};
|
||||||
use crate::lexer::{LexError, Lexer, StringPart, Token, TokenKind};
|
use crate::lexer::{LexError, Lexer, StringPart, Token, TokenKind};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
@@ -27,57 +27,79 @@ impl fmt::Display for ParseError {
|
|||||||
impl ParseError {
|
impl ParseError {
|
||||||
/// Convert to a rich diagnostic for Elm-style error display
|
/// Convert to a rich diagnostic for Elm-style error display
|
||||||
pub fn to_diagnostic(&self) -> Diagnostic {
|
pub fn to_diagnostic(&self) -> Diagnostic {
|
||||||
let (title, hints) = categorize_parse_error(&self.message);
|
let (code, title, hints) = categorize_parse_error(&self.message);
|
||||||
|
|
||||||
Diagnostic {
|
Diagnostic {
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
|
code,
|
||||||
title,
|
title,
|
||||||
message: self.message.clone(),
|
message: self.message.clone(),
|
||||||
span: self.span,
|
span: self.span,
|
||||||
hints,
|
hints,
|
||||||
|
expected_type: None,
|
||||||
|
actual_type: None,
|
||||||
|
secondary_spans: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Categorize parse errors to provide better titles and hints
|
/// Categorize parse errors to provide better titles, hints, and error codes
|
||||||
fn categorize_parse_error(message: &str) -> (String, Vec<String>) {
|
fn categorize_parse_error(message: &str) -> (Option<ErrorCode>, String, Vec<String>) {
|
||||||
let message_lower = message.to_lowercase();
|
let message_lower = message.to_lowercase();
|
||||||
|
|
||||||
if message_lower.contains("unexpected") && message_lower.contains("expected") {
|
if message_lower.contains("unexpected") && message_lower.contains("expected") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0101),
|
||||||
"Unexpected Token".to_string(),
|
"Unexpected Token".to_string(),
|
||||||
vec!["Check for missing or misplaced punctuation.".to_string()],
|
vec!["Check for missing or misplaced punctuation.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("expected") && message_lower.contains("expression") {
|
} else if message_lower.contains("expected") && message_lower.contains("expression") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0101),
|
||||||
"Missing Expression".to_string(),
|
"Missing Expression".to_string(),
|
||||||
vec!["An expression was expected here.".to_string()],
|
vec!["An expression was expected here.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("expected") && message_lower.contains(":") {
|
} else if message_lower.contains("expected") && message_lower.contains(":") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0104),
|
||||||
"Missing Type Annotation".to_string(),
|
"Missing Type Annotation".to_string(),
|
||||||
vec!["A type annotation is required here.".to_string()],
|
vec!["A type annotation is required here.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("unclosed") || message_lower.contains("unterminated") {
|
} else if message_lower.contains("unclosed") || message_lower.contains("unterminated") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0102),
|
||||||
"Unclosed Delimiter".to_string(),
|
"Unclosed Delimiter".to_string(),
|
||||||
vec![
|
vec![
|
||||||
"Check for matching opening and closing brackets.".to_string(),
|
"Check for matching opening and closing brackets.".to_string(),
|
||||||
"Make sure all strings are properly closed with quotes.".to_string(),
|
"Make sure all strings are properly closed with quotes.".to_string(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
} else if message_lower.contains("invalid") && message_lower.contains("literal") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0103),
|
||||||
|
"Invalid Literal".to_string(),
|
||||||
|
vec!["Check the format of the literal value.".to_string()],
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("invalid") && message_lower.contains("operator") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0105),
|
||||||
|
"Invalid Operator".to_string(),
|
||||||
|
vec!["Check the operator syntax.".to_string()],
|
||||||
|
)
|
||||||
} else if message_lower.contains("invalid") {
|
} else if message_lower.contains("invalid") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0100),
|
||||||
"Invalid Syntax".to_string(),
|
"Invalid Syntax".to_string(),
|
||||||
vec!["Check the syntax of this construct.".to_string()],
|
vec!["Check the syntax of this construct.".to_string()],
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("identifier") {
|
} else if message_lower.contains("identifier") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0100),
|
||||||
"Invalid Identifier".to_string(),
|
"Invalid Identifier".to_string(),
|
||||||
vec!["Identifiers must start with a letter and contain only letters, numbers, and underscores.".to_string()],
|
vec!["Identifiers must start with a letter and contain only letters, numbers, and underscores.".to_string()],
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
("Parse Error".to_string(), vec![])
|
(Some(ErrorCode::E0100), "Parse Error".to_string(), vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ use crate::ast::{
|
|||||||
ImportDecl, LetDecl, Literal, LiteralKind, MatchArm, Parameter, Pattern, Program, Span,
|
ImportDecl, LetDecl, Literal, LiteralKind, MatchArm, Parameter, Pattern, Program, Span,
|
||||||
Statement, TraitDecl, TypeDecl, TypeExpr, UnaryOp, VariantFields,
|
Statement, TraitDecl, TypeDecl, TypeExpr, UnaryOp, VariantFields,
|
||||||
};
|
};
|
||||||
use crate::diagnostics::{find_similar_names, format_did_you_mean, Diagnostic, Severity};
|
use crate::diagnostics::{find_similar_names, format_did_you_mean, Diagnostic, ErrorCode, Severity};
|
||||||
use crate::exhaustiveness::{check_exhaustiveness, missing_patterns_hint};
|
use crate::exhaustiveness::{check_exhaustiveness, missing_patterns_hint};
|
||||||
use crate::modules::ModuleLoader;
|
use crate::modules::ModuleLoader;
|
||||||
use crate::schema::{SchemaRegistry, Compatibility, BreakingChange};
|
use crate::schema::{SchemaRegistry, Compatibility, BreakingChange, AutoMigration};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
self, unify, EffectDef, EffectOpDef, EffectSet, HandlerDef, Property, PropertySet,
|
self, unify, unify_with_env, EffectDef, EffectOpDef, EffectSet, HandlerDef, Property, PropertySet,
|
||||||
TraitBoundDef, TraitDef, TraitImpl, TraitMethodDef, Type, TypeEnv, TypeScheme, VariantDef,
|
TraitBoundDef, TraitDef, TraitImpl, TraitMethodDef, Type, TypeEnv, TypeScheme, VariantDef,
|
||||||
VariantFieldsDef, VersionInfo,
|
VariantFieldsDef, VersionInfo,
|
||||||
};
|
};
|
||||||
@@ -39,66 +39,239 @@ impl std::fmt::Display for TypeError {
|
|||||||
impl TypeError {
|
impl TypeError {
|
||||||
/// Convert to a rich diagnostic for Elm-style error display
|
/// Convert to a rich diagnostic for Elm-style error display
|
||||||
pub fn to_diagnostic(&self) -> Diagnostic {
|
pub fn to_diagnostic(&self) -> Diagnostic {
|
||||||
// Categorize the error and extract hints
|
// Categorize the error and extract hints, error code, and type info
|
||||||
let (title, hints) = categorize_type_error(&self.message);
|
let (code, title, hints, expected, actual) = categorize_type_error(&self.message);
|
||||||
|
|
||||||
Diagnostic {
|
Diagnostic {
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
|
code,
|
||||||
title,
|
title,
|
||||||
message: self.message.clone(),
|
message: self.message.clone(),
|
||||||
span: self.span,
|
span: self.span,
|
||||||
hints,
|
hints,
|
||||||
|
expected_type: expected,
|
||||||
|
actual_type: actual,
|
||||||
|
secondary_spans: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Categorize a type error message to provide better titles and hints
|
/// Extract expected and actual types from an error message
|
||||||
fn categorize_type_error(message: &str) -> (String, Vec<String>) {
|
fn extract_types_from_message(message: &str) -> (Option<String>, Option<String>) {
|
||||||
|
// Try to extract "expected X, got Y" patterns
|
||||||
|
let patterns = [
|
||||||
|
("expected ", ", got "),
|
||||||
|
("expected ", " but got "),
|
||||||
|
("expected ", " found "),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (exp_prefix, got_prefix) in patterns {
|
||||||
|
if let Some(exp_start) = message.to_lowercase().find(exp_prefix) {
|
||||||
|
let after_expected = &message[exp_start + exp_prefix.len()..];
|
||||||
|
if let Some(got_pos) = after_expected.to_lowercase().find(got_prefix) {
|
||||||
|
let expected = after_expected[..got_pos].trim().to_string();
|
||||||
|
let after_got = &after_expected[got_pos + got_prefix.len()..];
|
||||||
|
// Take until end of word or punctuation
|
||||||
|
let actual = after_got
|
||||||
|
.split(|c: char| c == ',' || c == '.' || c == ';' || c == ')' || c.is_whitespace())
|
||||||
|
.next()
|
||||||
|
.unwrap_or("")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
if !expected.is_empty() && !actual.is_empty() {
|
||||||
|
return (Some(expected), Some(actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try "cannot unify X with Y" pattern
|
||||||
|
if let Some(unify_pos) = message.to_lowercase().find("cannot unify ") {
|
||||||
|
let after_unify = &message[unify_pos + 13..];
|
||||||
|
if let Some(with_pos) = after_unify.to_lowercase().find(" with ") {
|
||||||
|
let type1 = after_unify[..with_pos].trim().to_string();
|
||||||
|
let type2 = after_unify[with_pos + 6..]
|
||||||
|
.split(|c: char| c == ',' || c == '.' || c == ';' || c == ')' || c.is_whitespace())
|
||||||
|
.next()
|
||||||
|
.unwrap_or("")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
if !type1.is_empty() && !type2.is_empty() {
|
||||||
|
return (Some(type1), Some(type2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Categorize a type error message to provide better titles, hints, and error codes
|
||||||
|
fn categorize_type_error(message: &str) -> (Option<ErrorCode>, String, Vec<String>, Option<String>, Option<String>) {
|
||||||
let message_lower = message.to_lowercase();
|
let message_lower = message.to_lowercase();
|
||||||
|
let (expected, actual) = extract_types_from_message(message);
|
||||||
|
|
||||||
if message_lower.contains("type mismatch") {
|
if message_lower.contains("type mismatch") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0201),
|
||||||
"Type Mismatch".to_string(),
|
"Type Mismatch".to_string(),
|
||||||
vec!["Check that the types on both sides of the expression are compatible.".to_string()],
|
vec!["Check that the types on both sides of the expression are compatible.".to_string()],
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("undefined variable") || message_lower.contains("not found") {
|
} else if message_lower.contains("undefined variable") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0301),
|
||||||
|
"Undefined Variable".to_string(),
|
||||||
|
vec![
|
||||||
|
"Check the spelling of the variable name.".to_string(),
|
||||||
|
"Make sure the variable is defined before use.".to_string(),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("undefined function") || message_lower.contains("function") && message_lower.contains("not found") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0302),
|
||||||
|
"Undefined Function".to_string(),
|
||||||
|
vec![
|
||||||
|
"Check the spelling of the function name.".to_string(),
|
||||||
|
"Make sure the function is imported or defined.".to_string(),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("not found") || message_lower.contains("unknown") && message_lower.contains("name") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0301),
|
||||||
"Unknown Name".to_string(),
|
"Unknown Name".to_string(),
|
||||||
vec![
|
vec![
|
||||||
"Check the spelling of the name.".to_string(),
|
"Check the spelling of the name.".to_string(),
|
||||||
"Make sure the variable is defined before use.".to_string(),
|
"Make sure the variable is defined before use.".to_string(),
|
||||||
],
|
],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("cannot unify") {
|
} else if message_lower.contains("cannot unify") {
|
||||||
(
|
(
|
||||||
"Type Mismatch".to_string(),
|
Some(ErrorCode::E0202),
|
||||||
|
"Cannot Unify Types".to_string(),
|
||||||
vec!["The types are not compatible. Check your function arguments and return types.".to_string()],
|
vec!["The types are not compatible. Check your function arguments and return types.".to_string()],
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("expected") && message_lower.contains("argument") {
|
} else if message_lower.contains("expected") && message_lower.contains("argument") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0209),
|
||||||
"Wrong Number of Arguments".to_string(),
|
"Wrong Number of Arguments".to_string(),
|
||||||
vec!["Check the function signature and provide the correct number of arguments.".to_string()],
|
vec!["Check the function signature and provide the correct number of arguments.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("pure") && message_lower.contains("effect") {
|
} else if message_lower.contains("pure") && message_lower.contains("effect") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0701),
|
||||||
"Purity Violation".to_string(),
|
"Purity Violation".to_string(),
|
||||||
vec![
|
vec![
|
||||||
"Functions marked 'is pure' cannot perform effects.".to_string(),
|
"Functions marked 'is pure' cannot perform effects.".to_string(),
|
||||||
"Remove the 'is pure' annotation or handle the effects.".to_string(),
|
"Remove the 'is pure' annotation or handle the effects.".to_string(),
|
||||||
],
|
],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("effect") && message_lower.contains("unhandled") {
|
} else if message_lower.contains("effect") && message_lower.contains("unhandled") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0401),
|
||||||
"Unhandled Effect".to_string(),
|
"Unhandled Effect".to_string(),
|
||||||
vec!["Use a 'handle' expression to provide an implementation for this effect.".to_string()],
|
vec!["Use a 'handle' expression to provide an implementation for this effect.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
} else if message_lower.contains("recursive") {
|
} else if message_lower.contains("recursive") {
|
||||||
(
|
(
|
||||||
|
Some(ErrorCode::E0205),
|
||||||
"Invalid Recursion".to_string(),
|
"Invalid Recursion".to_string(),
|
||||||
vec!["Check that recursive calls have proper base cases.".to_string()],
|
vec!["Check that recursive calls have proper base cases.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("unknown effect") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0402),
|
||||||
|
"Unknown Effect".to_string(),
|
||||||
|
vec![
|
||||||
|
"Make sure the effect is spelled correctly.".to_string(),
|
||||||
|
"Built-in effects: Console, File, Process, Http, Random, Time, Sql.".to_string(),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("record has no field") || message_lower.contains("missing field") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0207),
|
||||||
|
"Missing Record Field".to_string(),
|
||||||
|
vec!["Check the field name spelling or review the record definition.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("undefined field") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0308),
|
||||||
|
"Undefined Field".to_string(),
|
||||||
|
vec!["Check the field name spelling or review the record definition.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("cannot access field") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0308),
|
||||||
|
"Invalid Field Access".to_string(),
|
||||||
|
vec!["Field access is only valid on record types.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("effect") && (message_lower.contains("not available") || message_lower.contains("missing")) {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0403),
|
||||||
|
"Missing Effect Handler".to_string(),
|
||||||
|
vec![
|
||||||
|
"Add the effect to your function's effect list.".to_string(),
|
||||||
|
"Example: fn myFn(): Int with {Console, Sql} = ...".to_string(),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("non-exhaustive") || message_lower.contains("exhaustive") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0501),
|
||||||
|
"Non-Exhaustive Patterns".to_string(),
|
||||||
|
vec!["Add patterns for the missing cases or use a wildcard pattern '_'.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("duplicate") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0305),
|
||||||
|
"Duplicate Definition".to_string(),
|
||||||
|
vec!["Each name can only be defined once in the same scope.".to_string()],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else if message_lower.contains("return type") {
|
||||||
|
(
|
||||||
|
Some(ErrorCode::E0211),
|
||||||
|
"Return Type Mismatch".to_string(),
|
||||||
|
vec!["The function body's type doesn't match the declared return type.".to_string()],
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
("Type Error".to_string(), vec![])
|
(
|
||||||
|
Some(ErrorCode::E0200),
|
||||||
|
"Type Error".to_string(),
|
||||||
|
vec![],
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,6 +621,62 @@ fn is_structurally_decreasing(arg: &Expr, param_name: &str) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate an auto-migration expression based on detected auto-migratable changes
|
||||||
|
/// Creates an expression like: { existingField1: old.existingField1, ..., newOptionalField: None }
|
||||||
|
fn generate_auto_migration_expr(
|
||||||
|
prev_def: &ast::TypeDef,
|
||||||
|
new_def: &ast::TypeDef,
|
||||||
|
auto_migrations: &[AutoMigration],
|
||||||
|
span: Span,
|
||||||
|
) -> Option<Expr> {
|
||||||
|
// Only handle record types for auto-migration
|
||||||
|
let (prev_fields, new_fields) = match (prev_def, new_def) {
|
||||||
|
(ast::TypeDef::Record(prev), ast::TypeDef::Record(new)) => (prev, new),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build a record expression with all fields
|
||||||
|
let mut field_exprs: Vec<(ast::Ident, Expr)> = Vec::new();
|
||||||
|
|
||||||
|
// Map of new fields that need auto-migration defaults
|
||||||
|
let auto_migrate_fields: std::collections::HashSet<String> = auto_migrations
|
||||||
|
.iter()
|
||||||
|
.filter_map(|m| match m {
|
||||||
|
AutoMigration::AddFieldWithDefault { field_name, .. } => Some(field_name.clone()),
|
||||||
|
AutoMigration::WidenType { .. } => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// For each field in the new definition
|
||||||
|
for new_field in new_fields {
|
||||||
|
let field_name = &new_field.name.name;
|
||||||
|
|
||||||
|
if auto_migrate_fields.contains(field_name) {
|
||||||
|
// New optional field - add with None default
|
||||||
|
field_exprs.push((
|
||||||
|
new_field.name.clone(),
|
||||||
|
Expr::Var(Ident::new("None", span)),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// Existing field - copy from old: old.fieldName
|
||||||
|
field_exprs.push((
|
||||||
|
new_field.name.clone(),
|
||||||
|
Expr::Field {
|
||||||
|
object: Box::new(Expr::Var(Ident::new("old", span))),
|
||||||
|
field: new_field.name.clone(),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the record expression
|
||||||
|
Some(Expr::Record {
|
||||||
|
fields: field_exprs,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a function terminates (structural recursion check)
|
/// Check if a function terminates (structural recursion check)
|
||||||
fn check_termination(func: &FunctionDecl) -> Result<(), String> {
|
fn check_termination(func: &FunctionDecl) -> Result<(), String> {
|
||||||
// Non-recursive functions always terminate
|
// Non-recursive functions always terminate
|
||||||
@@ -530,6 +759,12 @@ impl TypeChecker {
|
|||||||
self.env.bindings.get(name)
|
self.env.bindings.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get auto-generated migrations from type checking
|
||||||
|
/// Returns: type_name -> from_version -> migration_body
|
||||||
|
pub fn get_auto_migrations(&self) -> &HashMap<String, HashMap<u32, Expr>> {
|
||||||
|
&self.migrations
|
||||||
|
}
|
||||||
|
|
||||||
/// Type check a program
|
/// Type check a program
|
||||||
pub fn check_program(&mut self, program: &Program) -> Result<(), Vec<TypeError>> {
|
pub fn check_program(&mut self, program: &Program) -> Result<(), Vec<TypeError>> {
|
||||||
// First pass: collect all declarations
|
// First pass: collect all declarations
|
||||||
@@ -873,8 +1108,28 @@ impl TypeChecker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Compatibility::AutoMigrate(_)) | Ok(Compatibility::Compatible) => {
|
Ok(Compatibility::AutoMigrate(auto_migrations)) => {
|
||||||
// No issues - compatible or auto-migratable
|
// Generate automatic migration if one wasn't provided
|
||||||
|
if !self.migrations.get(&type_name).map(|m| m.contains_key(&prev_version)).unwrap_or(false) {
|
||||||
|
// Get the previous version's fields to build the migration
|
||||||
|
if let Some(prev_def) = self.schema_registry.get_version(&type_name, prev_version) {
|
||||||
|
if let Some(generated) = generate_auto_migration_expr(
|
||||||
|
&prev_def.definition,
|
||||||
|
&type_decl.definition,
|
||||||
|
&auto_migrations,
|
||||||
|
type_decl.name.span,
|
||||||
|
) {
|
||||||
|
// Register the auto-generated migration
|
||||||
|
self.migrations
|
||||||
|
.entry(type_name.clone())
|
||||||
|
.or_default()
|
||||||
|
.insert(prev_version, generated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Compatibility::Compatible) => {
|
||||||
|
// No issues - fully compatible
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Previous version not registered yet - that's fine
|
// Previous version not registered yet - that's fine
|
||||||
@@ -974,7 +1229,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
fn check_function(&mut self, func: &FunctionDecl) {
|
fn check_function(&mut self, func: &FunctionDecl) {
|
||||||
// Validate that all declared effects exist
|
// Validate that all declared effects exist
|
||||||
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test"];
|
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test", "Sql", "Postgres"];
|
||||||
for effect in &func.effects {
|
for effect in &func.effects {
|
||||||
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
||||||
let is_defined = self.env.lookup_effect(&effect.name).is_some();
|
let is_defined = self.env.lookup_effect(&effect.name).is_some();
|
||||||
@@ -1023,9 +1278,9 @@ impl TypeChecker {
|
|||||||
self.current_effects = old_effects;
|
self.current_effects = old_effects;
|
||||||
self.inferring_effects = old_inferring;
|
self.inferring_effects = old_inferring;
|
||||||
|
|
||||||
// Check that body type matches return type
|
// Check that body type matches return type (expand type aliases for record types)
|
||||||
let return_type = self.resolve_type(&func.return_type);
|
let return_type = self.resolve_type(&func.return_type);
|
||||||
if let Err(e) = unify(&body_type, &return_type) {
|
if let Err(e) = unify_with_env(&body_type, &return_type, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Function '{}' body has type {}, but declared return type is {}: {}",
|
"Function '{}' body has type {}, but declared return type is {}: {}",
|
||||||
@@ -1656,10 +1911,36 @@ impl TypeChecker {
|
|||||||
match unify(&func_type, &expected_fn) {
|
match unify(&func_type, &expected_fn) {
|
||||||
Ok(subst) => result_type.apply(&subst),
|
Ok(subst) => result_type.apply(&subst),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.errors.push(TypeError {
|
// Provide more detailed error message based on the type of mismatch
|
||||||
message: format!("Type mismatch in function call: {}", e),
|
let message = if e.contains("arity mismatch") || e.contains("different number") {
|
||||||
span,
|
// Try to extract actual function arity
|
||||||
});
|
if let Type::Function { params, .. } = &func_type {
|
||||||
|
format!(
|
||||||
|
"Function expects {} argument(s), but {} were provided",
|
||||||
|
params.len(),
|
||||||
|
arg_types.len()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("Type mismatch in function call: {}", e)
|
||||||
|
}
|
||||||
|
} else if e.contains("Effect mismatch") {
|
||||||
|
format!("Type mismatch in function call: {}", e)
|
||||||
|
} else {
|
||||||
|
// Get function name if available for better error
|
||||||
|
let fn_name = if let Expr::Var(id) = func {
|
||||||
|
Some(id.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(name) = fn_name {
|
||||||
|
format!("Type error in call to '{}': {}", name, e)
|
||||||
|
} else {
|
||||||
|
format!("Type mismatch in function call: {}", e)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.errors.push(TypeError { message, span });
|
||||||
Type::Error
|
Type::Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1729,7 +2010,7 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Built-in effects are always available
|
// Built-in effects are always available
|
||||||
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test"];
|
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test", "Sql", "Postgres"];
|
||||||
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
||||||
|
|
||||||
// Track this effect for inference
|
// Track this effect for inference
|
||||||
@@ -1814,10 +2095,18 @@ impl TypeChecker {
|
|||||||
Type::Record(fields) => match fields.iter().find(|(n, _)| n == &field.name) {
|
Type::Record(fields) => match fields.iter().find(|(n, _)| n == &field.name) {
|
||||||
Some((_, t)) => t.clone(),
|
Some((_, t)) => t.clone(),
|
||||||
None => {
|
None => {
|
||||||
self.errors.push(TypeError {
|
// Find similar field names
|
||||||
message: format!("Record has no field '{}'", field.name),
|
let available_fields: Vec<&str> = fields.iter().map(|(n, _)| n.as_str()).collect();
|
||||||
span,
|
let suggestions = find_similar_names(&field.name, available_fields.clone(), 2);
|
||||||
});
|
|
||||||
|
let mut message = format!("Record has no field '{}'", field.name);
|
||||||
|
if let Some(hint) = format_did_you_mean(&suggestions) {
|
||||||
|
message.push_str(&format!(". {}", hint));
|
||||||
|
} else if !available_fields.is_empty() {
|
||||||
|
message.push_str(&format!(". Available fields: {}", available_fields.join(", ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.errors.push(TypeError { message, span });
|
||||||
Type::Error
|
Type::Error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1915,10 +2204,10 @@ impl TypeChecker {
|
|||||||
) -> Type {
|
) -> Type {
|
||||||
let value_type = self.infer_expr(value);
|
let value_type = self.infer_expr(value);
|
||||||
|
|
||||||
// Check declared type if present
|
// Check declared type if present (expand type aliases for record types)
|
||||||
if let Some(type_expr) = typ {
|
if let Some(type_expr) = typ {
|
||||||
let declared = self.resolve_type(type_expr);
|
let declared = self.resolve_type(type_expr);
|
||||||
if let Err(e) = unify(&value_type, &declared) {
|
if let Err(e) = unify_with_env(&value_type, &declared, &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Variable '{}' has type {}, but declared type is {}: {}",
|
"Variable '{}' has type {}, but declared type is {}: {}",
|
||||||
@@ -2140,7 +2429,7 @@ impl TypeChecker {
|
|||||||
.map(|(n, _)| (n.name.clone(), Type::var()))
|
.map(|(n, _)| (n.name.clone(), Type::var()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Err(e) = unify(expected, &Type::Record(field_types.clone())) {
|
if let Err(e) = unify_with_env(expected, &Type::Record(field_types.clone()), &self.env) {
|
||||||
self.errors.push(TypeError {
|
self.errors.push(TypeError {
|
||||||
message: format!("Record pattern doesn't match type {}: {}", expected, e),
|
message: format!("Record pattern doesn't match type {}: {}", expected, e),
|
||||||
span: *span,
|
span: *span,
|
||||||
@@ -2234,7 +2523,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
// Built-in effects are always available in run blocks (they have runtime implementations)
|
// Built-in effects are always available in run blocks (they have runtime implementations)
|
||||||
let builtin_effects: EffectSet =
|
let builtin_effects: EffectSet =
|
||||||
EffectSet::from_iter(["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer"].iter().map(|s| s.to_string()));
|
EffectSet::from_iter(["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Sql"].iter().map(|s| s.to_string()));
|
||||||
|
|
||||||
// Extend current effects with handled ones and built-in effects
|
// Extend current effects with handled ones and built-in effects
|
||||||
let combined = self.current_effects.union(&handled_effects).union(&builtin_effects);
|
let combined = self.current_effects.union(&handled_effects).union(&builtin_effects);
|
||||||
|
|||||||
126
src/types.rs
126
src/types.rs
@@ -1173,6 +1173,73 @@ impl TypeEnv {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add Sql effect for database access
|
||||||
|
// Connection is represented as Int (connection ID)
|
||||||
|
let row_type = Type::Record(vec![]); // Dynamic record type
|
||||||
|
env.effects.insert(
|
||||||
|
"Sql".to_string(),
|
||||||
|
EffectDef {
|
||||||
|
name: "Sql".to_string(),
|
||||||
|
type_params: Vec::new(),
|
||||||
|
operations: vec![
|
||||||
|
EffectOpDef {
|
||||||
|
name: "open".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::Int, // Connection ID
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "openMemory".to_string(),
|
||||||
|
params: vec![],
|
||||||
|
return_type: Type::Int, // Connection ID
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "close".to_string(),
|
||||||
|
params: vec![("conn".to_string(), Type::Int)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "execute".to_string(),
|
||||||
|
params: vec![
|
||||||
|
("conn".to_string(), Type::Int),
|
||||||
|
("sql".to_string(), Type::String),
|
||||||
|
],
|
||||||
|
return_type: Type::Int, // Rows affected
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "query".to_string(),
|
||||||
|
params: vec![
|
||||||
|
("conn".to_string(), Type::Int),
|
||||||
|
("sql".to_string(), Type::String),
|
||||||
|
],
|
||||||
|
return_type: Type::List(Box::new(Type::var())), // List of records
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "queryOne".to_string(),
|
||||||
|
params: vec![
|
||||||
|
("conn".to_string(), Type::Int),
|
||||||
|
("sql".to_string(), Type::String),
|
||||||
|
],
|
||||||
|
return_type: Type::Option(Box::new(Type::var())), // Optional record
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "beginTx".to_string(),
|
||||||
|
params: vec![("conn".to_string(), Type::Int)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "commit".to_string(),
|
||||||
|
params: vec![("conn".to_string(), Type::Int)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "rollback".to_string(),
|
||||||
|
params: vec![("conn".to_string(), Type::Int)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Add Some and Ok, Err constructors
|
// Add Some and Ok, Err constructors
|
||||||
// Some : fn(a) -> Option<a>
|
// Some : fn(a) -> Option<a>
|
||||||
let a = Type::var();
|
let a = Type::var();
|
||||||
@@ -1743,6 +1810,65 @@ impl TypeEnv {
|
|||||||
|
|
||||||
TypeScheme::poly(type_vars, typ.clone())
|
TypeScheme::poly(type_vars, typ.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expand a Named type to its underlying structural type if it's an alias
|
||||||
|
/// This is needed for unifying record type aliases with record literals
|
||||||
|
pub fn expand_type_alias(&self, ty: &Type) -> Type {
|
||||||
|
match ty {
|
||||||
|
Type::Named(name) => {
|
||||||
|
if let Some(type_def) = self.types.get(name) {
|
||||||
|
match type_def {
|
||||||
|
TypeDef::Alias(inner) => self.expand_type_alias(inner),
|
||||||
|
// For enums and records, keep the Named type
|
||||||
|
_ => ty.clone(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ty.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Function { params, return_type, effects, properties } => {
|
||||||
|
Type::Function {
|
||||||
|
params: params.iter().map(|p| self.expand_type_alias(p)).collect(),
|
||||||
|
return_type: Box::new(self.expand_type_alias(return_type)),
|
||||||
|
effects: effects.clone(),
|
||||||
|
properties: properties.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::App { constructor, args } => {
|
||||||
|
Type::App {
|
||||||
|
constructor: Box::new(self.expand_type_alias(constructor)),
|
||||||
|
args: args.iter().map(|a| self.expand_type_alias(a)).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Tuple(elems) => {
|
||||||
|
Type::Tuple(elems.iter().map(|e| self.expand_type_alias(e)).collect())
|
||||||
|
}
|
||||||
|
Type::Record(fields) => {
|
||||||
|
Type::Record(fields.iter().map(|(n, t)| (n.clone(), self.expand_type_alias(t))).collect())
|
||||||
|
}
|
||||||
|
Type::List(inner) => {
|
||||||
|
Type::List(Box::new(self.expand_type_alias(inner)))
|
||||||
|
}
|
||||||
|
Type::Option(inner) => {
|
||||||
|
Type::Option(Box::new(self.expand_type_alias(inner)))
|
||||||
|
}
|
||||||
|
Type::Versioned { base, version } => {
|
||||||
|
Type::Versioned {
|
||||||
|
base: Box::new(self.expand_type_alias(base)),
|
||||||
|
version: version.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Primitives and type variables stay as-is
|
||||||
|
_ => ty.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unify types with type alias expansion
|
||||||
|
pub fn unify_with_env(t1: &Type, t2: &Type, env: &TypeEnv) -> Result<Substitution, String> {
|
||||||
|
let expanded1 = env.expand_type_alias(t1);
|
||||||
|
let expanded2 = env.expand_type_alias(t2);
|
||||||
|
unify(&expanded1, &expanded2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unification of types
|
/// Unification of types
|
||||||
|
|||||||
@@ -296,3 +296,89 @@ fn when<M>(condition: Bool, element: Html<M>): Html<M> =
|
|||||||
// Conditionally apply attributes
|
// Conditionally apply attributes
|
||||||
fn attrIf<M>(condition: Bool, attr: Attr<M>): List<Attr<M>> =
|
fn attrIf<M>(condition: Bool, attr: Attr<M>): List<Attr<M>> =
|
||||||
if condition then [attr] else []
|
if condition then [attr] else []
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Static HTML Rendering (for SSG)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Render an attribute to a string
|
||||||
|
fn renderAttr<M>(attr: Attr<M>): String =
|
||||||
|
match attr {
|
||||||
|
Class(name) => " class=\"" + name + "\"",
|
||||||
|
Id(name) => " id=\"" + name + "\"",
|
||||||
|
Style(prop, val) => " style=\"" + prop + ": " + val + "\"",
|
||||||
|
Href(url) => " href=\"" + url + "\"",
|
||||||
|
Src(url) => " src=\"" + url + "\"",
|
||||||
|
Alt(desc) => " alt=\"" + desc + "\"",
|
||||||
|
Type(t) => " type=\"" + t + "\"",
|
||||||
|
Value(v) => " value=\"" + v + "\"",
|
||||||
|
Placeholder(p) => " placeholder=\"" + p + "\"",
|
||||||
|
Disabled(true) => " disabled",
|
||||||
|
Disabled(false) => "",
|
||||||
|
Checked(true) => " checked",
|
||||||
|
Checked(false) => "",
|
||||||
|
Name(n) => " name=\"" + n + "\"",
|
||||||
|
DataAttr(name, value) => " data-" + name + "=\"" + value + "\"",
|
||||||
|
// Event handlers are ignored in static rendering
|
||||||
|
OnClick(_) => "",
|
||||||
|
OnInput(_) => "",
|
||||||
|
OnSubmit(_) => "",
|
||||||
|
OnChange(_) => "",
|
||||||
|
OnMouseEnter(_) => "",
|
||||||
|
OnMouseLeave(_) => "",
|
||||||
|
OnFocus(_) => "",
|
||||||
|
OnBlur(_) => "",
|
||||||
|
OnKeyDown(_) => "",
|
||||||
|
OnKeyUp(_) => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render attributes list to string
|
||||||
|
fn renderAttrs<M>(attrs: List<Attr<M>>): String =
|
||||||
|
List.foldl(attrs, "", fn(acc, attr) => acc + renderAttr(attr))
|
||||||
|
|
||||||
|
// Self-closing tags
|
||||||
|
fn isSelfClosing(tag: String): Bool =
|
||||||
|
tag == "br" || tag == "hr" || tag == "img" || tag == "input" ||
|
||||||
|
tag == "meta" || tag == "link" || tag == "area" || tag == "base" ||
|
||||||
|
tag == "col" || tag == "embed" || tag == "source" || tag == "track" || tag == "wbr"
|
||||||
|
|
||||||
|
// Render Html to string
|
||||||
|
fn render<M>(html: Html<M>): String =
|
||||||
|
match html {
|
||||||
|
Element(tag, attrs, children) => {
|
||||||
|
let attrStr = renderAttrs(attrs)
|
||||||
|
if isSelfClosing(tag) then
|
||||||
|
"<" + tag + attrStr + " />"
|
||||||
|
else {
|
||||||
|
let childrenStr = List.foldl(children, "", fn(acc, child) => acc + render(child))
|
||||||
|
"<" + tag + attrStr + ">" + childrenStr + "</" + tag + ">"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Text(content) => escapeHtml(content),
|
||||||
|
Empty => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape HTML special characters
|
||||||
|
fn escapeHtml(s: String): String = {
|
||||||
|
// Simple replacement - a full implementation would handle all entities
|
||||||
|
let s1 = String.replace(s, "&", "&")
|
||||||
|
let s2 = String.replace(s1, "<", "<")
|
||||||
|
let s3 = String.replace(s2, ">", ">")
|
||||||
|
let s4 = String.replace(s3, "\"", """)
|
||||||
|
s4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a full HTML document
|
||||||
|
fn document(title: String, headExtra: List<Html<M>>, bodyContent: List<Html<M>>): String = {
|
||||||
|
let headElements = List.concat([
|
||||||
|
[Element("meta", [DataAttr("charset", "UTF-8")], [])],
|
||||||
|
[Element("meta", [Name("viewport"), Value("width=device-width, initial-scale=1.0")], [])],
|
||||||
|
[Element("title", [], [Text(title)])],
|
||||||
|
headExtra
|
||||||
|
])
|
||||||
|
let doc = Element("html", [DataAttr("lang", "en")], [
|
||||||
|
Element("head", [], headElements),
|
||||||
|
Element("body", [], bodyContent)
|
||||||
|
])
|
||||||
|
"<!DOCTYPE html>\n" + render(doc)
|
||||||
|
}
|
||||||
|
|||||||
161
stdlib/http.lux
Normal file
161
stdlib/http.lux
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// HTTP Framework for Lux
|
||||||
|
//
|
||||||
|
// Provides helpers for building web applications.
|
||||||
|
//
|
||||||
|
// Note: Due to current type system limitations, this module provides
|
||||||
|
// helper functions rather than abstract types. Use the HttpServer effect
|
||||||
|
// directly for the server loop.
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Response Builder Functions
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Create a 200 OK response
|
||||||
|
fn httpOk(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 200, body: body }
|
||||||
|
|
||||||
|
// Create a 201 Created response
|
||||||
|
fn httpCreated(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 201, body: body }
|
||||||
|
|
||||||
|
// Create a 204 No Content response
|
||||||
|
fn httpNoContent(): { status: Int, body: String } =
|
||||||
|
{ status: 204, body: "" }
|
||||||
|
|
||||||
|
// Create a 400 Bad Request response
|
||||||
|
fn httpBadRequest(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 400, body: body }
|
||||||
|
|
||||||
|
// Create a 401 Unauthorized response
|
||||||
|
fn httpUnauthorized(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 401, body: body }
|
||||||
|
|
||||||
|
// Create a 403 Forbidden response
|
||||||
|
fn httpForbidden(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 403, body: body }
|
||||||
|
|
||||||
|
// Create a 404 Not Found response
|
||||||
|
fn httpNotFound(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 404, body: body }
|
||||||
|
|
||||||
|
// Create a 500 Server Error response
|
||||||
|
fn httpServerError(body: String): { status: Int, body: String } =
|
||||||
|
{ status: 500, body: body }
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Path Matching
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Check if a path matches a pattern with wildcards
|
||||||
|
// Pattern "/users/:id" matches "/users/42"
|
||||||
|
fn pathMatches(path: String, pattern: String): Bool = {
|
||||||
|
let pathParts = String.split(path, "/")
|
||||||
|
let patternParts = String.split(pattern, "/")
|
||||||
|
if List.length(pathParts) != List.length(patternParts) then false
|
||||||
|
else matchPathParts(pathParts, patternParts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matchPathParts(pathParts: List<String>, patternParts: List<String>): Bool = {
|
||||||
|
if List.length(pathParts) == 0 then true
|
||||||
|
else {
|
||||||
|
match List.head(pathParts) {
|
||||||
|
None => true,
|
||||||
|
Some(pathPart) => {
|
||||||
|
match List.head(patternParts) {
|
||||||
|
None => true,
|
||||||
|
Some(patternPart) => {
|
||||||
|
let isMatch = if String.startsWith(patternPart, ":") then true else pathPart == patternPart
|
||||||
|
if isMatch then {
|
||||||
|
let restPath = Option.getOrElse(List.tail(pathParts), [])
|
||||||
|
let restPattern = Option.getOrElse(List.tail(patternParts), [])
|
||||||
|
matchPathParts(restPath, restPattern)
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract a path segment by position (0-indexed, skipping leading empty segment)
|
||||||
|
// For path "/users/42", getPathSegment(path, 1) returns Some("42")
|
||||||
|
fn getPathSegment(path: String, index: Int): Option<String> = {
|
||||||
|
let parts = String.split(path, "/")
|
||||||
|
List.get(parts, index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// JSON Helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Escape a string for JSON (handles quotes and backslashes)
|
||||||
|
fn jsonEscape(s: String): String = {
|
||||||
|
String.replace(String.replace(s, "\\", "\\\\"), "\"", "\\\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a JSON string field: "key": "value"
|
||||||
|
fn jsonString(key: String, value: String): String = {
|
||||||
|
"\"" + jsonEscape(key) + "\":\"" + jsonEscape(value) + "\""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a JSON number field: "key": 42
|
||||||
|
fn jsonNumber(key: String, value: Int): String = {
|
||||||
|
"\"" + jsonEscape(key) + "\":" + toString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a JSON boolean field: "key": true
|
||||||
|
fn jsonBool(key: String, value: Bool): String = {
|
||||||
|
let boolStr = if value then "true" else "false"
|
||||||
|
"\"" + jsonEscape(key) + "\":" + boolStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap content in JSON object braces
|
||||||
|
fn jsonObject(content: String): String =
|
||||||
|
"{" + content + "}"
|
||||||
|
|
||||||
|
// Wrap content in JSON array brackets
|
||||||
|
fn jsonArray(content: String): String =
|
||||||
|
"[" + content + "]"
|
||||||
|
|
||||||
|
// Join multiple JSON fields/items with commas
|
||||||
|
fn jsonJoin(items: List<String>): String =
|
||||||
|
String.join(items, ",")
|
||||||
|
|
||||||
|
// Create a JSON error object: {"error": "message"}
|
||||||
|
fn jsonErrorMsg(message: String): String =
|
||||||
|
jsonObject(jsonString("error", message))
|
||||||
|
|
||||||
|
// Create a JSON message object: {"message": "text"}
|
||||||
|
fn jsonMessage(text: String): String =
|
||||||
|
jsonObject(jsonString("message", text))
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Usage Example (copy into your file)
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// fn router(method: String, path: String, body: String): { status: Int, body: String } = {
|
||||||
|
// if method == "GET" && path == "/" then httpOk("Welcome!")
|
||||||
|
// else if method == "GET" && pathMatches(path, "/users/:id") then {
|
||||||
|
// match getPathSegment(path, 1) {
|
||||||
|
// Some(id) => httpOk(jsonObject(jsonString("id", id))),
|
||||||
|
// None => httpNotFound(jsonErrorMsg("User not found"))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else httpNotFound(jsonErrorMsg("Not found"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn main(): Unit with {Console, HttpServer} = {
|
||||||
|
// HttpServer.listen(8080)
|
||||||
|
// Console.print("Server running on port 8080")
|
||||||
|
// serveLoop(5) // Handle 5 requests
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn serveLoop(remaining: Int): Unit with {Console, HttpServer} = {
|
||||||
|
// if remaining <= 0 then HttpServer.stop()
|
||||||
|
// else {
|
||||||
|
// let req = HttpServer.accept()
|
||||||
|
// let resp = router(req.method, req.path, req.body)
|
||||||
|
// HttpServer.respond(resp.status, resp.body)
|
||||||
|
// serveLoop(remaining - 1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
// This module re-exports the core standard library modules.
|
// This module re-exports the core standard library modules.
|
||||||
// Import with: import stdlib
|
// Import with: import stdlib
|
||||||
|
|
||||||
// Re-export Html module
|
// Re-export core modules
|
||||||
pub import html
|
pub import html
|
||||||
pub import browser
|
pub import browser
|
||||||
|
pub import http
|
||||||
|
pub import testing
|
||||||
|
|||||||
192
stdlib/testing.lux
Normal file
192
stdlib/testing.lux
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
// Property-Based Testing Library for Lux
|
||||||
|
//
|
||||||
|
// This module provides generators and helper functions for property-based testing.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// Use the generator functions (genInt, genIntList, genString, etc.) in your
|
||||||
|
// property tests to generate random test data.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// fn testReverseInvolutive(n: Int): Unit with {Console, Test, Random} = {
|
||||||
|
// if n <= 0 then
|
||||||
|
// Console.print(" PASS reverse(reverse(xs)) == xs")
|
||||||
|
// else {
|
||||||
|
// let xs = genIntList(0, 100, 20)
|
||||||
|
// if List.reverse(List.reverse(xs)) == xs then
|
||||||
|
// testReverseInvolutive(n - 1)
|
||||||
|
// else
|
||||||
|
// Test.fail("Property failed")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Generator Module
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Character set for string generation
|
||||||
|
let GEN_CHARS = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
let GEN_ALPHANUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
// Generate a random integer in a range
|
||||||
|
fn genInt(min: Int, max: Int): Int with {Random} =
|
||||||
|
Random.int(min, max)
|
||||||
|
|
||||||
|
// Generate a random integer from 0 to max
|
||||||
|
fn genIntUpTo(max: Int): Int with {Random} =
|
||||||
|
Random.int(0, max)
|
||||||
|
|
||||||
|
// Generate a random positive integer
|
||||||
|
fn genPositiveInt(max: Int): Int with {Random} =
|
||||||
|
Random.int(1, max)
|
||||||
|
|
||||||
|
// Generate a random boolean
|
||||||
|
fn genBool(): Bool with {Random} =
|
||||||
|
Random.bool()
|
||||||
|
|
||||||
|
// Generate a random character (lowercase letter)
|
||||||
|
fn genChar(): String with {Random} = {
|
||||||
|
let idx = Random.int(0, 25)
|
||||||
|
String.substring(GEN_CHARS, idx, idx + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random alphanumeric character
|
||||||
|
fn genAlphaNum(): String with {Random} = {
|
||||||
|
let idx = Random.int(0, 61)
|
||||||
|
String.substring(GEN_ALPHANUM, idx, idx + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random string of given max length
|
||||||
|
fn genString(maxLen: Int): String with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genStringOfLength(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random string of exact length
|
||||||
|
fn genStringOfLength(len: Int): String with {Random} = {
|
||||||
|
if len <= 0 then
|
||||||
|
""
|
||||||
|
else
|
||||||
|
genChar() + genStringOfLength(len - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random list of integers
|
||||||
|
fn genIntList(min: Int, max: Int, maxLen: Int): List<Int> with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genIntListOfLength(min, max, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random list of integers with exact length
|
||||||
|
fn genIntListOfLength(min: Int, max: Int, len: Int): List<Int> with {Random} = {
|
||||||
|
if len <= 0 then
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
List.concat([Random.int(min, max)], genIntListOfLength(min, max, len - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random list of booleans
|
||||||
|
fn genBoolList(maxLen: Int): List<Bool> with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genBoolListOfLength(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genBoolListOfLength(len: Int): List<Bool> with {Random} = {
|
||||||
|
if len <= 0 then
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
List.concat([Random.bool()], genBoolListOfLength(len - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random list of strings
|
||||||
|
fn genStringList(maxStrLen: Int, maxLen: Int): List<String> with {Random} = {
|
||||||
|
let len = Random.int(0, maxLen)
|
||||||
|
genStringListOfLength(maxStrLen, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genStringListOfLength(maxStrLen: Int, len: Int): List<String> with {Random} = {
|
||||||
|
if len <= 0 then
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
List.concat([genString(maxStrLen)], genStringListOfLength(maxStrLen, len - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Shrinking Module
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Shrink an integer towards zero
|
||||||
|
fn shrinkInt(n: Int): List<Int> = {
|
||||||
|
if n == 0 then
|
||||||
|
[]
|
||||||
|
else if n > 0 then
|
||||||
|
[0, n / 2, n - 1]
|
||||||
|
else
|
||||||
|
[0, n / 2, n + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink a list by removing elements
|
||||||
|
fn shrinkList<T>(xs: List<T>): List<List<T>> = {
|
||||||
|
let len = List.length(xs)
|
||||||
|
if len == 0 then
|
||||||
|
[]
|
||||||
|
else if len == 1 then
|
||||||
|
[[]]
|
||||||
|
else {
|
||||||
|
// Return: empty list, first half, second half
|
||||||
|
let half = len / 2
|
||||||
|
let firstHalf = List.take(xs, half)
|
||||||
|
let secondHalf = List.drop(xs, half)
|
||||||
|
[[], firstHalf, secondHalf]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink a string by removing characters
|
||||||
|
fn shrinkString(s: String): List<String> = {
|
||||||
|
let len = String.length(s)
|
||||||
|
if len == 0 then
|
||||||
|
[]
|
||||||
|
else if len == 1 then
|
||||||
|
[""]
|
||||||
|
else {
|
||||||
|
let half = len / 2
|
||||||
|
["", String.substring(s, 0, half), String.substring(s, half, len)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Common Properties
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Check that a list is sorted
|
||||||
|
fn isSorted(xs: List<Int>): Bool = {
|
||||||
|
match List.head(xs) {
|
||||||
|
None => true,
|
||||||
|
Some(first) => {
|
||||||
|
match List.tail(xs) {
|
||||||
|
None => true,
|
||||||
|
Some(rest) => isSortedHelper(first, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isSortedHelper(prev: Int, xs: List<Int>): Bool = {
|
||||||
|
match List.head(xs) {
|
||||||
|
None => true,
|
||||||
|
Some(curr) => {
|
||||||
|
if prev <= curr then {
|
||||||
|
match List.tail(xs) {
|
||||||
|
None => true,
|
||||||
|
Some(rest) => isSortedHelper(curr, rest)
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that two lists have the same elements (ignoring order)
|
||||||
|
fn sameElements(xs: List<Int>, ys: List<Int>): Bool = {
|
||||||
|
List.length(xs) == List.length(ys) &&
|
||||||
|
List.all(xs, fn(x) => List.contains(ys, x)) &&
|
||||||
|
List.all(ys, fn(y) => List.contains(xs, y))
|
||||||
|
}
|
||||||
153
website/lux-site/LUX_WEAKNESSES.md
Normal file
153
website/lux-site/LUX_WEAKNESSES.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Lux Weaknesses Discovered During Website Development
|
||||||
|
|
||||||
|
This document tracks issues and limitations discovered while building the Lux website in Lux.
|
||||||
|
|
||||||
|
## Critical Issues
|
||||||
|
|
||||||
|
### 1. Module Import System Not Working
|
||||||
|
|
||||||
|
**Description:** The `import` statement doesn't appear to work for importing standard library modules.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```lux
|
||||||
|
import html // Doesn't make html functions available
|
||||||
|
```
|
||||||
|
|
||||||
|
**Workaround:** Functions must be defined in the same file or copied.
|
||||||
|
|
||||||
|
**Status:** Needs investigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Parse Error in html.lux (Line 196-197)
|
||||||
|
|
||||||
|
**Description:** When trying to load files that import the html module, there's a parse error.
|
||||||
|
|
||||||
|
**Error Message:**
|
||||||
|
```
|
||||||
|
Module error: Module error in '<main>': Parse error: Parse error at 196-197: Unexpected token: \n
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status:** Needs investigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minor Issues
|
||||||
|
|
||||||
|
### 3. String.replace May Not Exist
|
||||||
|
|
||||||
|
**Description:** The `escapeHtml` function in html.lux uses `String.replace`, but this function may not be implemented.
|
||||||
|
|
||||||
|
**Workaround:** Implement character-by-character escaping.
|
||||||
|
|
||||||
|
**Status:** Needs verification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. FileSystem Effect Not Fully Implemented
|
||||||
|
|
||||||
|
**Description:** For static site generation, we need `FileSystem.mkdir`, `FileSystem.write`, `FileSystem.copy` operations. These may not be fully implemented.
|
||||||
|
|
||||||
|
**Workaround:** Use Bash commands via scripts.
|
||||||
|
|
||||||
|
**Status:** Needs verification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. List.concat May Have Issues
|
||||||
|
|
||||||
|
**Description:** The `List.concat` function used in html.lux document generation may not handle nested lists correctly.
|
||||||
|
|
||||||
|
**Status:** Needs verification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Gaps
|
||||||
|
|
||||||
|
### 6. No Built-in Static Site Generation
|
||||||
|
|
||||||
|
**Description:** There's no built-in way to generate static HTML files from Lux. A static site generator effect or module would be helpful.
|
||||||
|
|
||||||
|
**Recommendation:** Add a `FileSystem` effect with:
|
||||||
|
- `mkdir(path: String): Unit`
|
||||||
|
- `write(path: String, content: String): Unit`
|
||||||
|
- `copy(src: String, dst: String): Unit`
|
||||||
|
- `readDir(path: String): List<String>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. No Template String Support
|
||||||
|
|
||||||
|
**Description:** Multi-line strings and template literals (like JavaScript's backticks) would make HTML generation much easier.
|
||||||
|
|
||||||
|
**Current Approach:**
|
||||||
|
```lux
|
||||||
|
let html = "<div class=\"" + className + "\">" + content + "</div>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Better Approach (not available):**
|
||||||
|
```lux
|
||||||
|
let html = `<div class="${className}">${content}</div>`
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status:** Feature request
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. No Markdown Parser
|
||||||
|
|
||||||
|
**Description:** A built-in Markdown parser would be valuable for documentation sites.
|
||||||
|
|
||||||
|
**Status:** Feature request
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Working Features
|
||||||
|
|
||||||
|
These features work correctly and can be used for website generation:
|
||||||
|
|
||||||
|
1. **String concatenation** - Works with `+` operator
|
||||||
|
2. **Conditional expressions** - `if/then/else` works
|
||||||
|
3. **Console output** - `Console.print()` works
|
||||||
|
4. **Basic types** - `String`, `Int`, `Bool` work
|
||||||
|
5. **Let bindings** - Variable assignment works
|
||||||
|
6. **Functions** - Function definitions work
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### For Website MVP
|
||||||
|
|
||||||
|
Since the module system isn't working, the website should be:
|
||||||
|
1. **Hand-written HTML** (already done in `dist/index.html`)
|
||||||
|
2. **CSS separate file** (already done in `static/style.css`)
|
||||||
|
3. **Lux code examples** embedded as text in HTML
|
||||||
|
|
||||||
|
### For Future
|
||||||
|
|
||||||
|
Once these issues are fixed, the website can be:
|
||||||
|
1. **Generated from Lux** using the components and pages modules
|
||||||
|
2. **Markdown-based documentation** parsed and rendered by Lux
|
||||||
|
3. **Live playground** using WASM compilation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Results
|
||||||
|
|
||||||
|
| Feature | Status | Notes |
|
||||||
|
|---------|--------|-------|
|
||||||
|
| String concatenation | ✅ Works | `"<" + tag + ">"` |
|
||||||
|
| Conditionals | ✅ Works | `if x then y else z` |
|
||||||
|
| Console.print | ✅ Works | Basic output |
|
||||||
|
| Module imports | ❌ Broken | Parse errors |
|
||||||
|
| Html module | ❌ Blocked | Depends on imports |
|
||||||
|
| FileSystem | ❓ Unknown | Not tested |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Date Log
|
||||||
|
|
||||||
|
| Date | Finding |
|
||||||
|
|------|---------|
|
||||||
|
| 2026-02-16 | Module import system not working, parse error at line 196-197 in html.lux |
|
||||||
224
website/lux-site/dist/index.html
vendored
Normal file
224
website/lux-site/dist/index.html
vendored
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Lux - Functional Programming with First-Class Effects</title>
|
||||||
|
<meta name="description" content="Lux is a functional programming language with first-class effects. Effects are explicit. Types are powerful. Performance is native.">
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✨</text></svg>">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Source+Serif+Pro:wght@400;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="nav">
|
||||||
|
<a href="/" class="nav-logo">LUX</a>
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href="/learn/" class="nav-link">Learn</a>
|
||||||
|
<a href="/docs/" class="nav-link">Docs</a>
|
||||||
|
<a href="/playground/" class="nav-link">Playground</a>
|
||||||
|
<a href="/community/" class="nav-link">Community</a>
|
||||||
|
</div>
|
||||||
|
<a href="https://github.com/luxlang/lux" class="nav-github">GitHub</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero-logo">
|
||||||
|
<pre class="logo-ascii">╦ ╦ ╦╦ ╦
|
||||||
|
║ ║ ║╔╣
|
||||||
|
╩═╝╚═╝╩ ╩</pre>
|
||||||
|
</div>
|
||||||
|
<h1 class="hero-title">
|
||||||
|
Functional Programming<br>
|
||||||
|
with First-Class Effects
|
||||||
|
</h1>
|
||||||
|
<p class="hero-tagline">
|
||||||
|
Effects are explicit. Types are powerful. Performance is native.
|
||||||
|
</p>
|
||||||
|
<div class="hero-cta">
|
||||||
|
<a href="/learn/getting-started/" class="btn btn-primary">Get Started</a>
|
||||||
|
<a href="/playground/" class="btn btn-secondary">Playground</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Code Demo Section -->
|
||||||
|
<section class="code-demo">
|
||||||
|
<div class="container">
|
||||||
|
<div class="code-demo-grid">
|
||||||
|
<div class="code-block">
|
||||||
|
<pre class="code"><code><span class="hljs-keyword">fn</span> <span class="hljs-function">processOrder</span>(
|
||||||
|
order: <span class="hljs-type">Order</span>
|
||||||
|
): <span class="hljs-type">Receipt</span>
|
||||||
|
<span class="hljs-keyword">with</span> {<span class="hljs-effect">Database</span>, <span class="hljs-effect">Email</span>} =
|
||||||
|
{
|
||||||
|
<span class="hljs-keyword">let</span> saved = <span class="hljs-effect">Database</span>.save(order)
|
||||||
|
<span class="hljs-effect">Email</span>.send(
|
||||||
|
order.customer,
|
||||||
|
<span class="hljs-string">"Order confirmed!"</span>
|
||||||
|
)
|
||||||
|
Receipt(saved.id)
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="code-explanation">
|
||||||
|
<h3>The type signature tells you everything</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Queries the database</li>
|
||||||
|
<li>Sends an email</li>
|
||||||
|
<li>Returns a Receipt</li>
|
||||||
|
</ul>
|
||||||
|
<p class="highlight">No surprises. No hidden side effects.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Value Props Section -->
|
||||||
|
<section class="value-props">
|
||||||
|
<div class="container">
|
||||||
|
<div class="value-props-grid">
|
||||||
|
<div class="value-prop card">
|
||||||
|
<h3 class="value-prop-title">EFFECTS</h3>
|
||||||
|
<p class="value-prop-desc">Side effects are tracked in the type signature. Know exactly what every function does.</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-prop card">
|
||||||
|
<h3 class="value-prop-title">TYPES</h3>
|
||||||
|
<p class="value-prop-desc">Full type inference with algebraic data types. Catch bugs at compile time.</p>
|
||||||
|
</div>
|
||||||
|
<div class="value-prop card">
|
||||||
|
<h3 class="value-prop-title">PERFORMANCE</h3>
|
||||||
|
<p class="value-prop-desc">Compiles to native C via gcc. Matches C performance, beats Rust and Zig.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Benchmarks Section -->
|
||||||
|
<section class="benchmarks">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Performance</h2>
|
||||||
|
<p class="section-subtitle">fib(35) benchmark — verified with hyperfine</p>
|
||||||
|
<div class="benchmarks-chart">
|
||||||
|
<div class="benchmark-row">
|
||||||
|
<span class="benchmark-lang">Lux</span>
|
||||||
|
<div class="benchmark-bar-container">
|
||||||
|
<div class="benchmark-bar" style="width: 100%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="benchmark-time">28.1ms</span>
|
||||||
|
</div>
|
||||||
|
<div class="benchmark-row">
|
||||||
|
<span class="benchmark-lang">C</span>
|
||||||
|
<div class="benchmark-bar-container">
|
||||||
|
<div class="benchmark-bar" style="width: 97%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="benchmark-time">29.0ms</span>
|
||||||
|
</div>
|
||||||
|
<div class="benchmark-row">
|
||||||
|
<span class="benchmark-lang">Rust</span>
|
||||||
|
<div class="benchmark-bar-container">
|
||||||
|
<div class="benchmark-bar" style="width: 68%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="benchmark-time">41.2ms</span>
|
||||||
|
</div>
|
||||||
|
<div class="benchmark-row">
|
||||||
|
<span class="benchmark-lang">Zig</span>
|
||||||
|
<div class="benchmark-bar-container">
|
||||||
|
<div class="benchmark-bar" style="width: 60%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="benchmark-time">47.0ms</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="benchmarks-note">
|
||||||
|
<a href="/benchmarks/">See full methodology →</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Testing Section -->
|
||||||
|
<section class="testing">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Testing Without Mocks</h2>
|
||||||
|
<p class="section-subtitle">Swap effect handlers at test time. Same code, different behavior.</p>
|
||||||
|
<div class="code-demo-grid">
|
||||||
|
<div class="code-block">
|
||||||
|
<pre class="code"><code><span class="hljs-comment">// Production</span>
|
||||||
|
<span class="hljs-keyword">run</span> processOrder(order) <span class="hljs-keyword">with</span> {
|
||||||
|
<span class="hljs-effect">Database</span> -> postgresDb,
|
||||||
|
<span class="hljs-effect">Email</span> -> smtpServer
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="code-block">
|
||||||
|
<pre class="code"><code><span class="hljs-comment">// Testing</span>
|
||||||
|
<span class="hljs-keyword">run</span> processOrder(order) <span class="hljs-keyword">with</span> {
|
||||||
|
<span class="hljs-effect">Database</span> -> inMemoryDb,
|
||||||
|
<span class="hljs-effect">Email</span> -> collectEmails
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Quick Start Section -->
|
||||||
|
<section class="quick-start">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Get Started</h2>
|
||||||
|
<div class="code-block">
|
||||||
|
<pre class="code"><code><span class="hljs-comment"># Install via Nix</span>
|
||||||
|
nix run github:luxlang/lux
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Or build from source</span>
|
||||||
|
git clone https://github.com/luxlang/lux
|
||||||
|
cd lux && nix develop
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Start the REPL</span>
|
||||||
|
./target/release/lux</code></pre>
|
||||||
|
</div>
|
||||||
|
<a href="/learn/getting-started/" class="btn btn-primary">Full Installation Guide →</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer-grid">
|
||||||
|
<div class="footer-brand">
|
||||||
|
<span class="footer-logo">LUX</span>
|
||||||
|
<p>Functional programming with first-class effects.</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-column">
|
||||||
|
<h4>LEARN</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/learn/getting-started/">Getting Started</a></li>
|
||||||
|
<li><a href="/learn/tutorial/">Tutorial</a></li>
|
||||||
|
<li><a href="/learn/examples/">Examples</a></li>
|
||||||
|
<li><a href="/docs/">Reference</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-column">
|
||||||
|
<h4>COMMUNITY</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://discord.gg/lux">Discord</a></li>
|
||||||
|
<li><a href="https://github.com/luxlang/lux">GitHub</a></li>
|
||||||
|
<li><a href="/community/contributing/">Contributing</a></li>
|
||||||
|
<li><a href="/community/code-of-conduct/">Code of Conduct</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-column">
|
||||||
|
<h4>ABOUT</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/benchmarks/">Benchmarks</a></li>
|
||||||
|
<li><a href="/blog/">Blog</a></li>
|
||||||
|
<li><a href="https://github.com/luxlang/lux/blob/main/LICENSE">License</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-bottom">
|
||||||
|
<p>© 2026 Lux Language</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
707
website/lux-site/dist/static/style.css
vendored
Normal file
707
website/lux-site/dist/static/style.css
vendored
Normal file
@@ -0,0 +1,707 @@
|
|||||||
|
/* ============================================================================
|
||||||
|
Lux Website - Sleek and Noble
|
||||||
|
Translucent black, white, and gold with strong serif typography
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* CSS Variables */
|
||||||
|
:root {
|
||||||
|
/* Backgrounds */
|
||||||
|
--bg-primary: #0a0a0a;
|
||||||
|
--bg-secondary: #111111;
|
||||||
|
--bg-glass: rgba(255, 255, 255, 0.03);
|
||||||
|
--bg-glass-hover: rgba(255, 255, 255, 0.06);
|
||||||
|
--bg-code: rgba(212, 175, 55, 0.05);
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||||
|
--text-muted: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
|
/* Gold accents */
|
||||||
|
--gold: #d4af37;
|
||||||
|
--gold-light: #f4d03f;
|
||||||
|
--gold-dark: #b8860b;
|
||||||
|
--gold-glow: rgba(212, 175, 55, 0.3);
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--border-subtle: rgba(255, 255, 255, 0.1);
|
||||||
|
--border-gold: rgba(212, 175, 55, 0.3);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-heading: "Playfair Display", Georgia, serif;
|
||||||
|
--font-body: "Source Serif Pro", Georgia, serif;
|
||||||
|
--font-code: "JetBrains Mono", "Fira Code", monospace;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--container-width: 1200px;
|
||||||
|
--section-padding: 6rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.7;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gold-light);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: clamp(2.5rem, 5vw, 4rem); }
|
||||||
|
h2 { font-size: clamp(2rem, 4vw, 3rem); }
|
||||||
|
h3 { font-size: clamp(1.5rem, 3vw, 2rem); }
|
||||||
|
h4 { font-size: 1.25rem; }
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--gold);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--gold-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container {
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Navigation
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: rgba(10, 10, 10, 0.9);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--gold);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-github {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-github:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
border-color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Hero Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
min-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at top, rgba(212, 175, 55, 0.08) 0%, transparent 50%),
|
||||||
|
var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-ascii {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--gold);
|
||||||
|
line-height: 1.2;
|
||||||
|
text-shadow: 0 0 30px var(--gold-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Buttons
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--gold-dark), var(--gold));
|
||||||
|
color: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: linear-gradient(135deg, var(--gold), var(--gold-light));
|
||||||
|
color: #0a0a0a;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 20px var(--gold-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--gold);
|
||||||
|
border: 1px solid var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
color: var(--gold-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Code Demo Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.code-demo {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-demo-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: var(--bg-code);
|
||||||
|
border: 1px solid var(--border-gold);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code code {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation h3 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation ul {
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation li {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation li::before {
|
||||||
|
content: "•";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation .highlight {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--gold-light);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Value Props Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.value-props {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-props-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-prop {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-prop-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-prop-desc {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Cards
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--bg-glass);
|
||||||
|
border: 1px solid rgba(212, 175, 55, 0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
background: var(--bg-glass-hover);
|
||||||
|
border-color: rgba(212, 175, 55, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Benchmarks Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.benchmarks {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-chart {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 60px 1fr 80px;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-lang {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-bar-container {
|
||||||
|
height: 24px;
|
||||||
|
background: var(--bg-glass);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--gold-dark), var(--gold));
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-time {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--gold-light);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-note {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-note a {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-note a:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Testing Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.testing {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.testing h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testing .code-demo-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.testing .code-demo-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Quick Start Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.quick-start {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-start h2 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-start .code-block {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Footer
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 4rem 2rem 2rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr 1fr 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-logo {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--gold);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column h4 {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column a {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column a:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bottom {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bottom p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Documentation Layout
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.doc-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 250px 1fr;
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
height: calc(100vh - 80px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-right: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar-section h4 {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar-section ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-link {
|
||||||
|
display: block;
|
||||||
|
padding: 0.4rem 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-link:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-link.active {
|
||||||
|
color: var(--gold-light);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content {
|
||||||
|
padding: 3rem;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h1 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Responsive Design
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.code-demo-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-props-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar {
|
||||||
|
position: static;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
min-height: 80vh;
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-ascii {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Syntax Highlighting
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.hljs-keyword { color: var(--gold); }
|
||||||
|
.hljs-type { color: #82aaff; }
|
||||||
|
.hljs-string { color: #c3e88d; }
|
||||||
|
.hljs-number { color: #f78c6c; }
|
||||||
|
.hljs-comment { color: var(--text-muted); font-style: italic; }
|
||||||
|
.hljs-function { color: var(--gold-light); }
|
||||||
|
.hljs-effect { color: var(--gold-light); font-weight: 600; }
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Animations
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero > * {
|
||||||
|
animation: fadeIn 0.8s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero > *:nth-child(1) { animation-delay: 0.1s; }
|
||||||
|
.hero > *:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.hero > *:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
.hero > *:nth-child(4) { animation-delay: 0.4s; }
|
||||||
227
website/lux-site/src/components.lux
Normal file
227
website/lux-site/src/components.lux
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// Website Components
|
||||||
|
// Reusable UI components for the Lux website
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Navigation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn navLink(label: String, url: String): Html<Msg> =
|
||||||
|
a([class("nav-link"), href(url)], [text(label)])
|
||||||
|
|
||||||
|
fn navigation(): Html<Msg> =
|
||||||
|
nav([class("nav")], [
|
||||||
|
a([class("nav-logo"), href("/")], [text("LUX")]),
|
||||||
|
div([class("nav-links")], [
|
||||||
|
navLink("Learn", "/learn/"),
|
||||||
|
navLink("Docs", "/docs/"),
|
||||||
|
navLink("Playground", "/playground/"),
|
||||||
|
navLink("Community", "/community/")
|
||||||
|
]),
|
||||||
|
a([class("nav-github"), href("https://github.com/luxlang/lux")], [
|
||||||
|
text("GitHub")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Hero Section
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn hero(): Html<Msg> =
|
||||||
|
section([class("hero")], [
|
||||||
|
div([class("hero-logo")], [
|
||||||
|
pre([class("logo-ascii")], [text("╦ ╦ ╦╦ ╦\n║ ║ ║╔╣\n╩═╝╚═╝╩ ╩")])
|
||||||
|
]),
|
||||||
|
h1([class("hero-title")], [
|
||||||
|
text("Functional Programming"),
|
||||||
|
br(),
|
||||||
|
text("with First-Class Effects")
|
||||||
|
]),
|
||||||
|
p([class("hero-tagline")], [
|
||||||
|
text("Effects are explicit. Types are powerful. Performance is native.")
|
||||||
|
]),
|
||||||
|
div([class("hero-cta")], [
|
||||||
|
a([class("btn btn-primary"), href("/learn/getting-started/")], [
|
||||||
|
text("Get Started")
|
||||||
|
]),
|
||||||
|
a([class("btn btn-secondary"), href("/playground/")], [
|
||||||
|
text("Playground")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Code Demo Section
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn codeDemo(): Html<Msg> =
|
||||||
|
section([class("code-demo")], [
|
||||||
|
div([class("container")], [
|
||||||
|
div([class("code-demo-grid")], [
|
||||||
|
div([class("code-block")], [
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"fn processOrder(
|
||||||
|
order: Order
|
||||||
|
): Receipt
|
||||||
|
with {Database, Email} =
|
||||||
|
{
|
||||||
|
let saved = Database.save(order)
|
||||||
|
Email.send(
|
||||||
|
order.customer,
|
||||||
|
\"Order confirmed!\"
|
||||||
|
)
|
||||||
|
Receipt(saved.id)
|
||||||
|
}"
|
||||||
|
)])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
div([class("code-explanation")], [
|
||||||
|
h3([], [text("The type signature tells you everything")]),
|
||||||
|
ul([], [
|
||||||
|
li([], [text("Queries the database")]),
|
||||||
|
li([], [text("Sends an email")]),
|
||||||
|
li([], [text("Returns a Receipt")])
|
||||||
|
]),
|
||||||
|
p([class("highlight")], [
|
||||||
|
text("No surprises. No hidden side effects.")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Value Props Section
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn valueProp(title: String, description: String): Html<Msg> =
|
||||||
|
div([class("value-prop card")], [
|
||||||
|
h3([class("value-prop-title")], [text(title)]),
|
||||||
|
p([class("value-prop-desc")], [text(description)])
|
||||||
|
])
|
||||||
|
|
||||||
|
fn valueProps(): Html<Msg> =
|
||||||
|
section([class("value-props")], [
|
||||||
|
div([class("container")], [
|
||||||
|
div([class("value-props-grid")], [
|
||||||
|
valueProp(
|
||||||
|
"EFFECTS",
|
||||||
|
"Side effects are tracked in the type signature. Know exactly what every function does."
|
||||||
|
),
|
||||||
|
valueProp(
|
||||||
|
"TYPES",
|
||||||
|
"Full type inference with algebraic data types. Catch bugs at compile time."
|
||||||
|
),
|
||||||
|
valueProp(
|
||||||
|
"PERFORMANCE",
|
||||||
|
"Compiles to native C via gcc. Matches C performance, beats Rust and Zig."
|
||||||
|
)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Benchmarks Section
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn benchmarkBar(lang: String, time: String, width: Int): Html<Msg> =
|
||||||
|
div([class("benchmark-row")], [
|
||||||
|
span([class("benchmark-lang")], [text(lang)]),
|
||||||
|
div([class("benchmark-bar-container")], [
|
||||||
|
div([class("benchmark-bar"), style("width", toString(width) + "%")], [])
|
||||||
|
]),
|
||||||
|
span([class("benchmark-time")], [text(time)])
|
||||||
|
])
|
||||||
|
|
||||||
|
fn benchmarks(): Html<Msg> =
|
||||||
|
section([class("benchmarks")], [
|
||||||
|
div([class("container")], [
|
||||||
|
h2([], [text("Performance")]),
|
||||||
|
p([class("section-subtitle")], [
|
||||||
|
text("fib(35) benchmark — verified with hyperfine")
|
||||||
|
]),
|
||||||
|
div([class("benchmarks-chart")], [
|
||||||
|
benchmarkBar("Lux", "28.1ms", 100),
|
||||||
|
benchmarkBar("C", "29.0ms", 97),
|
||||||
|
benchmarkBar("Rust", "41.2ms", 68),
|
||||||
|
benchmarkBar("Zig", "47.0ms", 60)
|
||||||
|
]),
|
||||||
|
p([class("benchmarks-note")], [
|
||||||
|
a([href("/benchmarks/")], [text("See full methodology →")])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Quick Start Section
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn quickStart(): Html<Msg> =
|
||||||
|
section([class("quick-start")], [
|
||||||
|
div([class("container")], [
|
||||||
|
h2([], [text("Get Started")]),
|
||||||
|
div([class("code-block")], [
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"# Install via Nix
|
||||||
|
nix run github:luxlang/lux
|
||||||
|
|
||||||
|
# Or build from source
|
||||||
|
git clone https://github.com/luxlang/lux
|
||||||
|
cd lux && nix develop
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Start the REPL
|
||||||
|
./target/release/lux"
|
||||||
|
)])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
a([class("btn btn-primary"), href("/learn/getting-started/")], [
|
||||||
|
text("Full Installation Guide →")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Footer
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn footerColumn(title: String, links: List<(String, String)>): Html<Msg> =
|
||||||
|
div([class("footer-column")], [
|
||||||
|
h4([], [text(title)]),
|
||||||
|
ul([], List.map(links, fn((label, url)) =>
|
||||||
|
li([], [a([href(url)], [text(label)])])
|
||||||
|
))
|
||||||
|
])
|
||||||
|
|
||||||
|
fn footer(): Html<Msg> =
|
||||||
|
footer([class("footer")], [
|
||||||
|
div([class("container")], [
|
||||||
|
div([class("footer-grid")], [
|
||||||
|
div([class("footer-brand")], [
|
||||||
|
span([class("footer-logo")], [text("LUX")]),
|
||||||
|
p([], [text("Functional programming with first-class effects.")])
|
||||||
|
]),
|
||||||
|
footerColumn("Learn", [
|
||||||
|
("Getting Started", "/learn/getting-started/"),
|
||||||
|
("Tutorial", "/learn/tutorial/"),
|
||||||
|
("Examples", "/learn/examples/"),
|
||||||
|
("Reference", "/docs/")
|
||||||
|
]),
|
||||||
|
footerColumn("Community", [
|
||||||
|
("Discord", "https://discord.gg/lux"),
|
||||||
|
("GitHub", "https://github.com/luxlang/lux"),
|
||||||
|
("Contributing", "/community/contributing/"),
|
||||||
|
("Code of Conduct", "/community/code-of-conduct/")
|
||||||
|
]),
|
||||||
|
footerColumn("About", [
|
||||||
|
("Benchmarks", "/benchmarks/"),
|
||||||
|
("Blog", "/blog/"),
|
||||||
|
("License", "https://github.com/luxlang/lux/blob/main/LICENSE")
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
div([class("footer-bottom")], [
|
||||||
|
p([], [text("© 2026 Lux Language")])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
239
website/lux-site/src/generate.lux
Normal file
239
website/lux-site/src/generate.lux
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// Static Site Generator for Lux Website
|
||||||
|
//
|
||||||
|
// This module generates the static HTML files for the Lux website.
|
||||||
|
// Run with: lux website/lux-site/src/generate.lux
|
||||||
|
|
||||||
|
import html
|
||||||
|
import components
|
||||||
|
import pages
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Site Generation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn generateSite(): Unit with {FileSystem, Console} = {
|
||||||
|
Console.print("Generating Lux website...")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Create output directories
|
||||||
|
FileSystem.mkdir("website/lux-site/dist")
|
||||||
|
FileSystem.mkdir("website/lux-site/dist/learn")
|
||||||
|
FileSystem.mkdir("website/lux-site/dist/docs")
|
||||||
|
FileSystem.mkdir("website/lux-site/dist/static")
|
||||||
|
|
||||||
|
// Generate landing page
|
||||||
|
Console.print(" Generating index.html...")
|
||||||
|
let index = pages.landingPage()
|
||||||
|
let indexHtml = html.document(
|
||||||
|
"Lux - Functional Programming with First-Class Effects",
|
||||||
|
pages.pageHead("Lux"),
|
||||||
|
[index]
|
||||||
|
)
|
||||||
|
FileSystem.write("website/lux-site/dist/index.html", indexHtml)
|
||||||
|
|
||||||
|
// Generate documentation pages
|
||||||
|
Console.print(" Generating documentation...")
|
||||||
|
List.forEach(docPages(), fn(page) = {
|
||||||
|
let pageHtml = html.document(
|
||||||
|
page.title + " - Lux Documentation",
|
||||||
|
pages.pageHead(page.title),
|
||||||
|
[pages.docPageLayout(page)]
|
||||||
|
)
|
||||||
|
FileSystem.write("website/lux-site/dist/docs/" + page.slug + ".html", pageHtml)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Copy static assets
|
||||||
|
Console.print(" Copying static assets...")
|
||||||
|
FileSystem.copy("website/lux-site/static/style.css", "website/lux-site/dist/static/style.css")
|
||||||
|
|
||||||
|
Console.print("")
|
||||||
|
Console.print("Site generated: website/lux-site/dist/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Documentation pages to generate
|
||||||
|
fn docPages(): List<DocPage> = [
|
||||||
|
{
|
||||||
|
title: "Effects",
|
||||||
|
slug: "effects",
|
||||||
|
content: effectsDoc()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Types",
|
||||||
|
slug: "types",
|
||||||
|
content: typesDoc()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Syntax",
|
||||||
|
slug: "syntax",
|
||||||
|
content: syntaxDoc()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Documentation Content
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn effectsDoc(): Html<Msg> =
|
||||||
|
div([], [
|
||||||
|
p([], [text(
|
||||||
|
"Effects are Lux's defining feature. They make side effects explicit in function signatures, so you always know exactly what a function does."
|
||||||
|
)]),
|
||||||
|
|
||||||
|
h2([], [text("Declaring Effects")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"fn greet(name: String): String with {Console} = {
|
||||||
|
Console.print(\"Hello, \" + name)
|
||||||
|
\"greeted \" + name
|
||||||
|
}"
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
p([], [text(
|
||||||
|
"The `with {Console}` clause tells the compiler this function performs console I/O. Anyone calling this function will see this requirement in the type signature."
|
||||||
|
)]),
|
||||||
|
|
||||||
|
h2([], [text("Multiple Effects")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"fn processOrder(order: Order): Receipt
|
||||||
|
with {Database, Email, Logger} = {
|
||||||
|
Logger.info(\"Processing order: \" + order.id)
|
||||||
|
let saved = Database.save(order)
|
||||||
|
Email.send(order.customer, \"Order confirmed!\")
|
||||||
|
Receipt(saved.id)
|
||||||
|
}"
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
p([], [text(
|
||||||
|
"Functions can declare multiple effects. The caller must provide handlers for all of them."
|
||||||
|
)]),
|
||||||
|
|
||||||
|
h2([], [text("Handling Effects")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"// Production - real implementations
|
||||||
|
run processOrder(order) with {
|
||||||
|
Database -> postgresDb,
|
||||||
|
Email -> smtpServer,
|
||||||
|
Logger -> fileLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing - mock implementations
|
||||||
|
run processOrder(order) with {
|
||||||
|
Database -> inMemoryDb,
|
||||||
|
Email -> collectEmails,
|
||||||
|
Logger -> noopLogger
|
||||||
|
}"
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
p([], [text(
|
||||||
|
"The same code runs with different effect handlers. This makes testing trivial - no mocking frameworks required."
|
||||||
|
)])
|
||||||
|
])
|
||||||
|
|
||||||
|
fn typesDoc(): Html<Msg> =
|
||||||
|
div([], [
|
||||||
|
p([], [text(
|
||||||
|
"Lux has a powerful type system with full type inference. You rarely need to write type annotations, but they're there when you want them."
|
||||||
|
)]),
|
||||||
|
|
||||||
|
h2([], [text("Basic Types")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"let x: Int = 42
|
||||||
|
let name: String = \"Lux\"
|
||||||
|
let active: Bool = true
|
||||||
|
let ratio: Float = 3.14"
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
|
||||||
|
h2([], [text("Algebraic Data Types")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"type Option<T> =
|
||||||
|
| Some(T)
|
||||||
|
| None
|
||||||
|
|
||||||
|
type Result<T, E> =
|
||||||
|
| Ok(T)
|
||||||
|
| Err(E)
|
||||||
|
|
||||||
|
type Shape =
|
||||||
|
| Circle(Float)
|
||||||
|
| Rectangle(Float, Float)
|
||||||
|
| Triangle(Float, Float, Float)"
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
|
||||||
|
h2([], [text("Pattern Matching")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"fn area(shape: Shape): Float =
|
||||||
|
match shape {
|
||||||
|
Circle(r) => 3.14159 * r * r,
|
||||||
|
Rectangle(w, h) => w * h,
|
||||||
|
Triangle(a, b, c) => {
|
||||||
|
let s = (a + b + c) / 2.0
|
||||||
|
sqrt(s * (s - a) * (s - b) * (s - c))
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
)])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
fn syntaxDoc(): Html<Msg> =
|
||||||
|
div([], [
|
||||||
|
p([], [text(
|
||||||
|
"Lux syntax is clean and expression-oriented. Everything is an expression that returns a value."
|
||||||
|
)]),
|
||||||
|
|
||||||
|
h2([], [text("Functions")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"// Named function
|
||||||
|
fn add(a: Int, b: Int): Int = a + b
|
||||||
|
|
||||||
|
// Anonymous function (lambda)
|
||||||
|
let double = fn(x) => x * 2
|
||||||
|
|
||||||
|
// Function with block body
|
||||||
|
fn greet(name: String): String = {
|
||||||
|
let greeting = \"Hello, \"
|
||||||
|
greeting + name + \"!\"
|
||||||
|
}"
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
|
||||||
|
h2([], [text("Let Bindings")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"let x = 42
|
||||||
|
let name = \"world\"
|
||||||
|
let result = add(1, 2)"
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
|
||||||
|
h2([], [text("Conditionals")]),
|
||||||
|
pre([class("code")], [
|
||||||
|
code([], [text(
|
||||||
|
"let result = if x > 0 then \"positive\" else \"non-positive\"
|
||||||
|
|
||||||
|
// Multi-branch
|
||||||
|
let grade =
|
||||||
|
if score >= 90 then \"A\"
|
||||||
|
else if score >= 80 then \"B\"
|
||||||
|
else if score >= 70 then \"C\"
|
||||||
|
else \"F\""
|
||||||
|
)])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn main(): Unit with {FileSystem, Console} = {
|
||||||
|
generateSite()
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
117
website/lux-site/src/pages.lux
Normal file
117
website/lux-site/src/pages.lux
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// Page Templates
|
||||||
|
// Full page layouts for the Lux website
|
||||||
|
|
||||||
|
import html
|
||||||
|
import components
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Landing Page
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn landingPage(): Html<Msg> =
|
||||||
|
div([class("page")], [
|
||||||
|
components.navigation(),
|
||||||
|
main([], [
|
||||||
|
components.hero(),
|
||||||
|
components.codeDemo(),
|
||||||
|
components.valueProps(),
|
||||||
|
components.benchmarks(),
|
||||||
|
components.quickStart()
|
||||||
|
]),
|
||||||
|
components.footer()
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Documentation Page Layout
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
type DocPage = {
|
||||||
|
title: String,
|
||||||
|
slug: String,
|
||||||
|
content: Html<Msg>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn docSidebar(currentSlug: String): Html<Msg> =
|
||||||
|
aside([class("doc-sidebar")], [
|
||||||
|
div([class("doc-sidebar-section")], [
|
||||||
|
h4([], [text("LANGUAGE")]),
|
||||||
|
ul([], [
|
||||||
|
docNavItem("Syntax", "syntax", currentSlug),
|
||||||
|
docNavItem("Types", "types", currentSlug),
|
||||||
|
docNavItem("Effects", "effects", currentSlug),
|
||||||
|
docNavItem("Pattern Matching", "patterns", currentSlug),
|
||||||
|
docNavItem("Modules", "modules", currentSlug)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
div([class("doc-sidebar-section")], [
|
||||||
|
h4([], [text("STANDARD LIBRARY")]),
|
||||||
|
ul([], [
|
||||||
|
docNavItem("List", "list", currentSlug),
|
||||||
|
docNavItem("String", "string", currentSlug),
|
||||||
|
docNavItem("Option", "option", currentSlug),
|
||||||
|
docNavItem("Result", "result", currentSlug)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
div([class("doc-sidebar-section")], [
|
||||||
|
h4([], [text("EFFECTS")]),
|
||||||
|
ul([], [
|
||||||
|
docNavItem("Console", "console", currentSlug),
|
||||||
|
docNavItem("Http", "http", currentSlug),
|
||||||
|
docNavItem("FileSystem", "filesystem", currentSlug)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
div([class("doc-sidebar-section")], [
|
||||||
|
h4([], [text("TOOLING")]),
|
||||||
|
ul([], [
|
||||||
|
docNavItem("CLI", "cli", currentSlug),
|
||||||
|
docNavItem("LSP", "lsp", currentSlug),
|
||||||
|
docNavItem("Editors", "editors", currentSlug)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
fn docNavItem(label: String, slug: String, currentSlug: String): Html<Msg> = {
|
||||||
|
let activeClass = if slug == currentSlug then "active" else ""
|
||||||
|
li([], [
|
||||||
|
a([class("doc-nav-link " + activeClass), href("/docs/" + slug + "/")], [
|
||||||
|
text(label)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn docPageLayout(page: DocPage): Html<Msg> =
|
||||||
|
div([class("page doc-page")], [
|
||||||
|
components.navigation(),
|
||||||
|
div([class("doc-layout")], [
|
||||||
|
docSidebar(page.slug),
|
||||||
|
main([class("doc-content")], [
|
||||||
|
h1([], [text(page.title)]),
|
||||||
|
page.content
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
components.footer()
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Learn Page Layout
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn learnPageLayout(title: String, content: Html<Msg>): Html<Msg> =
|
||||||
|
div([class("page learn-page")], [
|
||||||
|
components.navigation(),
|
||||||
|
main([class("learn-content container")], [
|
||||||
|
h1([], [text(title)]),
|
||||||
|
content
|
||||||
|
]),
|
||||||
|
components.footer()
|
||||||
|
])
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Page Head Elements
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn pageHead(title: String): List<Html<Msg>> = [
|
||||||
|
Element("meta", [Name("description"), Value("Lux - Functional programming with first-class effects")], []),
|
||||||
|
Element("link", [Href("/static/style.css"), DataAttr("rel", "stylesheet")], []),
|
||||||
|
Element("link", [Href("https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Source+Serif+Pro:wght@400;600&family=JetBrains+Mono:wght@400;500&display=swap"), DataAttr("rel", "stylesheet")], [])
|
||||||
|
]
|
||||||
707
website/lux-site/static/style.css
Normal file
707
website/lux-site/static/style.css
Normal file
@@ -0,0 +1,707 @@
|
|||||||
|
/* ============================================================================
|
||||||
|
Lux Website - Sleek and Noble
|
||||||
|
Translucent black, white, and gold with strong serif typography
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* CSS Variables */
|
||||||
|
:root {
|
||||||
|
/* Backgrounds */
|
||||||
|
--bg-primary: #0a0a0a;
|
||||||
|
--bg-secondary: #111111;
|
||||||
|
--bg-glass: rgba(255, 255, 255, 0.03);
|
||||||
|
--bg-glass-hover: rgba(255, 255, 255, 0.06);
|
||||||
|
--bg-code: rgba(212, 175, 55, 0.05);
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||||
|
--text-muted: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
|
/* Gold accents */
|
||||||
|
--gold: #d4af37;
|
||||||
|
--gold-light: #f4d03f;
|
||||||
|
--gold-dark: #b8860b;
|
||||||
|
--gold-glow: rgba(212, 175, 55, 0.3);
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--border-subtle: rgba(255, 255, 255, 0.1);
|
||||||
|
--border-gold: rgba(212, 175, 55, 0.3);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-heading: "Playfair Display", Georgia, serif;
|
||||||
|
--font-body: "Source Serif Pro", Georgia, serif;
|
||||||
|
--font-code: "JetBrains Mono", "Fira Code", monospace;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--container-width: 1200px;
|
||||||
|
--section-padding: 6rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.7;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gold-light);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: clamp(2.5rem, 5vw, 4rem); }
|
||||||
|
h2 { font-size: clamp(2rem, 4vw, 3rem); }
|
||||||
|
h3 { font-size: clamp(1.5rem, 3vw, 2rem); }
|
||||||
|
h4 { font-size: 1.25rem; }
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--gold);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--gold-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container {
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Navigation
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: rgba(10, 10, 10, 0.9);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--gold);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-github {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-github:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
border-color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Hero Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
min-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at top, rgba(212, 175, 55, 0.08) 0%, transparent 50%),
|
||||||
|
var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-ascii {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--gold);
|
||||||
|
line-height: 1.2;
|
||||||
|
text-shadow: 0 0 30px var(--gold-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Buttons
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--gold-dark), var(--gold));
|
||||||
|
color: #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: linear-gradient(135deg, var(--gold), var(--gold-light));
|
||||||
|
color: #0a0a0a;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 20px var(--gold-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--gold);
|
||||||
|
border: 1px solid var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
color: var(--gold-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Code Demo Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.code-demo {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-demo-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: var(--bg-code);
|
||||||
|
border: 1px solid var(--border-gold);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code code {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation h3 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation ul {
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation li {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation li::before {
|
||||||
|
content: "•";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-explanation .highlight {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--gold-light);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Value Props Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.value-props {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-props-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-prop {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-prop-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-prop-desc {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Cards
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--bg-glass);
|
||||||
|
border: 1px solid rgba(212, 175, 55, 0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
background: var(--bg-glass-hover);
|
||||||
|
border-color: rgba(212, 175, 55, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Benchmarks Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.benchmarks {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-chart {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 60px 1fr 80px;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-lang {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-bar-container {
|
||||||
|
height: 24px;
|
||||||
|
background: var(--bg-glass);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--gold-dark), var(--gold));
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmark-time {
|
||||||
|
font-family: var(--font-code);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--gold-light);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-note {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-note a {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benchmarks-note a:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Testing Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.testing {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.testing h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testing .code-demo-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.testing .code-demo-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Quick Start Section
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.quick-start {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-start h2 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-start .code-block {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Footer
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 4rem 2rem 2rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr 1fr 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-logo {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--gold);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column h4 {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column a {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-column a:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bottom {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-bottom p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Documentation Layout
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.doc-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 250px 1fr;
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
height: calc(100vh - 80px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-right: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar-section h4 {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar-section ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-link {
|
||||||
|
display: block;
|
||||||
|
padding: 0.4rem 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-link:hover {
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-link.active {
|
||||||
|
color: var(--gold-light);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content {
|
||||||
|
padding: 3rem;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h1 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Responsive Design
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.code-demo-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-props-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-sidebar {
|
||||||
|
position: static;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
min-height: 80vh;
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-ascii {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Syntax Highlighting
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.hljs-keyword { color: var(--gold); }
|
||||||
|
.hljs-type { color: #82aaff; }
|
||||||
|
.hljs-string { color: #c3e88d; }
|
||||||
|
.hljs-number { color: #f78c6c; }
|
||||||
|
.hljs-comment { color: var(--text-muted); font-style: italic; }
|
||||||
|
.hljs-function { color: var(--gold-light); }
|
||||||
|
.hljs-effect { color: var(--gold-light); font-weight: 600; }
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Animations
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero > * {
|
||||||
|
animation: fadeIn 0.8s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero > *:nth-child(1) { animation-delay: 0.1s; }
|
||||||
|
.hero > *:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.hero > *:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
.hero > *:nth-child(4) { animation-delay: 0.4s; }
|
||||||
25
website/lux-site/test_html.lux
Normal file
25
website/lux-site/test_html.lux
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Test the HTML module rendering capabilities
|
||||||
|
|
||||||
|
// Import from stdlib
|
||||||
|
// Note: Documenting Lux weakness - no proper module import system visible to user
|
||||||
|
|
||||||
|
// Simple HTML test without relying on complex module imports
|
||||||
|
fn main(): Unit with {Console} = {
|
||||||
|
Console.print("Testing basic Lux functionality for website generation")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Test string concatenation
|
||||||
|
let tag = "div"
|
||||||
|
let content = "Hello, World!"
|
||||||
|
let html = "<" + tag + ">" + content + "</" + tag + ">"
|
||||||
|
|
||||||
|
Console.print("Generated HTML: " + html)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Test conditional
|
||||||
|
let isActive = true
|
||||||
|
let className = if isActive then "active" else "inactive"
|
||||||
|
Console.print("Class name: " + className)
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
Reference in New Issue
Block a user