diff --git a/.gitignore b/.gitignore index 4387ac0..e286f95 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ /build /.deps -/.claude/settings.local.json -/.claude.local/ +/.claude/ + + +/.brain/ diff --git a/Cargo.lock b/Cargo.lock index ff90c81..4993ad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9698bf0769c641b18618039fe2ebd41eb3541f98433000f64e663fab7cea2c87" +checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a" dependencies = [ "gimli 0.33.0", ] @@ -84,9 +84,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitmaps" @@ -139,9 +139,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -229,27 +229,27 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f248321c6a7d4de5dcf2939368e96a397ad3f53b6a076e38d0104d1da326d37" +checksum = "046d4b584c3bb9b5eb500c8f29549bec36be11000f1ba2a927cef3d1a9875691" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6d78ff1f7d9bf8b7e1afbedbf78ba49e38e9da479d4c8a2db094e22f64e2bc" +checksum = "b9b194a7870becb1490366fc0ae392ccd188065ff35f8391e77ac659db6fb977" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b6005ba640213a5b95382aeaf6b82bf028309581c8d7349778d66f27dc1180b" +checksum = "bb6a4ab44c6b371e661846b97dab687387a60ac4e2f864e2d4257284aad9e889" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fb5b134a12b559ff0c0f5af0fcd755ad380723b5016c4e0d36f74d39485340" +checksum = "b8b7a44150c2f471a94023482bda1902710746e4bed9f9973d60c5a94319b06d" dependencies = [ "serde", "serde_derive", @@ -268,9 +268,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85837de8be7f17a4034a6b08816f05a3144345d2091937b39d415990daca28f4" +checksum = "01b06598133b1dd76758b8b95f8d6747c124124aade50cea96a3d88b962da9fa" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -296,9 +296,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e433faa87d38e5b8ff469e44a26fea4f93e58abd7a7c10bad9810056139700c9" +checksum = "6190e2e7bcf0a678da2f715363d34ed530fedf7a2f0ab75edaefef72a70465ff" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -309,24 +309,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5397ba61976e13944ca71230775db13ee1cb62849701ed35b753f4761ed0a9b7" +checksum = "f583cf203d1aa8b79560e3b01f929bdacf9070b015eec4ea9c46e22a3f83e4a0" [[package]] name = "cranelift-control" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc81c88765580720eb30f4fc2c1bfdb75fcbf3094f87b3cd69cecca79d77a245" +checksum = "803159df35cc398ae54473c150b16d6c77e92ab2948be638488de126a3328fbc" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463feed5d46cf8763f3ba3045284cf706dd161496e20ec9c14afbb4ba09b9e66" +checksum = "3109e417257082d88087f5bcce677525bdaa8322b88dd7f175ed1a1fd41d546c" dependencies = [ "cranelift-bitset", "serde", @@ -336,9 +336,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c5eca7696c1c04ab4c7ed8d18eadbb47d6cc9f14ec86fe0881bf1d7e97e261" +checksum = "14db6b0e0e4994c581092df78d837be2072578f7cb2528f96a6cf895e56dee63" dependencies = [ "cranelift-codegen", "log", @@ -348,15 +348,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1153844610cc9c6da8cf10ce205e45da1a585b7688ed558aa808bbe2e4e6d77" +checksum = "ec66ea5025c7317383699778282ac98741d68444f956e3b1d7b62f12b7216e67" [[package]] name = "cranelift-native" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97b583fe9a60f06b0464cee6be5a17f623fd91b217aaac99b51b339d19911af" +checksum = "373ade56438e6232619d85678477d0a88a31b3581936e0503e61e96b546b0800" dependencies = [ "cranelift-codegen", "libc", @@ -365,9 +365,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.130.0" +version = "0.130.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8594dc6bb4860fa8292f1814c76459dbfb933e1978d8222de6380efce45c7cee" +checksum = "ef53619d3cd5c78fd998c6d9420547af26b72e6456f94c2a8a2334cb76b42baa" [[package]] name = "crc32fast" @@ -521,9 +521,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fdeflate" @@ -737,9 +737,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ "fallible-iterator", "indexmap", @@ -802,6 +802,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.5.0" @@ -898,12 +904,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -955,9 +961,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", @@ -977,9 +983,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -989,9 +995,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "libc", ] @@ -1193,9 +1199,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "png" @@ -1271,9 +1277,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" [[package]] name = "pulley-interpreter" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7975f0975fa2c047bf47d617bdf716689e42ee82b159bd000ead7330d7697a1b" +checksum = "010dec3755eb61b2f1051ecb3611b718460b7a74c131e474de2af20a845938af" dependencies = [ "cranelift-bitset", "log", @@ -1283,9 +1289,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210c0386ef0ddedb337ec99b91e560ae9c341415ef75958cb39ddb537bb0c84" +checksum = "ad360c32e85ca4b083ac0e2b6856e8f11c3d5060dafa7d5dc57b370857fa3018" dependencies = [ "proc-macro2", "quote", @@ -1294,9 +1300,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" [[package]] name = "quote" @@ -1321,9 +1327,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha", @@ -1360,9 +1366,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -1391,13 +1397,13 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "952ddbfc6f9f64d006c3efd8c9851a6ba2f2b944ba94730db255d55006e0ffda" +checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" dependencies = [ "allocator-api2", "bumpalo", - "hashbrown 0.15.5", + "hashbrown 0.17.0", "log", "rustc-hash", "smallvec", @@ -1440,9 +1446,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustix" @@ -1480,9 +1486,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -1533,9 +1539,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -1572,9 +1578,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "siphasher" @@ -1738,9 +1744,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "pin-project-lite", ] @@ -1771,24 +1777,24 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -1810,9 +1816,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -1834,9 +1840,9 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "js-sys", "wasm-bindgen", @@ -1866,11 +1872,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -1879,14 +1885,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -1897,9 +1903,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1907,9 +1913,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -1920,9 +1926,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -1968,6 +1974,16 @@ dependencies = [ "wasmparser 0.245.1", ] +[[package]] +name = "wasm-encoder" +version = "0.247.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b6733b8b91d010a6ac5b0fb237dc46a19650bc4c67db66857e2e787d437204" +dependencies = [ + "leb128fmt", + "wasmparser 0.247.0", +] + [[package]] name = "wasm-metadata" version = "0.244.0" @@ -2005,6 +2021,17 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmparser" +version = "0.247.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + [[package]] name = "wasmprinter" version = "0.245.1" @@ -2018,9 +2045,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54fa9f298901a64ed3eae16b130f0b30c80dbb74a9e7f129a791f4e74649b917" +checksum = "ce205cd643d661b5ba5ba4717e13730262e8cdbc8f2eacbc7b906d45c1a74026" dependencies = [ "addr2line", "async-trait", @@ -2071,9 +2098,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a3aaaa3a522f443af67a7ed8d4efa20b0c3784e1031980537fbfcb497f70a7" +checksum = "0b8b78abf3677d4a0a5db82e5015b4d085ff3a1b8b472cbb8c70d4b769f019ce" dependencies = [ "anyhow", "cpp_demangle", @@ -2102,9 +2129,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cache" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0454f53d6c91d9a3b30be6d5dbd27e8ff595fddaafe69665df908fc385bbd836" +checksum = "8e4fd4103ba413c0da2e636f73490c6c8e446d708cbde7573703941bc3d6a448" dependencies = [ "base64", "directories-next", @@ -2122,9 +2149,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0d00d29ed90a63d2445072860a8a42d7151390157236a69bc3ae056786e9c9" +checksum = "0d3d6914f34be2f9d78d8ee9f422e834dfc204e71ccce697205fae95fed87892" dependencies = [ "anyhow", "proc-macro2", @@ -2137,15 +2164,15 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acfd639ca7ab9e1cc37f053edd95bed6a7bed16370a8b2643dc7d9ef3047935" +checksum = "3751b0616b914fdd87fe1bf804694a078f321b000338e6476bc48a4d6e454f21" [[package]] name = "wasmtime-internal-core" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e671917bb6856ae360cb59d7aaf26f1cfd042c7b924319dd06fd380739fc0b2e" +checksum = "22632b187e1b0716f1b9ac57ad29013bed33175fcb19e10bb6896126f82fac67" dependencies = [ "anyhow", "hashbrown 0.16.1", @@ -2155,9 +2182,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cranelift" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dfd752e1dcf79eeeadc6f2681e2fb4a9f0b5534d18c5b9b93faccd0de2c80c" +checksum = "8b3ca07b3e0bb3429674b173b5800577719d600774dd81bff58f775c0aaa64ee" dependencies = [ "cfg-if", "cranelift-codegen", @@ -2182,9 +2209,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e9171af643316c11d6ebe52f31f6e2a5d6d1d270de9167a7b7b6f0e3f72982" +checksum = "20c8b2c9704eb1f33ead025ec16038277ccb63d0a14c31e99d5b765d7c36da55" dependencies = [ "cc", "cfg-if", @@ -2197,9 +2224,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe23134536b9883ffc2afcffae23f7ffbcb1791e2d9fac6d6464a37ea4c8fdd" +checksum = "d950310d07391d34369f62c48336ebb14eacbd4d6f772bb5f349c24e838e0664" dependencies = [ "cc", "object", @@ -2209,9 +2236,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3112806515fac8495883885eb8dbdde849988ae91fe6beb544c0d7c0f4c9aa" +checksum = "3606662c156962d096be3127b8b8ae8ee2f8be3f896dad29259ff01ddb64abfd" dependencies = [ "cfg-if", "libc", @@ -2221,9 +2248,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dafc29c6e538273fda8409335137654751bdf24beab65702b7866b0a85ee108a" +checksum = "75eef0747e52dc545b075f64fd0e0cc237ae738e641266b1970e07e2d744bc32" dependencies = [ "cfg-if", "cranelift-codegen", @@ -2234,9 +2261,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772f2b105b7fdd3dfb2cdf70c297baaeb96fe76a95cdc6fa516f713f04090c73" +checksum = "d8b0a5dab02a8fb527f547855ecc0e05f9fdc3d5bd57b8b080349408f9a6cece" dependencies = [ "proc-macro2", "quote", @@ -2245,9 +2272,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-winch" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556c3b176aba3cce565b2bafcdc049e7410ac1d86bf1ef663a035d9ded0dddc" +checksum = "8007342bd12ff400293a817973f7ecd6f1d9a8549a53369a9c1af357166f1f1e" dependencies = [ "cranelift-codegen", "gimli 0.33.0", @@ -2262,9 +2289,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c47507f09e68462a0ed9f351ef410584a52e01d7ec92bc588bf7fa597ce528ef" +checksum = "7900c3e3c1d6e475bc225d73b02d6d5484815f260022e6964dca9558e50dd01a" dependencies = [ "anyhow", "bitflags", @@ -2275,23 +2302,23 @@ dependencies = [ [[package]] name = "wast" -version = "245.0.1" +version = "247.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" +checksum = "579d2d47eb33b0cdf9b14723cb115f1e1b7d6e77aac6f0816e5b7c7aeaa418ff" dependencies = [ "bumpalo", - "gimli 0.31.1", + "gimli 0.32.3", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.245.1", + "wasm-encoder 0.247.0", ] [[package]] name = "wat" -version = "1.245.1" +version = "1.247.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" +checksum = "f3f4091c56437e86f2b57fa2fac72c4f528957a605b3f44f7c0b3b19a17ac5ee" dependencies = [ "wast", ] @@ -2329,9 +2356,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "43.0.0" +version = "43.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3d76763e4ddc48ede73792d067396ba5ee74c3c581db90e6638fe6b46bf52" +checksum = "eb9f45f7172a2628c8317766e427babc0a400f9d10b1c0f0b0617c5ed5b79de6" dependencies = [ "cranelift-assembler-x64", "cranelift-codegen", @@ -2422,9 +2449,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" [[package]] name = "wit-bindgen" @@ -2435,6 +2462,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -2535,18 +2568,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/content/docs/debugging/index.md b/content/docs/debugging/index.md new file mode 100644 index 0000000..b1bd2fb --- /dev/null +++ b/content/docs/debugging/index.md @@ -0,0 +1,50 @@ +--- +title: Debugging +template: docs +--- + +# Debugging + +ƒink ships with a built-in debug adapter: `fink dap ` speaks the Debug Adapter Protocol on stdin/stdout, so any DAP client can drive a ƒink program. + +## VSCode + +The ƒink VSCode extension (linked from the [fink repo README](https://github.com/fink-lang/fink#readme)) registers `fink` as a debugger type. With the extension installed, a launch config looks like: + +```text +{ + "version": "0.2.0", + "configurations": [ + { + "type": "fink", + "request": "launch", + "name": "Debug ƒink", + "program": "${file}", + "stopOnEntry": false + } + ] +} +``` + +Save as `.vscode/launch.json` in the workspace. Set `stopOnEntry: true` to pause on the first expression of the program; leave it `false` to run from start to the first breakpoint. + +## What works + +- **Breakpoints.** Click in the gutter to set one. A breakpoint on a line with no executable ƒink expression comes back unverified (greyed out) — move it to the nearest line with a binding, call, or operator. +- **Continue.** Runs until the next breakpoint or program end. +- **Step in / over / out.** All three advance to the next executable ƒink expression. True step-over and step-out (skip-the-callee, run-to-return) are a known limitation — every ƒink call is a tail call, so there is no call stack for the debugger to walk. +- **Program output.** `write stdout, 'hello'` / `write stderr, ...` shows up in the editor's debug console. +- **Clean exit.** The debug session ends when `main` returns. + +## Running from the command line + +```bash +fink dap path/to/program.fnk +``` + +Reads DAP requests on stdin, writes events and responses on stdout. Useful if you're wiring ƒink into a different editor, or writing an integration test. + +## Known gaps + +- **Stdin inside the debugger isn't implemented.** A program that reads from its `stdin` channel while running under `fink dap` will trap. `fink run` works normally. +- **Panic messages carry no source location.** A runtime panic (e.g. an irrefutable pattern failing) traps with a generic message rather than pointing at the line. diff --git a/content/docs/execution-model/index.md b/content/docs/execution-model/index.md new file mode 100644 index 0000000..d083b45 --- /dev/null +++ b/content/docs/execution-model/index.md @@ -0,0 +1,171 @@ +--- +title: Execution Model +template: docs +--- + +# Execution Model + +How a ƒink module runs. + +## 1. ƒink is functional + +Values are first-class. Functions are pure expressions over values. Composition is application. + +## 2. Immutability follows + +If functions cannot mutate their inputs and values are first-class, there are no mutable cells. Lexical scope is an immutable map from names to values. Bindings do not overwrite; nested scopes shadow. + +Every time a ƒink module looks "dynamic" — mutual recursion, operator overloading, mocking in tests, the host handing stdio to a module at startup — something principled has to be happening. The mechanism is **effects**. + +## 3. Effects + +An **effect context** is a value threaded through execution, carrying whatever state changes meaning for downstream computation — host capabilities, user-context installations, scheduler state, mutual-rec scopes, lazy-state slots. At the source level it is **entirely invisible**: ƒink code never names it, never declares a parameter for it, never installs it. Code reads as ordinary applicative expressions. + +The CPS lowering makes it **entirely explicit**: every effectful function's actual signature takes a context argument and its continuation takes a potentially-new context. `read_file "foo.txt"` at source is really `read_file("foo.txt", ctx, k)` at the impl level, with the continuation invoked as `k(bytes, ctx')`. An iterator whose impl needs state never exposes that state in source code — the iterator's state lives in the context, step operations produce a new context with the advanced state. Same mechanism for file positions, scheduler state, lazy thunks, anything. Consuming a context — reading from it, passing it onward unchanged — is pure. + +Registered protocol impls *conceptually* belong in the context, but operationally most of them never appear there. With static information the compiler resolves a protocol use to a direct call; with partial information it compiles to a closed set of statically-enumerated cases; with none it emits a call into a polymorphic dispatcher (like the ones in `operators.wat`). Each of these is a compiled-in dispatch, not a runtime lookup. The runtime context only carries what can't be baked in. + +Lexical scope (which names are bound where in the source) is a separate thing. Scope is a compile-time construct about name visibility; the effect context is a runtime value about what impls are registered, what the host has supplied, and similar state that can't be resolved statically. The two meet at mutual recursion: the one place lexical scope itself is effectful, because admitting forward refs requires producing a new context in which both names are resolvable. + +The effect is **producing a new context**. A computation that takes the current context and returns a new one (with an added impl, a host capability, ...) is an effect. Everything downstream that consumes the new context is pure again. + +The criterion, at the source level: + +> **A ƒink function is pure if its evaluation does not change the implicit context. It is effectful if it does.** + +Effects are the mechanism for anything context-dependent in ƒink. They are narrow — most of a module is pure; effects are the exception. + +CPS is a convenient lens for illustrating this: in a CPS-lowered program, you can *see* the shape — a pure step passes values onward; an effect step produces a new context that subsequent continuations consume. This compiler uses CPS. A compiler built on a different IR would state the criterion in its own terms, but the semantics are the same. + +Things that are effects: + +- Mutual recursion (forward references need a shared scope). +- Dynamic dispatch (resolution depends on run-time state). +- Impl registration (introducing a new resolution into scope). +- Host-provided capabilities (stdio, panic, scheduler yield — state entering from outside). +- Lazy evaluation (deferred state). + +Things that are pure: + +- Non-recursive binding. +- Eager import of a pure module. +- Statically-resolved application. +- Passing, returning, and constructing values. + +## 4. Everything dynamic is a user of effects + +Protocols, impls, and their registration are one user of the effects system. stdio is another. The scheduler is another. Mutual recursion is another. They are all the same mechanism applied at different levels. + +### 4.1 Protocols and impls + +- A **protocol** is a typed name. Declared as a pure value: `op_plus = type: Fn any, any`. Nothing special about it — it is a type. +- An **impl** satisfies a protocol for some pattern of types. Impls are registered into the current context by a pattern-match whose evaluation is an effect. + +The registration syntax is just ƒink's pattern-match-assignment. The same construct that destructures and binds (`[a, b] = some_list`) also registers impls when the pattern's LHS has no binding slots and the head is a type-guard: + +```fink +op_plus T1, T2 = fn a, b: ... +``` + +reads as a pattern match: `op_plus` is the type-guard, `T1, T2` are types being matched, nothing is destructured into, and the right-hand side is the impl to register for that type pattern. Evaluating this line is an effect: it registers the impl. + +### 4.2 Dispatch + +A protocol use (e.g. `a + b`) resolves against the impls in scope. The compiler uses whatever information it has: + +- **Fully known** — emit a direct call to the specific impl. Pure. +- **Narrowed to a small closed set** — emit a static switch over those candidates. Pure; the dispatch is compiled in, not looked up. +- **Unknown** — emit a call into a polymorphic dispatcher (like `op_plus` in `operators.wat`). The dispatcher inspects the value and routes. Still a compiled-in routine, not a runtime context lookup. + +None of these materialise a dictionary of impls in the runtime context. Dispatch happens via direct calls, static enumerations, or polymorphic dispatchers — all three are ordinary function calls, not context queries. + +### 4.3 Bindings, mutual recursion, imports + +- Non-recursive binding (`x = 5`) is pure — the name's value is fixed before anything references it. Ordinary lexical scoping. +- Mutual recursion (`ping = fn: pong() \n pong = fn: ping()`) is effectful — each name must be resolvable from the other's body before either body runs. This is the one case where lexical scope itself needs effect-context help: the construct produces a new context in which both names resolve. +- Eager import of a pure module is pure — same shape as binding a batch of names. +- Import of a module that registers impls or performs any other effect is effectful, inasmuch as its effects run at import time. +- Lazy import is effectful — deferred evaluation needs threaded state. + +### 4.4 Host capabilities + +stdio, panic, scheduler yield, and anything else a host provides are impls that the host registers into the module's root context before user code runs. Their presence is an effect — state enters from outside. User code consumes them through the same resolution mechanism as any other impl. + +## 5. Module lifecycle within a host + +A host doesn't "load and run" a compiled module. It **participates in populating the module's root context**. + +1. The host starts. +2. The host asks the module to initialise its root context with its own impls (arithmetic, containers, apply, args, ...). +3. The module returns a handle to that context. +4. The host registers its own impls into the context (stdio, panic, scheduler yield, ...). +5. The host asks the module to run against the populated context. +6. User code runs. Resolutions happen against the complete context. + +Steps 2 and 4 are effects: registrations into the module's root context. Step 5 is the module consuming the resulting context. + +A module doesn't declare a target host. It declares which protocols it uses (by using them) and which it implements (e.g. a `main` function). The linker against a specific host checks the host's contract covers the module's uses. A module using stdio implicitly expects a host that provides stdio impls. + +Different hosts provide different impls: the CLI provides OS-backed stdio and an OS-reactor scheduler; a browser provides console-backed stdio and a JS-event-loop scheduler; a library consumer provides neither and exposes public exports for the host to call directly. One lifecycle shape, many host realisations. + +## 6. Concept vs. implementation + +This document describes the concept. Implementation status is recorded in the source. + +Notable current gaps: + +- Type-guards in patterns are not yet implemented. Without them, the registration syntax in 4.1 cannot be written in ƒink source. The compiler hard-codes the currently-possible resolutions (operator dispatch on known types, container ops on known containers, etc.) in WAT instead of consulting a registry populated by ƒink-level registrations. The model is unchanged; the realisation is narrower than the model allows. +- The module-lifecycle handshake in section 5 is not staged in today's implementation. The host calls a single fixed entry; runtime and stdlib impls are wired in at link time rather than through host-driven registration. +- User-level contexts (section 7) are designed but syntax and semantics are not settled. Today the only handlers are compiler-internal (impl registration baked into the lowering, the scheduler, the host-root registration). + +Each implementation file documents its own deviation from the concept. For the compiler's backend realisation story — how pure vs. effectful computations lower to WASM, how scopes and registries are realised, where compile-time resolution happens — see [the compiler source](https://github.com/fink-lang/fink/tree/main/src/passes/wasm). + +## 7. Related work + +The mechanism described here is **algebraic effects**. At the concept level it's a type-level construct: a function's signature reflects whether its evaluation changes the implicit context, and each protocol its body may use. The current ƒink compiler realises the mechanism via a CPS calling convention with an implicit context argument, but that is an impl choice — a different compiler could lower it differently without changing the language model. + +The three defining pieces of algebraic effects are present: + +- **Operations.** Protocol uses (`a + b`, `log x`, `yield`) resolve against the current context. They are operations in the Koka/Frank/OCaml-5 sense — their meaning is given by what's registered, not by declaration-site semantics. +- **Handlers.** Any ƒink construct that produces a new context is a handler: impl registration, mutual-recursive binding, host capability registration, user-defined context blocks (see below), and the scheduler (which parks and resumes continuations at `yield`). +- **Resumable continuations.** At the concept level, an effectful call receives an explicit rest-of-computation that the handler can resume, run multiple times, or discard. The current compiler represents this directly via CPS: every call site has an explicit continuation value, the scheduler parks and resumes continuations, `yield` is a language-level suspend point. A non-CPS compiler would represent the same continuations differently. + +**User contexts** (a planned feature, design not yet settled) will make scoped handler installation source-visible — the only place context scoping shows up in ƒink source. The exact syntax and semantics are open; the sketch below is only to convey the shape, not to specify the feature: + +```fink +# sketch, not final syntax +foo = context fn ...: + ... + +spam = fn ...: + # does something that requires the foo context + +with foo 1, 2: + spam 3, 4 +``` + +Something like `foo` would be declared as a context (a handler). A `with` form would install it for the duration of a block; code inside the block (like `spam`) would have the handler available; outside the block it would not. This is the construct that makes the "scoped override" feature — present in Koka, OCaml 5, Unison — available at the ƒink source level. + +**Compared to other languages with algebraic effects:** + +- **Koka, OCaml 5, Unison, Frank** — same concept-level semantics; different surface. They have dedicated `effect` declarations, `handler` blocks, and `perform` / `resume` primitives. ƒink unifies these: protocols declared as ordinary typed values play the role of Koka's named effect operations; constructs that produce a new context play the role of handlers; the implicit context threading plays the role of `perform`/`resume`. +- **Effect-row typing.** Koka and Unison annotate every function with the effects it may perform. ƒink's model has the same type-level notion. The compiler already infers effect information at protocol granularity — every `+` compiles to a call into the operator dispatcher in `operators.wat`, every protocol use routes through its dispatcher, and these routings are known at compile time. What's missing is exposing that information in the source as effect rows on function signatures. An impl gap pending type inference, not a model difference. +- **Handler syntax.** Koka has `with handler { ... } { ... }` as a dedicated form. ƒink's planned user-context form (the `with foo 1, 2: ...` sketch above) fills the same role; other ƒink handlers (impl registration, mutual-rec binding, host-root setup) are implicit in their constructs. + +**Not to be confused with:** + +- **Haskell's `IO` / `State` monads or F#'s computation expressions** — those are monadic encodings of effects, not algebraic effects. ƒink's model is operationally closer to Koka than to Haskell. +- **Typeclass dictionary passing (Haskell, Rust traits).** ƒink does not dictionary-pass at runtime. A protocol use compiles to a direct call (when the impl is fully known), a closed set of statically-enumerated cases (when the impl is narrowed to a few candidates), or a call into a polymorphic dispatcher like the ones in `operators.wat` (when it isn't). None of these materialise a dictionary in the runtime context. Haskell/Rust, by contrast, pass explicit dictionary arguments at every polymorphic call site. +- **Dynamic scope / implicit parameters (Racket parameters, Common Lisp specials, Scala `given`).** Closer than Haskell, but in those systems the implicit parameter is named at least once at source — declared, installed, or referenced explicitly. ƒink's implicit is *entirely invisible* at source: a `read_file "foo.txt"` has no visible parameter for the file system capability, an iterator has no visible state, a `yield` has no visible scheduler handle. Only the planned user-contexts feature surfaces the implicit. Those systems also typically lack continuation capture; ƒink's CPS-with-handlers makes `yield` + scheduler and other control-flow-shaped handlers first-class. + +## 8. Glossary + +- **Pure** — a ƒink function whose evaluation does not change the implicit context. +- **Effect** — a ƒink function whose evaluation produces a new effect context. The mechanism for all context-dependent behaviour. +- **Effect context** — a runtime value threaded through execution carrying state that affects downstream computation (host capabilities, scheduler state, user-context installations, mutual-rec scopes, lazy-state slots). Implicit at the source level — ƒink code never names or passes it. Protocol impls conceptually belong in the context but operationally most never appear there; dispatch compiles to direct calls, static enumerations, or polymorphic dispatchers, not runtime context lookups. Consuming a context is pure; producing a new one is an effect. +- **Lexical scope** — a compile-time construct: the map from names to values visible at a point in source. Static in the common case; only effectful for mutual recursion, where admitting forward refs requires producing a new context. +- **Protocol** — a typed name, declared as a regular value. Used as the guard in impl-registration patterns. +- **Impl** — a function registered for a protocol against a pattern of types. +- **Registration** — the effect of introducing an impl into the current context. Syntactically: a pattern-match whose LHS head is a type-guard and which has no binding slots. +- **Resolution** — looking up which impl applies to a protocol use. Compile-time when the compiler can see the applicable impl at the use site; run-time otherwise. +- **Realisation** — how an effect is implemented at run time on a given target (compile-time specialisation, context threading, host imports, thread-locals, ...). Backend concern; does not change the language model. diff --git a/content/docs/index.md b/content/docs/index.md index c2ba7ec..28a3ab8 100644 --- a/content/docs/index.md +++ b/content/docs/index.md @@ -7,7 +7,19 @@ template: page ## [Language Reference](language/) -The core language — literals, operators, functions, pattern matching, pipes, error handling, and types. +The core language — literals, operators, functions, pattern matching, pipes, modules, concurrency and IO. + +## [Execution Model](execution-model/) + +How a ƒink program runs — effects, the implicit context, dispatch, mutual recursion, host capabilities, module lifecycle. + +## [Debugging](debugging/) + +Running ƒink under a debugger — `fink dap`, the VSCode extension, breakpoints, stepping, current limitations. + +## [Roadmap](roadmap/) + +Features designed but not yet reachable from ƒink source — `try` / `Ok` / `Err`, macros, user contexts, and more. ## [Guides](guides/) diff --git a/content/docs/language/index.md b/content/docs/language/index.md index 438c28c..4a318c8 100644 --- a/content/docs/language/index.md +++ b/content/docs/language/index.md @@ -5,8 +5,33 @@ template: docs # Language Reference -> The type system, protocols, macros, and async/concurrency features are work in progress. -> This reference covers the stable core of the language. +ƒink is a small, functional, indentation-based language. Values are immutable, types are inferred, IO goes through channels. + +Features not yet reachable in the compiler live in the [Roadmap](../roadmap/). For the execution model — what effects are, how modules run, how mutual recursion and IO fit — see the [Execution Model](../execution-model/). + +--- + +## Quickstart + +Save as `hello.fnk`: + +```fink +{stdout, write} = import 'std/io.fnk' + +main = fn ..args: + write stdout, 'Hello, ƒink!' + 0 +``` + +Run it: + +```bash +fink hello.fnk +``` + +`fink ` is shorthand for `fink run ` — `run` is the default subcommand. + +You'll see `Hello, ƒink!` on stdout. `main` returns an exit code — `0` for success. For more on `write`, channels, `spawn` / `await` and the rest, see [Concurrency and IO](#concurrency-and-io). --- @@ -31,114 +56,191 @@ true false ``` -### Numbers +### Integers -Integer size is inferred from the literal value and sign: +Integer types are inferred from the literal's value and sign (the values below show the inferred type — type information isn't surfaced in tooling yet). Underscores separate digit groups and are ignored. ```fink -1_234_567 # u32 -+1 # i8 --1 # i8 -0xFF # u8 -+0xFF # i8 -0xFfFf # u16 -0xFFFF_FFFF # u32 -0xFFFF_FFFF_FFFF_FFFF # u64 -0o_1234_5670 # octal -0b_0101_1111 # binary +1_234_567 # u32 ++1 # i8 +-1 # i8 +0xFF # u8 ++0xFF # i8 +0xFfFf # u16 +0xFFFF_FFFF # u32 +0xFFFF_FFFF_FFFF_FFFF # u64 +0o_1234_5670 # octal +0b_0101_1111 # binary ``` -Floats and decimals: +### Floats and decimals -```fink -1.0 # f32 -1.0e100_000 # f64 +Floats are sized the same way (`f32` / `f64`). Decimals are a distinct type and don't mix with floats. -1.0d # decimal — cannot mix with floats -1.0d-100 # decimal with negative exponent +```fink +1.0 # f32 +1.0e100_000 # f64 +1.0d # decimal ``` ### Strings -Single-quoted, with interpolation and multiline support: +Single-quoted. A string with `${expr}` inside is a **template string** — the expression is evaluated and interpolated. Escape sequences work in any string. ```fink 'hello world' -'hello ${1 + 2}' +'result: ${1 + 2}' -' - multiline - string -' +'line one\nline two' ``` -Escape sequences: +Multiline strings start with `'` alone on a line and indent the content. Common leading whitespace is stripped from each line, and the surrounding newlines are preserved: ```fink -'\n' # newline -'\t' # tab -'\\' # backslash -'\'' # single quote -'\x0f' # hex code point -'\u{10_ff_ff}' # Unicode code point -'\${' # literal ${ +foo = ' + one + two + three +' + +# foo == '\none\ntwo\nthree\n' ``` -String blocks — no need to escape single quotes, indentation stripped: +Block strings begin with `":` and end when the indent drops back. Common leading whitespace is stripped, but unlike the multiline `'` form, leading and trailing newlines are not included in the value. Template interpolation and embedded single-quotes need no escaping: + +```fink +foo = ": + one + two + +# foo == 'one\ntwo' +``` ```fink ": - supports templating ${bar} - no need to escape 'spam' + template interpolation ${name} + and 'quotes' without escaping ``` -Tagged template strings pass raw parts and values to a function: +Use `'` when you want the surrounding newlines preserved; use `":` when you want a clean trimmed string and don't want to escape interior `'` or `${...}` segments. + +Escape sequences: ```fink -fmt'hello ${name}' -sql'SELECT * FROM users WHERE name = ${name}' -rx'(?[a-z]+)' +' + \n - new line + \r - carriage return + \v - vertical tab + \t - tab + \b - backspace + \f - formfeed + \\ - backslash + \' - single quote + \$ - dollar sign + \x0f - hex byte (exactly 2 hex digits) + \u{ff} - Unicode code point between U+0000 and U+10FFFF + \u{10_ff_ff} - underscores allowed for readability +' ``` -Raw strings — escape sequences are not processed: +#### Tagged templates + +A function name immediately before a string literal calls the function with the string's parts and interpolated values. The standard library exposes two tags from `std/str.fnk`: ```fink -raw'foo \n \t bar' -raw": - foo \n - bar -``` +{fmt, raw} = import 'std/str.fnk' -### Tagged literals +fmt'result: ${1 + 2}' # interpolates — same as 'result: ${1 + 2}' +raw'line\nbreak' # leaves \n literal — no escape processing +``` -Postfix function application for readable units: +A tag is just a function. It receives `(parts, vals)` — `parts` is the sequence of literal segments, `vals` is the sequence of interpolated values. Defining your own: ```fink -10sec # == sec 10 -10.5min # == min 10.5 -(foo)min # == min foo +fmt_log = fn parts, vals: + ... + +fmt_log'hello ${name}, you have ${count} messages' +# calls fmt_log with parts ['hello ', ', you have ', ' messages'], vals [name, count] ``` -### Collections +## Collections + +Collections come in two literal shapes: + +- **Sequential** — `[ ... ]` — an ordered series of values. +- **Keyed** — `{ ... }` — values addressed by a key. + +The shape is syntax. The runtime *type* is chosen by the compiler from what the literal contains. Sequential literals default to a `list`. Keyed literals default to a `record` when every key is known at compile time, otherwise a `dict`. To pick a different type explicitly — for example to dedup with a `set` — call its constructor. + +### Sequential — `[ ... ]` + +Ordered, zero-indexed. ```fink -# sequences (tuple-optimized when used as such) [] [1, 2, 3] -seq 1, 2, 3 +``` -# records (compile-time field names) +Multiline: + +```fink +numbers = [ + 1 + 2 + 3 +] +``` + +For a specific sequential type, call its constructor with the elements as arguments: + +```fink +{set} = import 'std/set.fnk' + +set 1, 2, 2, 3 # set of 1, 2, 3 — duplicates collapsed +``` + +```fink +{list} = import 'std/list.fnk' + +list 1, 2, 3 # explicit list constructor +``` + +### Keyed — `{ ... }` + +A `record` has keys known at compile time. They can be identifiers, string literals (for keys with spaces or unusual characters), or parenthesised expressions the compiler can resolve at compile time. + +```fink {} {foo: 1, bar: 2} -{foo: 1, 'ni na': 2, (key): 3} # computed keys +{'foo bar': 42} +{(1 + 1): 'two'} + +point = {x: 1, y: 2} +``` + +When a key resolves only at runtime, the compiler builds a `dict` instead. There's no user-facing `dict` constructor yet — see the [Roadmap](../roadmap/#dicts). + +--- + +## Identifiers and wildcards + +Identifiers are sequences of UTF-8 graphemes. Hyphens and underscores are fine inside a name (whitespace around operators disambiguates from subtraction). -# dictionaries (runtime keys) -dict {foo: 1, 'bar': 2, (key): 3} +```fink +foo +foo-bar +foo_bar +ni_1234 +``` + +`_` is the wildcard — a non-binding placeholder, not a name. Use it in patterns and parameter positions to discard. -# sets -set 1, 2, 3 -ordered_set 3, 2, 1 +```fink +_ # in a pattern, discard +fn _, b: b # ignore the first argument +[_, x] = [1, 2] # discard first element ``` --- @@ -148,21 +250,37 @@ ordered_set 3, 2, 1 ### Arithmetic ```fink --a # unary minus +-a # unary minus a + b a - b a * b a / b -a // b # integer divide -a ** b # power -a % b # remainder (sign follows dividend) -a %% b # true modulus (sign follows divisor) -a /% b # divmod — returns [quotient, remainder] +a // b # integer divide +a ** b # power +a % b # remainder (sign follows dividend) +a %% b # true modulus (sign follows divisor) +a /% b # divmod — returns [quotient, remainder] +``` + +### Comparison + +Comparison operators produce a `bool` and chain naturally: + +```fink +a > b +a >= b +a < b +a <= b +a == b +a != b +a >< b # disjoint — a and b have no element in common + +1 < x < 10 # chained ``` ### Logical -Operands must be bools, returns bool: +Operate on booleans and return a boolean. ```fink not a @@ -173,98 +291,114 @@ a xor b ### Bitwise +Shared symbols with logical; dispatch is by value type. + ```fink -~a # not -a & b # and -a ^ b # xor -a >> b # shift right -a << b # shift left -a >>> b # rotate right -a <<< b # rotate left +not 0b0101_0101 # 0b1010_1010 +0b1100 and 0b1010 # 0b0000_1000 +0b1100 or 0b1010 # 0b0000_1110 +0b1100 xor 0b1010 # 0b0000_0110 + +a << b # shift left +a >> b # shift right +a <<< b # rotate left +a >>> b # rotate right ``` -### Comparison +### Ranges -Chainable, always returns bool: +```fink +0..10 # 0 inclusive, 10 exclusive +0...10 # 0 inclusive, 10 inclusive + +1 + 2..3 + 4 # (1 + 2)..(3 + 4) — `..` binds looser than arithmetic +(1 + 2)..(3 + 4) # parens optional for clarity +``` + +Range literals are first-class values. + +### Membership + +`in` / `not in` test membership across any container that supports it — ranges, sequences, sets, and keyed types (records, dicts): ```fink -a == b -a != b -a > b -a >= b -a < b -a <= b -a > b > c # chained -a in b -a not in b -a >< b # disjoint +5 in 0..10 # range +2 in [1, 2, 3] # sequence element +'x' in {x: 1, y: 2} # keyed type — checks the keys, not the values +5 not in 0..3 # negated form ``` -### Ranges +### Member access + +By name: ```fink -0..10 # exclusive end -0...10 # inclusive end -'a'...'z' # char range -start..end -(1 + 2)..(3 + 4) +point.x +foo.bar.spam ``` -### Spread +By expression — the expression must be resolvable at compile time, or the operand's type must implement `.`: ```fink -[head, ..tail] -[..seq1, ..seq2] # concat +[10, 20, 30].(0) # 10 -{foo: bar, ..rest} -{..rec1, ..rec2} # merge -``` +key = 'x' +point.(key) # point.x ---- +point.'x' # string-literal form of .(expr) — useful for keys that aren't valid identifiers +``` -## Bindings and Pattern Matching +### Spread -### Left-hand binding +Destructures on the left, splices on the right. ```fink -foo = 1 +[head, ..tail] = [1, 2, 3] -[a, b] = [1, 2] -{x, y} = point -{x, y: z} = point # bind x, rename y to z +greet = fn name, ..titles: '${name} — ${titles}' + +both = [..left, ..right] +merged = {..a, ..b} ``` -Patterns can include guards: +--- -```fink -[x, y >= 2] = [1, 2] -[is_odd head, ..tail] = [3, 4, 5] -``` +## Precedence and grouping + +Parentheses group. Newlines separate statements. -Rest patterns: +`;` is the inline statement separator — it binds tighter than `,`, so `[add 1, 2; add 3, 4]` is `[(add 1, 2), (add 3, 4)]`, not `[add 1, (2; add 3), 4]`. Use it when you want to keep multiple statements on one line that would otherwise span several: ```fink -[head, ..tail] = [1, 2, 3, 4] -[head, ..middle, end] = [1, 2, 3, 4] -``` +15 == (1 + 2) * (2 + 3) -String patterns: +[3, 7] == [ + add 1, 2 + add 3, 4 +] -```fink -'start ${middle} end' = 'start foo end' -# middle == ' foo ' +[3, 7] == [add 1, 2; add 3, 4] ``` -Record patterns match partially; sequence patterns match exactly: +--- + +## Bindings + +ƒink bindings use pattern matching — the left side is a pattern, the right side is the value. + +### Left-hand ```fink -{a} = {a: 1, b: 2} # ok — records are partial -[a, ..] = [1, 2] # ok — explicit rest discard +foo = 1 + +[a, b] = [1, 2] +{x, y} = point +{x, y: z} = point # bind x to point.x and y to point.z ``` -### Right-hand binding +### Right-hand -Capture the result of a multiline expression: +`expr |= pat` evaluates `expr` and binds it to `pat`. The same patterns as `=` work; the only difference is direction. Useful when the value-producing expression is long and the binding name reads better on the right: ```fink foo @@ -273,95 +407,116 @@ foo |= result ``` -### `match` +### Guards + +Any pattern position accepts a guard — a boolean expression that must hold for the pattern to match. ```fink -match foo: - 1: 'one' - 2: 'two' - _: 'other' +[x, y > 2] = [1, 3] +[x, is_even y] = [1, 4] # assumes a user-defined `is_even` ``` -Match on structure: +### Nesting and spread + +Sequential patterns support spread anywhere — at the head, the tail, or in the middle: ```fink -match foo: - [head, ..tail]: head - []: 'empty' -``` +[a, [b, c]] = [1, [2, 3]] -Match with guards: +{a, b: {c, d}} = {a: 1, b: {c: 2, d: 3}} -```fink -match foo: - n > 0 and n < 10: 'small positive ${n}' - n > 0: 'large positive ${n}' - even n: 'even number ${n}' - _: 'other' +[head, ..tail] = [1, 2, 3, 4] # head=1, tail=[2, 3, 4] +[..init, last] = [1, 2, 3, 4] # init=[1, 2, 3], last=4 +[head, ..middle, end] = [1, 2, 3, 4] # head=1, middle=[2, 3], end=4 +[a, b, ..mid, x, y] = [1, 2, 3, 4, 5, 6] # a=1, b=2, mid=[3, 4], x=5, y=6 ``` -Match on types: +### Keyed patterns match partially; sequential patterns match exactly ```fink -match foo: - str s: 'its a string ${s}' - u8 n: 'its a u8 ${n}' +{a} = {a: 1, b: 2} # fine — keyed patterns match partially +[a] = [1, 2] # fails — sequential pattern has extra elements +[a, ..] = [1, 2] # fine — .. discards the rest ``` -Match on sequence and record structure: +### String patterns -```fink -match items: - []: 'empty' - [x]: 'one element' - [x, y]: 'two elements' - [x, ..rest]: 'head and rest' +A template string on the left-hand side captures interpolation holes from a literal-on-the-right. -match foobar: - {}: 'empty' - {foo: 1}: 'has foo = 1' - {foo: 1, ..rest}: 'has foo = 1 and more' +```fink +'start ${middle} end' = 'start foo end' +# middle == 'foo' ``` --- ## Functions +Defined with `fn args: body`. Zero args is `fn: body`. + ```fink add = fn a, b: result = a + b result +``` + +A single-line form is also fine when the body is short: + +```fink +add = fn a, b: a + b -# no args greet = fn: 'hello' +``` -# default args -greet = fn name='world': 'hello ${name}' +### Pattern-matched parameters -# pattern matching in args -foo = fn {x, y}: x + y -bar = fn [head, ..tail]: head -baz = fn arg, ..rest: arg +Same pattern language as bindings: + +```fink +sum = fn {x, y}: x + y +head = fn [head, ..]: head ``` -### `fn match` sugar +### Varargs + +One trailing `..rest` parameter captures the rest of the arguments as a sequence. (Interpolating a sequence renders it as `[a, b, c]`.) + +```fink +log = fn prefix, ..parts: + '${prefix}: ${parts}' + +log 'tags', 'red', 'green' # 'tags: [red, green]' +``` + +### `fn match` + +Syntactic sugar for `fn args: match args:`. Use when the whole function body is a `match` on the parameter. ```fink classify = fn match n: n > 0: 'positive' n < 0: 'negative' - _: 'zero' + _: 'zero' ``` -### Mutual recursion +is the same as -Forward references at module level allow mutual recursion without special syntax: +```fink +classify = fn n: match n: + n > 0: 'positive' + n < 0: 'negative' + _: 'zero' +``` + +### Higher-order, closures, recursion + +Functions are values. They close over their enclosing scope. Module-level functions can refer to each other in any order (mutual recursion): ```fink is_even = fn n: match n: 0: true - _: is_odd n - 1 + _: is_odd n - 1 is_odd = fn n: match n: @@ -369,127 +524,295 @@ is_odd = fn n: _: is_even n - 1 ``` +Mutual recursion only works at module scope. Inside a function body or block, bindings are not pre-declared — referring to a name before it is bound is an error. If you need mutual recursion in a nested scope, hoist the helpers to module level. + --- ## Application -Prefix application, right-to-left nesting: +Apply arguments to a function by writing them after it, separated by commas. (For `;` see [Precedence and grouping](#precedence-and-grouping).) ```fink log 'hello' add 1, 2 -# nested — right to left -foo bar spam ham -# == foo (bar (spam ham)) - -# multiline — indented args add mul 2, 3 mul 3, 4 +# same as: +add (mul 2, 3), (mul 3, 4) + +add mul 2, 3; mul 3, 4 ``` -Use `;` as a strong inline separator (stronger than `,`): +Nested application is right-to-left: ```fink -add mul 2, 3; mul 3, 4 -# == add (mul 2, 3), (mul 3, 4) +foo bar spam +# same as: +foo (bar spam) +``` + +To call a zero-argument function, pass the wildcard `_` as the sole argument: + +```fink +greet = fn: 'hello' + +greet _ # calls greet with no arguments +``` + +### Tagged postfix application + +A literal followed by a function name applies the function to the literal. Useful for unit-like wrappers and other post-fix conversions: + +```fink +10sec # sec 10 +10.5min # min 10.5 +(foo)min # min foo ``` ### Partial application with `?` -`?` creates an anonymous function scoped to the current expression or pipe segment: +`?` in an expression stands for a hole that, taken together with the expression's scope, becomes a function of one argument. ```fink add5 = add 5, ? -add5 = ? + 5 +# same as: +add5 = fn $: add 5, $ -filter is_divisible ?, 2 # == filter fn $: is_divisible $, 2 -map ? * 2 # == map fn $: $ * 2 +inc = ? + 1 +# same as: +inc = fn $: $ + 1 ``` -`?` is transparent through sequences, records, and operators — all `?` in the same scope share one parameter: +`?` bubbles up to the nearest scope boundary. The boundaries are: + +- a parenthesised group `(...)`, +- a pipe segment (everything between two `|`s, or from a `|` to the start of the statement), +- the right-hand side of a binding (`lhs = rhs` — the bubble stops at `rhs`, never engulfs the `=`), +- a standalone top-level expression. + +All `?` in the same scope refer to the same single parameter. ```fink -[?, ?] # == fn $: [$, $] -{foo: ?, bar: ?} # == fn $: {foo: $, bar: $} +[?, ?] # fn $: [$, $] +{foo: ?, bar: ?} # fn $: {foo: $, bar: $} + +(foo ?.(1), ?.(2)) # fn $: foo $.(1), $.(2) — one input, used twice ``` -`(...)` is an explicit scope boundary: +Parenthesise to narrow the scope: ```fink -foo (bar ?) # == foo (fn $: bar $) +{bar: (? + 2), spam: (? + 3)} +# same as: +{bar: fn $: $ + 2, spam: fn $: $ + 3} ``` --- ## Pipes -Left-to-right application: +`|` applies left-to-right. Each pipe segment is its own partial-application scope. ```fink -'hello' -| capitalize -| log -# == log (capitalize 'hello') +'hello' | capitalize | log +# same as: +log capitalize 'hello' ``` -Each pipe segment is its own `?` scope: +With partial application, each segment uses `?` for the incoming value: ```fink -1..10 -| filter ? % 2 == 0 -| map ? * 2 -| [..?] -|= even_nums +add = fn a, b: a + b + +result = 2 + | add 3, ? # add 3 to 2 + | add 10, ? # then add 10 +# result == 15 ``` -Pass result as spread arguments: +Use `..?` to splat a sequence into multiple arguments: ```fink -[1, 2] | add ..? -# == fn [a, b]: add a, b +[1, 2] | add ..? # add 1, 2 ``` --- -## Error Handling +## Pattern matching -`try` unwraps `Ok` or propagates `Err` up the call stack: +`match` tries each arm top-to-bottom; the first that matches wins. Bindings from the matching pattern are in scope for the arm's body. ```fink -fn foo: - a = try bar a - b = try baz a - Ok a + b +classify = fn match n: + 0: 'zero' + n > 0 and n < 10: 'small positive' + n > 0: 'large positive' + _: 'negative' +``` + +Deep structural matching: + +```fink +describe = fn match point: + {x: 0, y: 0}: 'origin' + {x: 0, y}: 'on y-axis at ${y}' + {x, y: 0}: 'on x-axis at ${x}' + {x, y}: '(${x}, ${y})' ``` -`match` handles errors explicitly: +Sequential and keyed patterns support spread: ```fink -fn foo: - match bar _: - Ok x: x + 1 - Err e: log 'error: ${e}' +match items: + []: 'empty' + [x]: 'one: ${x}' + [x, ..rest]: 'head ${x}, rest ${rest}' + [..init, last]: 'last ${last}, init ${init}' + +match config: + {}: 'empty' + {debug: true, ..}: 'debug mode' + {..anything}: 'some config' ``` -Error chaining: +Patterns match by *shape*, not concrete type. A `[..]` pattern matches anything sequence-like — list, set, range. A `{..}` pattern matches anything keyed — record, dict. + +String patterns capture holes in a template: ```fink -fn foo: - match bar _: - Ok x: Ok x - Err e: Err e, 'foo failed' +match 'hello world': + 'hello ${rest}': rest # 'world' + _: '' ``` --- ## Modules +A file is a module. Names bound at the top level are its exports. + ```fink -{foo, bar} = import './foobar.fnk' +# ./greet.fnk +{stdout, write} = import 'std/io.fnk' + +hello = fn name: + write stdout, 'hello ${name}' ``` +`hello` is exported. The `stdout` and `write` names are bindings local to this module — they were brought in by `import` and are not re-exported. + +Another module imports it by destructuring the result of `import`: + +```fink +# ./example.fnk +{hello} = import './greet.fnk' + +main = fn first_name, last_name: + hello '${first_name} ${last_name}' + +``` + +Path resolution: + +- `./example.fnk` and `../bar.fnk` — relative to the importing file. +- `std/foo.fnk` — bundled standard library (e.g. `std/io.fnk`, `std/list.fnk`, `std/set.fnk`, `std/str.fnk`). + +--- + +## Concurrency and IO + +ƒink programs are cooperative — tasks yield at I/O and scheduler points. Values flow between tasks through channels. stdio behaves like channels. + +### `main` and the IO channels + +The runner calls `main` with `..args` — CLI argv. `main` returns an exit code. The IO channels (`stdin`, `stdout`, `stderr`) and the IO functions (`read`, `write`) come from `import 'std/io.fnk'`, not as positional parameters. + +```fink +{stdout, write} = import 'std/io.fnk' + +main = fn ..args: + write stdout, 'Hello, world' + 0 +``` + +### Writing to a stream + +`write stream, value` sends `value` to `stream` and returns `stream`. The return value enables chaining via the pipeline operator: + +```fink +stdout +| write ?, 'foo' +| write ?, 'bar' +``` + +This writes `foobar` to stdout. `write` is the recommended way to send to streams. + +> **Low-level operators.** `>>` and `<<` are channel-send operators (`x >> stream` and `stream << x`) that also serve as bitwise shifts; dispatch is by value type. They remain available but `write` is preferred for stream IO. + +### Receiving from a channel + +`receive` parks the current task until a message arrives: + +```fink +line = receive stdin +``` + +### Spawning and awaiting + +`spawn` creates a task from a zero-arg function; `await` blocks on its result. + +```fink +future = spawn fn: + compute_something + +result = await future +``` + +### Reading raw bytes + +`read stream, n` reads up to `n` bytes from a host stream (stdin, typically): + +```fink +bytes = read stdin, 1024 +``` + +--- + +## Block scoping + +Every indented body is its own scope; bindings inside don't leak out. + +```fink +result = ( + tmp = 10 + 20 + tmp * 2 +) +# tmp is not in scope here +# result == 60 +``` + +Record field bodies, match arm bodies, and function bodies behave the same way. Module scope is the only place where bindings are mutually recursive — order of definition does not matter inside a module. + +--- + +## Indentation + +Indented lines continue the preceding construct. A decrease in indent ends the construct. + +```fink +add + mul 2, 3 + mul 3, 4 + +# continuation after a comma is fine +foo bar, + spam +``` + +For inline statement separation with `;` see [Precedence and grouping](#precedence-and-grouping). + --- ## Types *(work in progress)* @@ -528,3 +851,11 @@ match opt: Some x: x None: 0 ``` + +--- + +## Further reading + +- [Execution Model](../execution-model/) — how a ƒink program runs. +- [Debugging](../debugging/) — running ƒink under a debugger. +- [Roadmap](../roadmap/) — designed features not yet reachable. diff --git a/content/docs/roadmap/index.md b/content/docs/roadmap/index.md new file mode 100644 index 0000000..c013658 --- /dev/null +++ b/content/docs/roadmap/index.md @@ -0,0 +1,65 @@ +--- +title: Roadmap +template: docs +--- + +# Roadmap + +What's designed but not yet usable from ƒink source. Features listed here have some presence in the compiler or runtime; they just aren't reachable to a ƒink programmer yet. + +For features that *work today*, see the [Language Reference](../language/). + +## Error handling (`try`) + +`try` parses and lowers through CPS as a passthrough. The language-level semantics — `Ok` / `Err` values, propagation from the enclosing function, `match Ok / Err` patterns — aren't wired yet. Once they are, `try foo` will unwrap on `Ok` and propagate the `Err` up the call stack. + +```fink +content = try read_file 'config.toml' +# on Ok: content bound; on Err: propagate out of this fn +``` + +## Dicts + +The runtime has a HAMT-based dict type and most operations are wired (get / set / delete / size / merge / equality), but there's no user-facing constructor exposed under `std/dict.fnk` yet. Records today are structurally dicts at runtime — they share the same HAMT implementation — but the language-level `dict {...}` form with dynamic string keys (as opposed to records' compile-time-known identifier keys) isn't reachable from source. + +```fink +{dict} = import 'std/dict.fnk' + +scores = dict 'alice': 1, 'bob': 2 +``` + +## Macros + +Compile-time AST manipulation — `macro` definitions, `eval`, `gen_ast`-style APIs. Entirely future work; nothing in the compiler. + +## Context and effects (`with`, `get_ctx`) + +Scoped ambient values — a structured alternative to implicit globals. Designed in sketch form, no compiler support. + +```fink +DB_CTX = context DB +with db_ctx: + result = foo () +``` + +Concept: see the [Execution Model](../execution-model/) §7. + +## Missing arithmetic operators + +`**` (power) and `/%` (divmod) parse but aren't lowered yet — using them today panics in the lowering pass. The other arithmetic operators all work end-to-end. + +## Ordering operator (`<=>`) + +A three-way comparison returning `LT` / `EQ` / `GT` was designed but isn't shipped — `<=>` doesn't lex, and `LT`/`EQ`/`GT` aren't defined. The pairwise `<` / `<=` / `>` / `>=` / `==` / `!=` operators cover most needs today. + +## Advanced pattern matchers + +A few advanced match forms are parseable but don't lower end-to-end yet: + +- Spread guards: `[..(is_odd), ..evens] = [1, 2, 3, 4, 5]` +- String range patterns as match arms: `'a'..'z'` +- Pattern-position call guards with spread capture: `[..(is_divisible_by ?, 3) |= divs, ..rest]` + +## Types and protocols + +Deferred pending a broader design conversation. Not documented here until the model is settled.