diff --git a/examples/Arithmetic/main.roc b/examples/Arithmetic/main.roc index 05aa0ab..3742463 100644 --- a/examples/Arithmetic/main.roc +++ b/examples/Arithmetic/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task diff --git a/examples/BasicRustGUI/.gitignore b/examples/BasicRustGUI/.gitignore new file mode 100644 index 0000000..af423fe --- /dev/null +++ b/examples/BasicRustGUI/.gitignore @@ -0,0 +1,16 @@ +platform/*.a +platform/*.dylib +platform/*.so +platform/*.rh +platform/*.rm + +target/ + +build + +# executables +hello-gui +inspect-gui + +# glue generated files not used +crates/roc_app/ diff --git a/examples/BasicRustGUI/Cargo.lock b/examples/BasicRustGUI/Cargo.lock new file mode 100644 index 0000000..3f085a2 --- /dev/null +++ b/examples/BasicRustGUI/Cargo.lock @@ -0,0 +1,2555 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "approx" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" +dependencies = [ + "num-traits", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ash" +version = "0.35.2+1.2.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6a491bcad4563b355ac2bb6e3f09d5e1c5d628710c7156e901dad0c416075e" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "calloop" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +dependencies = [ + "log", + "nix 0.22.3", +] + +[[package]] +name = "cc" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgmath" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" +dependencies = [ + "approx 0.4.0", + "num-traits", +] + +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.4", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "copypasta" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys 0.8.6", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags 1.3.2", + "libloading 0.7.4", + "winapi", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.5", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glyph_brush" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca26e3a8a43052ca015c0b1ce055035ff5bc47afddff5b01c7141e0c3e3a2a1" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "ordered-float", + "rustc-hash 2.0.0", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6c910def52365fef3f439a6b50a4d5c11b28eec4cf6c191f6dfea18e88d7f" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash 2.0.0", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1e288bfd2f6c0313f78bf5aa538356ad481a3bb97e9b7f93220ab0066c5992" +dependencies = [ + "ab_glyph", + "approx 0.5.1", + "xi-unicode", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" +dependencies = [ + "bitflags 1.3.2", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.6.0", + "gpu-descriptor-types", + "hashbrown 0.14.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "host" +version = "0.0.1" +dependencies = [ + "roc_host", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "inplace_it" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e567468c50f3d4bc7397702e09b380139f9b9288b4e909b070571007f8b5bf78" + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading 0.7.4", + "pkg-config", +] + +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if 1.0.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.23.1" +source = "git+https://github.com/gfx-rs/metal-rs?rev=44af5cc#44af5cca340617d42d701264f9bf71d1f3e68096" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "naga" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/naga?rev=8e2e39e#8e2e39e4d8fa5bbb657c3b170b4f6607d703e284" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "codespan-reporting", + "hexf-parse", + "indexmap 1.9.3", + "log", + "num-traits", + "rustc-hash 1.1.0", + "spirv", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71bee8ea72d685477e28bd004cfe1bf99c754d688cd78cad139eae4089484d4" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "ordered-float" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "palette" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9cd68f7112581033f157e56c77ac4a5538ec5836a2e39284e65bd7d7275e49" +dependencies = [ + "approx 0.5.1", + "num-traits", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05eedf46a8e7c27f74af0c9cfcdb004ceca158cb1b918c6f68f8d7a549b3e427" +dependencies = [ + "find-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "pest_meta" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" + +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "roc_host" +version = "0.0.1" +dependencies = [ + "arrayvec", + "bytemuck", + "cgmath", + "colored", + "copypasta", + "fs_extra", + "futures", + "glyph_brush", + "libc", + "log", + "nonempty", + "page_size", + "palette", + "pest", + "pest_derive", + "roc_std", + "serde", + "snafu", + "threadpool", + "wgpu", + "wgpu_glyph", + "winit", +] + +[[package]] +name = "roc_host_bin" +version = "0.0.1" +dependencies = [ + "roc_host", +] + +[[package]] +name = "roc_std" +version = "0.0.1" +dependencies = [ + "arrayvec", + "libc", + "pretty_assertions", + "quickcheck", + "quickcheck_macros", + "serde", + "serde_json", + "static_assertions", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2 0.3.1", + "nix 0.22.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +dependencies = [ + "bitflags 1.3.2", + "dlib", + "lazy_static", + "log", + "memmap2 0.5.10", + "nix 0.24.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit 0.16.1", + "wayland-client", +] + +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags 1.3.2", + "num-traits", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + +[[package]] +name = "ttf-parser" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8686b91785aff82828ed725225925b33b4fde44c4bb15876e5f7c832724c420a" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "rand", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.71", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "js-sys", + "log", + "naga", + "parking_lot", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "cfg_aliases", + "codespan-reporting", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "ash", + "bit-set", + "bitflags 1.3.2", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading 0.7.4", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "wgpu_glyph" +version = "0.16.0" +source = "git+https://github.com/Anton-4/wgpu_glyph?rev=257d109#257d1098cbafa3c8a0a2465937b06fc730fc6ffb" +dependencies = [ + "bytemuck", + "glyph_brush", + "log", + "wgpu", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" +dependencies = [ + "bitflags 1.3.2", + "cocoa", + "core-foundation 0.9.4", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio", + "ndk", + "ndk-glue", + "ndk-sys", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "smithay-client-toolkit 0.15.4", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "winapi", + "x11-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11-clipboard" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" +dependencies = [ + "xcb", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xcb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +dependencies = [ + "libc", + "log", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" + +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] diff --git a/examples/BasicRustGUI/Cargo.toml b/examples/BasicRustGUI/Cargo.toml new file mode 100644 index 0000000..bbeff19 --- /dev/null +++ b/examples/BasicRustGUI/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +resolver = "2" +members = [ + "crates/roc_std", + "crates/roc_host", + "crates/roc_host_lib", + "crates/roc_host_bin", +] + +[profile.release] +lto = true +strip = "debuginfo" +codegen-units = 1 diff --git a/examples/BasicRustGUI/Community.roc b/examples/BasicRustGUI/Community.roc new file mode 100644 index 0000000..f413f13 --- /dev/null +++ b/examples/BasicRustGUI/Community.roc @@ -0,0 +1,81 @@ +module [ + Community, + empty, + addPerson, + addFriend, + Person, + walkFriendNames, +] + +## Datatype representing a community for demonstration purposes in inspect-gui.roc and inspect-logging.roc + +Community := { + people : List Person, + friends : List (Set U64), +} + implements [Inspect] + +Person := { + firstName : Str, + lastName : Str, + age : U8, + hasBeard : Bool, + favoriteColor : Color, +} + implements [Inspect] + +Color : [ + Red, + Green, + Blue, + RGB (U8, U8, U8), +] + +empty = @Community { people: [], friends: [] } + +addPerson = \@Community { people, friends }, person -> + @Community { + people: List.append people (@Person person), + friends: List.append friends (Set.empty {}), + } + +addFriend = \@Community { people, friends }, from, to -> + when (List.get friends from, List.get friends to) is + (Ok fromSet, Ok toSet) -> + @Community { + people, + friends: friends + |> List.set from (Set.insert fromSet to) + |> List.set to (Set.insert toSet from), + } + + _ -> + @Community { people, friends } + +walkFriendNames : Community, state, (state, Str, Set Str -> state) -> state +walkFriendNames = \@Community { people, friends }, s0, nextFn -> + (out, _) = + (s1, id), friendSet <- List.walk friends (s0, 0) + (@Person person) = + when List.get people id is + Ok v -> v + Err _ -> crash "Unknown Person" + personName = + person.firstName + |> Str.concat " " + |> Str.concat person.lastName + + friendNames = + friendsSet, friendId <- Set.walk friendSet (Set.empty {}) + (@Person friend) = + when List.get people friendId is + Ok v -> v + Err _ -> crash "Unknown Person" + friendName = + friend.firstName + |> Str.concat " " + |> Str.concat friend.lastName + Set.insert friendsSet friendName + + (nextFn s1 personName friendNames, id + 1) + out diff --git a/examples/BasicRustGUI/GuiFormatter.roc b/examples/BasicRustGUI/GuiFormatter.roc new file mode 100644 index 0000000..146da44 --- /dev/null +++ b/examples/BasicRustGUI/GuiFormatter.roc @@ -0,0 +1,231 @@ +module [ + GuiFormatter, + toGui, +] + +## Creates GUI representations of Roc values, for use in inspect-gui.roc + +## This can't depend on the platform, so I just copied all of this. + +Rgba : { r : F32, g : F32, b : F32, a : F32 } +ButtonStyles : { bgColor : Rgba, borderColor : Rgba, borderWidth : F32, textColor : Rgba } +Elem : [Button Elem ButtonStyles, Col (List Elem), Row (List Elem), Text Str] + +GuiFormatter := { nodes : List Elem } + implements [ + InspectFormatter { + init: init, + list: list, + set: set, + dict: dict, + tag: tag, + tuple: tuple, + record: record, + bool: bool, + str: str, + function: function, + opaque: opaque, + u8: u8, + i8: i8, + u16: u16, + i16: i16, + u32: u32, + i32: i32, + u64: u64, + i64: i64, + u128: u128, + i128: i128, + f32: f32, + f64: f64, + dec: dec, + + }, + ] + +init : {} -> GuiFormatter +init = \{} -> @GuiFormatter { nodes: [] } + +list : list, ElemWalker GuiFormatter list elem, (elem -> Inspector GuiFormatter) -> Inspector GuiFormatter +list = \content, walkFn, toInspector -> + f0 <- Inspect.custom + # Use a temporary buffer for the children nodes + (@GuiFormatter { nodes }) = + init {} + |> \f1 -> + f2, elem <- walkFn content f1 + elem + |> toInspector + |> Inspect.apply f2 + + addNode f0 (Col nodes) + +set : set, ElemWalker GuiFormatter set elem, (elem -> Inspector GuiFormatter) -> Inspector GuiFormatter +set = \content, walkFn, toInspector -> + f0 <- Inspect.custom + # Use a temporary buffer for the children nodes + (@GuiFormatter { nodes }) = + init {} + |> \f1 -> + f2, elem <- walkFn content f1 + elem + |> toInspector + |> Inspect.apply f2 + + addNode f0 (Col nodes) + +dict : dict, KeyValWalker GuiFormatter dict key value, (key -> Inspector GuiFormatter), (value -> Inspector GuiFormatter) -> Inspector GuiFormatter +dict = \d, walkFn, keyToInspector, valueToInspector -> + f0 <- Inspect.custom + # Use a temporary buffer for the children nodes + (@GuiFormatter { nodes }) = + init {} + |> \f1 -> + f2, key, value <- walkFn d f1 + (@GuiFormatter { nodes: innerNodes }) = + init {} + |> \x -> Inspect.apply (keyToInspector key) x + |> addNode (Text ":") + |> \x -> Inspect.apply (valueToInspector value) x + + addNode f2 (Row innerNodes) + + addNode f0 (Col nodes) + +tag : Str, List (Inspector GuiFormatter) -> Inspector GuiFormatter +tag = \name, fields -> + f0 <- Inspect.custom + # Use a temporary buffer for the children nodes + (@GuiFormatter { nodes }) = + init {} + |> addNode (Text name) + |> \f1 -> + f2, fieldInspector <- List.walk fields f1 + Inspect.apply fieldInspector f2 + + addNode f0 (Row nodes) + +tuple : List (Inspector GuiFormatter) -> Inspector GuiFormatter +tuple = \fields -> + f0 <- Inspect.custom + # Use a temporary buffer for the children nodes + (@GuiFormatter { nodes }) = + init {} + |> \f1 -> + f2, fieldInspector <- List.walk fields f1 + Inspect.apply fieldInspector f2 + + addNode f0 (Row nodes) + +record : List { key : Str, value : Inspector GuiFormatter } -> Inspector GuiFormatter +record = \fields -> + f0 <- Inspect.custom + # Use a temporary buffer for the children nodes + (@GuiFormatter { nodes }) = + init {} + |> \f1 -> + f2, { key, value } <- List.walk fields f1 + (@GuiFormatter { nodes: innerNodes }) = + init {} + |> addNode (Text key) + |> addNode (Text ":") + |> \x -> Inspect.apply value x + + addNode f2 (Row innerNodes) + + addNode f0 (Col nodes) + +bool : Bool -> Inspector GuiFormatter +bool = \b -> + if b then + f0 <- Inspect.custom + addNode f0 (Text "true") + else + f0 <- Inspect.custom + addNode f0 (Text "false") + +str : Str -> Inspector GuiFormatter +str = \s -> + f0 <- Inspect.custom + addNode f0 (Text "\"$(s)\"") + +opaque : * -> Inspector GuiFormatter +opaque = \_ -> + f0 <- Inspect.custom + addNode f0 (Text "") + +function : * -> Inspector GuiFormatter +function = \_ -> + f0 <- Inspect.custom + addNode f0 (Text "") + +u8 : U8 -> Inspector GuiFormatter +u8 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +i8 : I8 -> Inspector GuiFormatter +i8 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +u16 : U16 -> Inspector GuiFormatter +u16 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +i16 : I16 -> Inspector GuiFormatter +i16 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +u32 : U32 -> Inspector GuiFormatter +u32 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +i32 : I32 -> Inspector GuiFormatter +i32 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +u64 : U64 -> Inspector GuiFormatter +u64 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +i64 : I64 -> Inspector GuiFormatter +i64 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +u128 : U128 -> Inspector GuiFormatter +u128 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +i128 : I128 -> Inspector GuiFormatter +i128 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +f32 : F32 -> Inspector GuiFormatter +f32 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +f64 : F64 -> Inspector GuiFormatter +f64 = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +dec : Dec -> Inspector GuiFormatter +dec = \num -> + f0 <- Inspect.custom + addNode f0 (num |> Num.toStr |> Text) + +addNode : GuiFormatter, Elem -> GuiFormatter +addNode = \@GuiFormatter { nodes }, node -> + @GuiFormatter { nodes: List.append nodes node } + +toGui : GuiFormatter -> Elem +toGui = \@GuiFormatter { nodes } -> Col nodes diff --git a/examples/BasicRustGUI/README.md b/examples/BasicRustGUI/README.md new file mode 100644 index 0000000..cc074a2 --- /dev/null +++ b/examples/BasicRustGUI/README.md @@ -0,0 +1,22 @@ +# Basic Rust GUI + +This is a basic GUI platform using Rust. + +1. Build the platform using `roc build.roc` +2. Run an example using `roc hello-gui.roc` or `roc inspect.gui.roc` + +## Hello-GUI Example + +```roc +file:hello-gui.roc +``` + +## Inspect-GUI Example + +```roc +file:inspect-gui.roc +``` + +## Developing + +You can re-generate the `roc_std` crate using `bash glue-gen.sh`. This will use the `RustGlue.roc` script in the roc repository to vendor the roc std library types for use in this platform. diff --git a/examples/BasicRustGUI/build.roc b/examples/BasicRustGUI/build.roc new file mode 100644 index 0000000..146fb66 --- /dev/null +++ b/examples/BasicRustGUI/build.roc @@ -0,0 +1,142 @@ +app [main] { + cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", +} + +import cli.Task exposing [Task] +import cli.Cmd +import cli.Stdout +import cli.Env +import cli.Arg +import cli.Arg.Opt +import cli.Arg.Cli + +## Builds the basic-gui example platform +## +## run with: roc ./build.roc +## +main : Task {} _ +main = + + cliParser = + Arg.Opt.maybeStr { short: "p", long: "roc", help: "Path to the roc executable. Can be just `roc` or a full path." } + |> Arg.Cli.finish { + name: "basic-gui-builder", + version: "", + authors: ["Luke Boswell "], + description: "Generates all files needed by Roc to use this platform.", + } + |> Arg.Cli.assertValid + + when Arg.Cli.parseOrDisplayMessage cliParser (Arg.list! {}) is + Ok parsedArgs -> run parsedArgs + Err errMsg -> Task.err (Exit 1 errMsg) + +run : Result Str err -> Task {} _ +run = \maybeRoc -> + + # rocCmd may be a path or just roc + rocCmd = maybeRoc |> Result.withDefault "roc" + rocVersion! rocCmd + + # target is MacosArm64, LinuxX64,... + target = getNativeTarget! + + stubLibPath = "platform/libapp.$(stubExt target)" + buildStubAppLib! rocCmd stubLibPath + cargoBuildHost! + copyHostLib! target + preprocessHost! rocCmd stubLibPath + + info! "Successfully built platform files!" + +rocVersion : Str -> Task {} _ +rocVersion = \rocCmd -> + + info! "Checking provided roc; executing `$(rocCmd) version`:" + + rocCmd + |> Cmd.exec ["version"] + |> Task.mapErr! RocVersionCheckFailed + +getNativeTarget : Task RocTarget _ +getNativeTarget = + + info! "Getting the native target ..." + + Env.platform + |> Task.await convertNativeTarget + +buildStubAppLib : Str, Str -> Task {} _ +buildStubAppLib = \rocCmd, stubLibPath -> + + info! "Building stubbed app shared library ..." + + rocCmd + |> Cmd.exec ["build", "--lib", "platform/libapp.roc", "--output", stubLibPath, "--optimize"] + |> Task.mapErr! ErrBuildingAppStub + +cargoBuildHost : Task {} _ +cargoBuildHost = + + info! "Building rust host ..." + + "cargo" + |> Cmd.exec ["build", "--release"] + |> Task.mapErr! ErrBuildingHostBinaries + +copyHostLib : RocTarget -> Task {} _ +copyHostLib = \target -> + hostBuildPath = "target/release/libhost.a" + hostDestPath = "platform/$(prebuiltStaticLibrary target)" + info! "Moving the prebuilt binary from $(hostBuildPath) to $(hostDestPath) ..." + "cp" + |> Cmd.exec [hostBuildPath, hostDestPath] + |> Task.mapErr! ErrMovingPrebuiltLegacyBinary + +RocTarget : [ + MacosArm64, + MacosX64, + LinuxArm64, + LinuxX64, + WindowsArm64, + WindowsX64, +] + +convertNativeTarget : _ -> Task RocTarget _ +convertNativeTarget = \{ os, arch } -> + when (os, arch) is + (MACOS, AARCH64) -> Task.ok MacosArm64 + (MACOS, X64) -> Task.ok MacosX64 + (LINUX, AARCH64) -> Task.ok LinuxArm64 + (LINUX, X64) -> Task.ok LinuxX64 + _ -> Task.err (UnsupportedNative os arch) + +stubExt : RocTarget -> Str +stubExt = \target -> + when target is + MacosX64 | MacosArm64 -> "dylib" + LinuxArm64 | LinuxX64 -> "so" + WindowsX64 | WindowsArm64 -> "dll" + +prebuiltStaticLibrary : RocTarget -> Str +prebuiltStaticLibrary = \target -> + when target is + MacosArm64 -> "macos-arm64.a" + MacosX64 -> "macos-x64.a" + LinuxArm64 -> "linux-arm64.a" + LinuxX64 -> "linux-x64.a" + WindowsArm64 -> "windows-arm64.lib" + WindowsX64 -> "windows-x64.lib" + +preprocessHost : Str, Str -> Task {} _ +preprocessHost = \rocCmd, stubLibPath -> + info! "Preprocessing surgical host ..." + surgicalBuildPath = "target/release/host" + + rocCmd + |> Cmd.exec ["preprocess-host", surgicalBuildPath, "platform/main.roc", stubLibPath] + |> Task.mapErr! ErrPreprocessingSurgicalBinary + +info : Str -> Task {} _ +info = \msg -> + Stdout.line! "\u(001b)[34mINFO:\u(001b)[0m $(msg)" diff --git a/examples/BasicRustGUI/crates/roc_host/Cargo.toml b/examples/BasicRustGUI/crates/roc_host/Cargo.toml new file mode 100644 index 0000000..87a9f6e --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "roc_host" +version = "0.0.1" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2021" + +[lib] +name = "roc_host" +path = "src/lib.rs" +crate-type = ["lib"] + +[dependencies] +arrayvec = "0.7.2" +libc = "0.2" +page_size = "0.4.2" +roc_std = { path = "../roc_std" } +cgmath = "0.18.0" +colored = "2.0.0" +copypasta = "0.7.1" +fs_extra = "1.2.0" +futures = "0.3.17" +glyph_brush = "0.7.2" +log = "0.4.14" +nonempty = "0.7.0" +palette = "0.6.0" +pest = "2.1.3" +pest_derive = "2.1.0" +serde = { version = "1.0.130", features = ["derive"] } +snafu = { version = "0.6.10", features = ["backtraces"] } +threadpool = "1.8.1" +wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" } +wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" } +winit = "0.26.1" + +[features] +default = [] + +[dependencies.bytemuck] +features = ["derive"] +version = "1.7.2" diff --git a/examples/BasicRustGUI/crates/roc_host/Inconsolata-Regular.ttf b/examples/BasicRustGUI/crates/roc_host/Inconsolata-Regular.ttf new file mode 100644 index 0000000..3e54746 Binary files /dev/null and b/examples/BasicRustGUI/crates/roc_host/Inconsolata-Regular.ttf differ diff --git a/examples/BasicRustGUI/crates/roc_host/src/focus.rs b/examples/BasicRustGUI/crates/roc_host/src/focus.rs new file mode 100644 index 0000000..71b46b6 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/focus.rs @@ -0,0 +1,172 @@ +use crate::roc::{ElemId, RocElem, RocElemTag}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Focus { + focused: Option, + focused_ancestors: Vec<(ElemId, usize)>, +} + +impl Default for Focus { + fn default() -> Self { + Self { + focused: None, + focused_ancestors: Vec::new(), + } + } +} + +impl Focus { + pub fn focused_elem(&self) -> Option { + self.focused + } + + /// e.g. the user pressed Tab. + /// + /// This is in contrast to next_local, which advances within a button group. + /// For example, if I have three radio buttons in a group, pressing the + /// arrow keys will cycle through them over and over without exiting the group - + /// whereas pressing Tab will cycle through them once and then exit the group. + pub fn next_global(&mut self, root: &RocElem) { + match self.focused { + Some(focused) => { + // while let Some((ancestor_id, index)) = self.focused_ancestors.pop() { + // let ancestor = ancestor_id.elem(); + + // // TODO FIXME - right now this will re-traverse a lot of ground! To prevent this, + // // we should remember past indices searched, and tell the ancestors "hey stop searching when" + // // you reach these indices, because they were already covered previously. + // // One potentially easy way to do this: pass a min_index and max_index, and only look between those! + // // + // // Related idea: instead of doing .pop() here, iterate normally so we can `break;` after storing + // // `new_ancestors = Some(next_ancestors);` - this way, we still have access to the full ancestry, and + // // can maybe even pass it in to make it clear what work has already been done! + // if let Some((new_id, new_ancestors)) = + // Self::next_focusable_sibling(focused, Some(ancestor), Some(index)) + // { + // // We found the next element to focus, so record that. + // self.focused = Some(new_id); + + // // We got a path to the new focusable's ancestor(s), so add them to the path. + // // (This may restore some of the ancestors we've been .pop()-ing as we iterated.) + // self.focused_ancestors.extend(new_ancestors); + + // return; + // } + + // // Need to write a bunch of tests for this, especially tests of focus wrapping around - e.g. + // // what happens if it wraps around to a sibling? What happens if it wraps around to something + // // higher up the tree? Lower down the tree? What if nothing is focusable? + // // A separate question: what if we should have a separate text-to-speech concept separate from focus? + // } + } + None => { + // Nothing was focused in the first place, so try to focus the root. + if root.is_focusable() { + self.focused = Some(root.id()); + self.focused_ancestors = Vec::new(); + } else if let Some((new_id, new_ancestors)) = + Self::next_focusable_sibling(root, None, None) + { + // If the root itself is not focusable, use its next focusable sibling. + self.focused = Some(new_id); + self.focused_ancestors = new_ancestors; + } + + // Regardless of whether we found a focusable Elem, we're done. + return; + } + } + } + + /// Return the next focusable sibling element after this one. + /// If this element has no siblings, or no *next* sibling after the given index + /// (e.g. the given index refers to the last element in a Row element), return None. + fn next_focusable_sibling( + elem: &RocElem, + ancestor: Option<&RocElem>, + opt_index: Option, + ) -> Option<(ElemId, Vec<(ElemId, usize)>)> { + use RocElemTag::*; + + match elem.tag() { + Button | Text => None, + Row | Col => { + let children = unsafe { &elem.entry().row_or_col.children.as_slice() }; + let iter = match opt_index { + Some(focus_index) => children[0..focus_index].iter(), + None => children.iter(), + }; + + for child in iter { + if let Some(focused) = Self::next_focusable_sibling(child, ancestor, None) { + return Some(focused); + } + } + + None + } + } + } +} + +#[test] +fn next_global_button_root() { + use crate::roc::{ButtonStyles, RocElem}; + + let child = RocElem::text(""); + let root = RocElem::button(ButtonStyles::default(), child); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Buttons should be focusable, so advancing focus should give the button focus. + assert_eq!(focus.focused_elem(), Some(root.id())); + + // Since the button is at the root, advancing again should maintain focus on it. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), Some(root.id())); +} + +#[test] +fn next_global_text_root() { + let root = RocElem::text(""); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Text should not be focusable, so advancing focus should have no effect here. + assert_eq!(focus.focused_elem(), None); + + // Just to double-check, advancing a second time should not change this. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), None); +} + +#[test] +fn next_global_row() { + use crate::roc::{ButtonStyles, RocElem}; + + let child = RocElem::text(""); + let button = RocElem::button(ButtonStyles::default(), child); + let button_id = button.id(); + let root = RocElem::row(&[button] as &[_]); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Buttons should be focusable, so advancing focus should give the first button in the row focus. + assert_eq!(focus.focused_elem(), Some(button_id)); + + // Since the button is the only element in the row, advancing again should maintain focus on it. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), Some(button_id)); +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/colors.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/colors.rs new file mode 100644 index 0000000..e0932a1 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/colors.rs @@ -0,0 +1,50 @@ +use cgmath::Vector4; +use palette::{FromColor, Hsv, Srgb}; + +/// This order is optimized for what Roc will send +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Rgba { + a: f32, + b: f32, + g: f32, + r: f32, +} + +impl Rgba { + pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0); + + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Self { r, g, b, a } + } + + pub const fn to_array(self) -> [f32; 4] { + [self.r, self.b, self.g, self.a] + } + + pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> Self { + Self::from_hsba(hue, saturation, brightness, 1.0) + } + + pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> Self { + let rgb = Srgb::from_color(Hsv::new( + hue as f32, + (saturation as f32) / 100.0, + (brightness as f32) / 100.0, + )); + + Self::new(rgb.red, rgb.green, rgb.blue, alpha) + } +} + +impl From for [f32; 4] { + fn from(rgba: Rgba) -> Self { + rgba.to_array() + } +} + +impl From for Vector4 { + fn from(rgba: Rgba) -> Self { + Vector4::new(rgba.r, rgba.b, rgba.g, rgba.a) + } +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/buffer.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/buffer.rs new file mode 100644 index 0000000..68f6ace --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/buffer.rs @@ -0,0 +1,96 @@ +// Contains parts of https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Contains parts of https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By Héctor Ramón, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you Héctor Ramón and Iced contributors! + +use std::mem; + +use super::{quad::Quad, vertex::Vertex}; +use crate::graphics::primitives::rect::RectElt; +use wgpu::util::DeviceExt; + +pub struct RectBuffers { + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub quad_buffer: wgpu::Buffer, +} + +pub const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +pub const MAX_QUADS: usize = 1_000; + +pub fn create_rect_buffers( + gpu_device: &wgpu::Device, + cmd_encoder: &mut wgpu::CommandEncoder, + rects: &[RectElt], +) -> RectBuffers { + let vertex_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_VERTS), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + + let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: mem::size_of::() as u64 * MAX_QUADS as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let quads: Vec = rects.iter().map(to_quad).collect(); + + let buffer_size = (quads.len() as u64) * Quad::SIZE; + + let staging_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&quads), + usage: wgpu::BufferUsages::COPY_SRC, + }); + + cmd_encoder.copy_buffer_to_buffer(&staging_buffer, 0, &quad_buffer, 0, buffer_size); + + RectBuffers { + vertex_buffer, + index_buffer, + quad_buffer, + } +} + +pub fn to_quad(rect_elt: &RectElt) -> Quad { + Quad { + pos: rect_elt.rect.pos.into(), + width: rect_elt.rect.width, + height: rect_elt.rect.height, + color: (rect_elt.color.to_array()), + border_color: rect_elt.border_color.into(), + border_width: rect_elt.border_width, + } +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/mod.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/mod.rs new file mode 100644 index 0000000..0215941 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/mod.rs @@ -0,0 +1,5 @@ +pub mod buffer; +pub mod ortho; +pub mod pipelines; +pub mod quad; +pub mod vertex; diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/ortho.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/ortho.rs new file mode 100644 index 0000000..9e4348e --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/ortho.rs @@ -0,0 +1,117 @@ +use cgmath::{Matrix4, Ortho}; +use wgpu::util::DeviceExt; +use wgpu::{ + BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, + ShaderStages, +}; + +// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct Uniforms { + // We can't use cgmath with bytemuck directly so we'll have + // to convert the Matrix4 into a 4x4 f32 array + ortho: [[f32; 4]; 4], +} + +impl Uniforms { + fn new(w: u32, h: u32) -> Self { + let ortho: Matrix4 = Ortho:: { + left: 0.0, + right: w as f32, + bottom: h as f32, + top: 0.0, + near: -1.0, + far: 1.0, + } + .into(); + Self { + ortho: ortho.into(), + } + } +} + +// update orthographic buffer according to new window size +pub fn update_ortho_buffer( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, + ortho_buffer: &Buffer, + cmd_queue: &wgpu::Queue, +) { + let new_uniforms = Uniforms::new(inner_width, inner_height); + + let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[new_uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC, + }); + + // get a command encoder for the current frame + let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Resize"), + }); + + // overwrite the new buffer over the old one + encoder.copy_buffer_to_buffer( + &new_ortho_buffer, + 0, + ortho_buffer, + 0, + std::mem::size_of_val(vec![new_uniforms].as_slice()) as wgpu::BufferAddress, + ); + + cmd_queue.submit(Some(encoder.finish())); +} + +#[derive(Debug)] +pub struct OrthoResources { + pub buffer: Buffer, + pub bind_group_layout: BindGroupLayout, + pub bind_group: BindGroup, +} + +pub fn init_ortho( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, +) -> OrthoResources { + let uniforms = Uniforms::new(inner_width, inner_height); + + let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + // bind groups consist of extra resources that are provided to the shaders + let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("Ortho bind group layout"), + }); + + let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &ortho_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: ortho_buffer.as_entire_binding(), + }], + label: Some("Ortho bind group"), + }); + + OrthoResources { + buffer: ortho_buffer, + bind_group_layout: ortho_bind_group_layout, + bind_group: ortho_bind_group, + } +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/pipelines.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/pipelines.rs new file mode 100644 index 0000000..a0dc790 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/pipelines.rs @@ -0,0 +1,72 @@ +use super::ortho::{init_ortho, OrthoResources}; +use super::quad::Quad; +use super::vertex::Vertex; +use std::borrow::Cow; + +pub struct RectResources { + pub pipeline: wgpu::RenderPipeline, + pub ortho: OrthoResources, +} + +pub fn make_rect_pipeline( + gpu_device: &wgpu::Device, + surface_config: &wgpu::SurfaceConfiguration, +) -> RectResources { + let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device); + + let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&ortho.bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = create_render_pipeline( + gpu_device, + &pipeline_layout, + surface_config.format, + &wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/quad.wgsl"))), + }, + ); + + RectResources { pipeline, ortho } +} + +pub fn create_render_pipeline( + device: &wgpu::Device, + layout: &wgpu::PipelineLayout, + color_format: wgpu::TextureFormat, + shader_module_desc: &wgpu::ShaderModuleDescriptor, +) -> wgpu::RenderPipeline { + let shader = device.create_shader_module(shader_module_desc); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render pipeline"), + layout: Some(layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::DESC, Quad::DESC], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[wgpu::ColorTargetState { + format: color_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + operation: wgpu::BlendOperation::Add, + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + }, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrites::ALL, + }], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }) +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/quad.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/quad.rs new file mode 100644 index 0000000..f5f4f50 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/quad.rs @@ -0,0 +1,31 @@ +/// A polygon with 4 corners +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Quad { + pub pos: [f32; 2], + pub width: f32, + pub height: f32, + pub color: [f32; 4], + pub border_color: [f32; 4], + pub border_width: f32, +} + +// Safety: repr(C), and as defined will not having padding. +unsafe impl bytemuck::Pod for Quad {} +unsafe impl bytemuck::Zeroable for Quad {} + +impl Quad { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + 1 => Float32x2, + 2 => Float32, + 3 => Float32, + 4 => Float32x4, + 5 => Float32x4, + 6 => Float32, + ), + }; +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/vertex.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/vertex.rs new file mode 100644 index 0000000..f31b29b --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/lowlevel/vertex.rs @@ -0,0 +1,34 @@ +// Inspired by https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Inspired by https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By Héctor Ramón, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you Héctor Ramón and Iced contributors! +use bytemuck::{Pod, Zeroable}; + +#[repr(C)] +#[derive(Copy, Clone, Zeroable, Pod)] +pub struct Vertex { + pub _position: [f32; 2], +} + +impl Vertex { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + // position + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + ], + }; +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/mod.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/mod.rs new file mode 100644 index 0000000..0eb7fcd --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/mod.rs @@ -0,0 +1,4 @@ +pub mod colors; +pub mod lowlevel; +pub mod primitives; +pub mod style; diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/mod.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/mod.rs new file mode 100644 index 0000000..a9adb18 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/mod.rs @@ -0,0 +1,2 @@ +pub mod rect; +pub mod text; diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/rect.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/rect.rs new file mode 100644 index 0000000..8183fc6 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/rect.rs @@ -0,0 +1,27 @@ +use crate::graphics::colors::Rgba; +use cgmath::Vector2; + +#[derive(Debug, Copy, Clone)] +pub struct RectElt { + pub rect: Rect, + pub color: Rgba, + pub border_width: f32, + pub border_color: Rgba, +} + +/// These fields are ordered this way because in Roc, the corresponding stuct is: +/// +/// { top : F32, left : F32, width : F32, height : F32 } +/// +/// alphabetically, that's { height, left, top, width } - which works out to the same as: +/// +/// struct Rect { height: f32, pos: Vector2, width: f32 } +/// +/// ...because Vector2 is a repr(C) struct of { x: f32, y: f32 } +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct Rect { + pub height: f32, + pub pos: Vector2, + pub width: f32, +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/text.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/text.rs new file mode 100644 index 0000000..de87bbd --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/primitives/text.rs @@ -0,0 +1,144 @@ +// Adapted from https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the COPYRIGHT +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +use crate::graphics::colors::Rgba; +use crate::graphics::style::DEFAULT_FONT_SIZE; +use ab_glyph::{FontArc, Glyph, InvalidFont}; +use cgmath::{Vector2, Vector4}; +use glyph_brush::OwnedSection; +use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, Section}; + +use super::rect::Rect; + +#[derive(Debug)] +pub struct Text<'a> { + pub position: Vector2, + pub area_bounds: Vector2, + pub color: Rgba, + pub text: &'a str, + pub size: f32, + + #[allow(dead_code)] + pub visible: bool, + pub centered: bool, +} + +impl<'a> Default for Text<'a> { + fn default() -> Self { + Self { + position: (0.0, 0.0).into(), + area_bounds: (f32::INFINITY, f32::INFINITY).into(), + color: Rgba::WHITE, + text: "", + size: DEFAULT_FONT_SIZE, + visible: true, + centered: false, + } + } +} + +pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout { + wgpu_glyph::Layout::default().h_align(if text.centered { + wgpu_glyph::HorizontalAlign::Center + } else { + wgpu_glyph::HorizontalAlign::Left + }) +} + +#[allow(dead_code)] +fn section_from_text<'a>( + text: &'a Text, + layout: wgpu_glyph::Layout, +) -> wgpu_glyph::Section<'a> { + Section { + screen_position: text.position.into(), + bounds: text.area_bounds.into(), + layout, + ..Section::default() + } + .add_text( + wgpu_glyph::Text::new(text.text) + .with_color(text.color) + .with_scale(text.size), + ) +} + +#[allow(dead_code)] +pub fn owned_section_from_text(text: &Text) -> OwnedSection { + let layout = layout_from_text(text); + + OwnedSection { + screen_position: text.position.into(), + bounds: text.area_bounds.into(), + layout, + ..OwnedSection::default() + } + .add_text( + glyph_brush::OwnedText::new(text.text) + .with_color(Vector4::from(text.color)) + .with_scale(text.size), + ) +} + +#[allow(dead_code)] +pub fn owned_section_from_glyph_texts( + text: Vec, + screen_position: (f32, f32), + area_bounds: (f32, f32), + layout: wgpu_glyph::Layout, +) -> glyph_brush::OwnedSection { + glyph_brush::OwnedSection { + screen_position, + bounds: area_bounds, + layout, + text, + } +} + +#[allow(dead_code)] +pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) { + let layout = layout_from_text(text); + + let section = section_from_text(text, layout); + + glyph_brush.queue(section.clone()); +} + +#[allow(dead_code)] +fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect { + let position = glyph.glyph.position; + let px_scale = glyph.glyph.scale; + let width = glyph_width(&glyph.glyph); + let height = px_scale.y; + let top_y = glyph_top_y(&glyph.glyph); + + Rect { + pos: [position.x, top_y].into(), + width, + height, + } +} + +#[allow(dead_code)] +pub fn glyph_top_y(glyph: &Glyph) -> f32 { + let height = glyph.scale.y; + + glyph.position.y - height * 0.75 +} + +#[allow(dead_code)] +pub fn glyph_width(glyph: &Glyph) -> f32 { + glyph.scale.x * 0.4765 +} + +pub fn build_glyph_brush( + gpu_device: &wgpu::Device, + render_format: wgpu::TextureFormat, +) -> Result, InvalidFont> { + let inconsolata = FontArc::try_from_slice(include_bytes!("../../../Inconsolata-Regular.ttf"))?; + + Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format)) +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/shaders/quad.wgsl b/examples/BasicRustGUI/crates/roc_host/src/graphics/shaders/quad.wgsl new file mode 100644 index 0000000..a561e2f --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/shaders/quad.wgsl @@ -0,0 +1,60 @@ + + +struct Globals { + ortho: mat4x4; +}; + +@group(0) +@binding(0) +var globals: Globals; + +struct VertexInput { + @location(0) position: vec2; +}; + +struct Quad { + @location(1) pos: vec2; // can't use the name "position" twice for compatibility with metal on MacOS + @location(2) width: f32; + @location(3) height: f32; + @location(4) color: vec4; + @location(5) border_color: vec4; + @location(6) border_width: f32; +}; + +struct VertexOutput { + @builtin(position) position: vec4; + @location(0) color: vec4; + @location(1) border_color: vec4; + @location(2) border_width: f32; +}; + +@stage(vertex) +fn vs_main( + input: VertexInput, + quad: Quad +) -> VertexOutput { + + var transform: mat4x4 = mat4x4( + vec4(quad.width, 0.0, 0.0, 0.0), + vec4(0.0, quad.height, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(quad.pos, 0.0, 1.0) + ); + + var out: VertexOutput; + + out.position = globals.ortho * transform * vec4(input.position, 0.0, 1.0);; + out.color = quad.color; + out.border_color = quad.border_color; + out.border_width = quad.border_width; + + return out; +} + + +@stage(fragment) +fn fs_main( + input: VertexOutput +) -> @location(0) vec4 { + return input.color; +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/graphics/style.rs b/examples/BasicRustGUI/crates/roc_host/src/graphics/style.rs new file mode 100644 index 0000000..11e6090 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/graphics/style.rs @@ -0,0 +1 @@ +pub const DEFAULT_FONT_SIZE: f32 = 30.0; diff --git a/examples/BasicRustGUI/crates/roc_host/src/gui.rs b/examples/BasicRustGUI/crates/roc_host/src/gui.rs new file mode 100644 index 0000000..2f98bd2 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/gui.rs @@ -0,0 +1,657 @@ +use crate::{ + graphics::{ + colors::Rgba, + lowlevel::buffer::create_rect_buffers, + lowlevel::{buffer::MAX_QUADS, ortho::update_ortho_buffer}, + lowlevel::{buffer::QUAD_INDICES, pipelines}, + primitives::{ + rect::{Rect, RectElt}, + text::build_glyph_brush, + }, + }, + roc::{RocElem, RocElemTag}, +}; +use cgmath::{Vector2, Vector4}; +use glyph_brush::OwnedSection; +use pipelines::RectResources; +use roc_std::RocStr; +use std::error::Error; +use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView}; +use wgpu_glyph::{GlyphBrush, GlyphCruncher}; +use winit::{ + dpi::PhysicalSize, + event, + event::{Event, ModifiersState}, + event_loop::ControlFlow, + platform::run_return::EventLoopExtRunReturn, +}; + +// Inspired by: +// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license +// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license +// +// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/ + +fn run_event_loop(title: &str, root: RocElem) -> Result<(), Box> { + // Open window and create a surface + let mut event_loop = winit::event_loop::EventLoop::new(); + + let window = winit::window::WindowBuilder::new() + .with_inner_size(PhysicalSize::new(1900.0, 1000.0)) + .with_title(title) + .build(&event_loop) + .unwrap(); + + let instance = wgpu::Instance::new(wgpu::Backends::all()); + + let surface = unsafe { instance.create_surface(&window) }; + + // Initialize GPU + let (gpu_device, cmd_queue) = futures::executor::block_on(async { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .expect( + r#"Request adapter + If you're running this from inside nix, run with: + `nixVulkanIntel `. + See extra docs here: github.com/guibou/nixGL + "#, + ); + + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await + .expect("Request device") + }); + + // Create staging belt and a local pool + let mut staging_belt = wgpu::util::StagingBelt::new(1024); + let mut local_pool = futures::executor::LocalPool::new(); + let local_spawner = local_pool.spawner(); + + // Prepare swap chain + let render_format = wgpu::TextureFormat::Bgra8Unorm; + let mut size = window.inner_size(); + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }; + + surface.configure(&gpu_device, &surface_config); + + let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config); + + let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; + + let is_animating = true; + + let mut keyboard_modifiers = ModifiersState::empty(); + + // Render loop + window.request_redraw(); + + event_loop.run_return(|event, _, control_flow| { + // TODO dynamically switch this on/off depending on whether any + // animations are running. Should conserve CPU usage and battery life! + if is_animating { + *control_flow = ControlFlow::Poll; + } else { + *control_flow = ControlFlow::Wait; + } + + match event { + //Close + Event::WindowEvent { + event: event::WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + //Resize + Event::WindowEvent { + event: event::WindowEvent::Resized(new_size), + .. + } => { + size = new_size; + + surface.configure( + &gpu_device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ); + + update_ortho_buffer( + size.width, + size.height, + &gpu_device, + &rect_resources.ortho.buffer, + &cmd_queue, + ); + } + //Received Character + Event::WindowEvent { + event: event::WindowEvent::ReceivedCharacter(_ch), + .. + } => { + // let input_outcome_res = + // app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers); + // if let Err(e) = input_outcome_res { + // print_err(&e) + // } else if let Ok(InputOutcome::Ignored) = input_outcome_res { + // println!("Input '{}' ignored!", ch); + // } + todo!("TODO handle character input"); + } + //Keyboard Input + Event::WindowEvent { + event: event::WindowEvent::KeyboardInput { input: _, .. }, + .. + } => { + // if let Some(virtual_keycode) = input.virtual_keycode { + // if let Some(ref mut ed_model) = app_model.ed_model_opt { + // if ed_model.has_focus { + // let keydown_res = keyboard_input::handle_keydown( + // input.state, + // virtual_keycode, + // keyboard_modifiers, + // &mut app_model, + // ); + + // if let Err(e) = keydown_res { + // print_err(&e) + // } + // } + // } + // } + // TODO todo!("TODO handle keyboard input"); + } + //Modifiers Changed + Event::WindowEvent { + event: event::WindowEvent::ModifiersChanged(modifiers), + .. + } => { + keyboard_modifiers = modifiers; + } + Event::RedrawRequested { .. } => { + // Get a command cmd_encoder for the current frame + let mut cmd_encoder = + gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Redraw"), + }); + + let surface_texture = surface + .get_current_texture() + .expect("Failed to acquire next SwapChainTexture"); + + let view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + // for text_section in &rects_and_texts.text_sections_behind { + // let borrowed_text = text_section.to_borrowed(); + + // glyph_brush.queue(borrowed_text); + // } + + // draw first layer of text + // glyph_brush + // .draw_queued( + // &gpu_device, + // &mut staging_belt, + // &mut cmd_encoder, + // &view, + // size.width, + // size.height, + // ) + // .expect("Failed to draw first layer of text."); + + // draw rects on top of first text layer + // draw_rects( + // &rects_and_texts.rects_front, + // &mut cmd_encoder, + // &view, + // &gpu_device, + // &rect_resources, + // wgpu::LoadOp::Load, + // ); + + // TODO use with_capacity based on some heuristic + let (_bounds, drawable) = to_drawable( + &root, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + &mut glyph_brush, + ); + + process_drawable( + drawable, + &mut staging_belt, + &mut glyph_brush, + &mut cmd_encoder, + &view, + &gpu_device, + &rect_resources, + wgpu::LoadOp::Load, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + ); + + // for text_section in &rects_and_texts.text_sections_front { + // let borrowed_text = text_section.to_borrowed(); + + // glyph_brush.queue(borrowed_text); + // } + + // draw text + // glyph_brush + // .draw_queued( + // &gpu_device, + // &mut staging_belt, + // &mut cmd_encoder, + // &view, + // size.width, + // size.height, + // ) + // .expect("Failed to draw queued text."); + + staging_belt.finish(); + cmd_queue.submit(Some(cmd_encoder.finish())); + surface_texture.present(); + + // Recall unused staging buffers + use futures::task::SpawnExt; + + local_spawner + .spawn(staging_belt.recall()) + .expect("Recall staging belt"); + + local_pool.run_until_stalled(); + } + _ => { + *control_flow = winit::event_loop::ControlFlow::Wait; + } + } + }); + + // Done, let's just exit... and don't bother cleaning anything up + // for this simple example + std::process::exit(0); +} + +fn draw_rects( + all_rects: &[RectElt], + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, +) { + let rect_buffers = create_rect_buffers(gpu_device, cmd_encoder, all_rects); + + let mut render_pass = begin_render_pass(cmd_encoder, texture_view, load_op); + + render_pass.set_pipeline(&rect_resources.pipeline); + render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); + + render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, rect_buffers.quad_buffer.slice(..)); + + render_pass.set_index_buffer( + rect_buffers.index_buffer.slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..MAX_QUADS as u32); +} + +fn begin_render_pass<'a>( + cmd_encoder: &'a mut CommandEncoder, + texture_view: &'a TextureView, + load_op: LoadOp, +) -> RenderPass<'a> { + cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachment { + view: texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: load_op, + store: true, + }, + }], + depth_stencil_attachment: None, + label: None, + }) +} + +pub fn render(title: RocStr, root: RocElem) { + run_event_loop(title.as_str(), root).expect("Error running event loop"); +} + +#[derive(Copy, Clone, Debug, Default)] +struct Bounds { + width: f32, + height: f32, +} + +#[derive(Clone, Debug)] +struct Drawable { + bounds: Bounds, + content: DrawableContent, +} + +#[derive(Clone, Debug)] +enum DrawableContent { + /// This stores an actual Section because an earlier step needs to know the bounds of + /// the text, and making a Section is a convenient way to compute those bounds. + Text(OwnedSection, Vector2), + FillRect { + color: Rgba, + border_width: f32, + border_color: Rgba, + }, + Multi(Vec), + Offset(Vec<(Vector2, Drawable)>), +} + +#[allow(clippy::too_many_arguments)] +fn process_drawable( + drawable: Drawable, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + // TODO iterate through drawables, + // calculating a pos using offset, + // calling draw and updating bounding boxes + let pos: Vector2 = (0.0, 0.0).into(); + + draw( + drawable.bounds, + drawable.content, + pos, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); +} + +#[allow(clippy::too_many_arguments)] +fn draw( + bounds: Bounds, + content: DrawableContent, + pos: Vector2, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + use DrawableContent::*; + + match content { + Text(section, offset) => { + glyph_brush.queue(section.with_screen_position(pos + offset).to_borrowed()); + + glyph_brush + .draw_queued( + gpu_device, + staging_belt, + cmd_encoder, + texture_view, + texture_size.width as u32, // TODO why do we make these be u32 and then cast to f32 in orthorgraphic_projection? + texture_size.height as u32, + ) + .expect("Failed to draw text element"); + } + FillRect { + color, + border_width, + border_color, + } => { + // TODO store all these colors and things in FillRect + let rect_elt = RectElt { + rect: Rect { + pos, + width: bounds.width, + height: bounds.height, + }, + color, + border_width, + border_color, + }; + + // TODO inline draw_rects into here! + draw_rects( + &[rect_elt], + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + ); + } + Offset(children) => { + for (offset, child) in children.into_iter() { + draw( + child.bounds, + child.content, + pos + offset, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); + } + } + Multi(children) => { + for child in children.into_iter() { + draw( + child.bounds, + child.content, + pos, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); + } + } + } +} + +fn to_drawable( + elem: &RocElem, + bounds: Bounds, + glyph_brush: &mut GlyphBrush<()>, +) -> (Bounds, Drawable) { + use RocElemTag::*; + + match elem.tag() { + Button => { + let button = unsafe { &elem.entry().button }; + let styles = button.styles; + let (child_bounds, child_drawable) = to_drawable(&button.child, bounds, glyph_brush); + + let button_drawable = Drawable { + bounds: child_bounds, + content: DrawableContent::FillRect { + color: styles.bg_color, + border_width: styles.border_width, + border_color: styles.border_color, + }, + }; + + let drawable = Drawable { + bounds: child_bounds, + content: DrawableContent::Multi(vec![button_drawable, child_drawable]), + }; + + (child_bounds, drawable) + } + Text => { + // TODO let text color and font settings inherit from parent + let text = unsafe { &elem.entry().text }; + let is_centered = true; // TODO don't hardcode this + let layout = wgpu_glyph::Layout::default().h_align(if is_centered { + wgpu_glyph::HorizontalAlign::Center + } else { + wgpu_glyph::HorizontalAlign::Left + }); + + let section = owned_section_from_str(text.as_str(), bounds, layout); + + // Calculate the bounds and offset by measuring glyphs + let text_bounds; + let offset; + + match glyph_brush.glyph_bounds(section.to_borrowed()) { + Some(glyph_bounds) => { + text_bounds = Bounds { + width: glyph_bounds.max.x - glyph_bounds.min.x, + height: glyph_bounds.max.y - glyph_bounds.min.y, + }; + + offset = (-glyph_bounds.min.x, -glyph_bounds.min.y).into(); + } + None => { + text_bounds = Bounds { + width: 0.0, + height: 0.0, + }; + + offset = (0.0, 0.0).into(); + } + } + + let drawable = Drawable { + bounds: text_bounds, + content: DrawableContent::Text(section, offset), + }; + + (text_bounds, drawable) + } + Row => { + let row = unsafe { &elem.entry().row_or_col }; + let mut final_bounds = Bounds::default(); + let mut offset: Vector2 = (0.0, 0.0).into(); + let mut offset_entries = Vec::with_capacity(row.children.len()); + + for child in row.children.as_slice().iter() { + let (child_bounds, child_drawable) = to_drawable(child, bounds, glyph_brush); + + offset_entries.push((offset, child_drawable)); + + // Make sure the final height is enough to fit this child + final_bounds.height = final_bounds.height.max(child_bounds.height); + + // Add the child's width to the final width + final_bounds.width += child_bounds.width; + + // Offset the next child to make sure it appears after this one. + offset.x += child_bounds.width; + } + + ( + final_bounds, + Drawable { + bounds: final_bounds, + content: DrawableContent::Offset(offset_entries), + }, + ) + } + Col => { + let col = unsafe { &elem.entry().row_or_col }; + let mut final_bounds = Bounds::default(); + let mut offset: Vector2 = (0.0, 0.0).into(); + let mut offset_entries = Vec::with_capacity(col.children.len()); + + for child in col.children.as_slice().iter() { + let (child_bounds, child_drawable) = to_drawable(child, bounds, glyph_brush); + + offset_entries.push((offset, child_drawable)); + + // Make sure the final width is enough to fit this child + final_bounds.width = final_bounds.width.max(child_bounds.width); + + // Add the child's height to the final height + final_bounds.height += child_bounds.height; + + // Offset the next child to make sure it appears after this one. + offset.y += child_bounds.height; + } + + ( + final_bounds, + Drawable { + bounds: final_bounds, + content: DrawableContent::Offset(offset_entries), + }, + ) + } + } +} + +fn owned_section_from_str( + string: &str, + bounds: Bounds, + layout: wgpu_glyph::Layout, +) -> OwnedSection { + // TODO don't hardcode any of this! + let color = Rgba::WHITE; + let size: f32 = 40.0; + + OwnedSection { + bounds: (bounds.width, bounds.height), + layout, + ..OwnedSection::default() + } + .add_text( + glyph_brush::OwnedText::new(string) + .with_color(Vector4::from(color)) + .with_scale(size), + ) +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/lib.rs b/examples/BasicRustGUI/crates/roc_host/src/lib.rs new file mode 100644 index 0000000..03a46b4 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/lib.rs @@ -0,0 +1,20 @@ +mod graphics; +mod gui; +mod rects_and_texts; +mod roc; + +use crate::roc::RocElem; + +extern "C" { + #[link_name = "roc__renderForHost_1_exposed"] + fn roc_render() -> RocElem; +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + let root_elem = unsafe { roc_render() }; + + gui::render("test title".into(), root_elem); + + 0 +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/rects_and_texts.rs b/examples/BasicRustGUI/crates/roc_host/src/rects_and_texts.rs new file mode 100644 index 0000000..e1ab52e --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/rects_and_texts.rs @@ -0,0 +1,71 @@ +use crate::graphics::primitives::rect::RectElt; +use crate::graphics::primitives::text::{owned_section_from_text, Text}; + +#[derive(Debug)] +pub struct RectsAndTexts { + pub text_sections_behind: Vec, // displayed in front of rect_behind, behind everything else + pub text_sections_front: Vec, // displayed in front of everything + pub rects_behind: Vec, // displayed at lowest depth + pub rects_front: Vec, // displayed in front of text_sections_behind, behind text_sections_front +} + +#[allow(dead_code)] +impl RectsAndTexts { + pub fn new() -> Self { + Self { + text_sections_behind: Vec::new(), + text_sections_front: Vec::new(), + rects_behind: Vec::new(), + rects_front: Vec::new(), + } + } + + pub fn init( + rects_behind: Vec, + texts_behind: Vec, + rects_front: Vec, + texts_front: Vec, + ) -> Self { + Self { + text_sections_behind: texts_behind + .iter() + .map(|txt| owned_section_from_text(txt)) + .collect(), + text_sections_front: texts_front + .iter() + .map(|txt| owned_section_from_text(txt)) + .collect(), + rects_behind, + rects_front, + } + } + + pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_behind.push(new_text_section); + } + + pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_front.push(new_text_section); + } + + pub fn add_rect_behind(&mut self, new_rect: RectElt) { + self.rects_behind.push(new_rect); + } + + pub fn add_rects_behind(&mut self, new_rects: Vec) { + self.rects_behind.extend(new_rects); + } + + pub fn add_rect_front(&mut self, new_rect: RectElt) { + self.rects_front.push(new_rect); + } + + pub fn extend(&mut self, rects_and_texts: RectsAndTexts) { + self.text_sections_behind + .extend(rects_and_texts.text_sections_behind); + self.text_sections_front + .extend(rects_and_texts.text_sections_front); + self.rects_behind.extend(rects_and_texts.rects_behind); + self.rects_front.extend(rects_and_texts.rects_front); + } +} diff --git a/examples/BasicRustGUI/crates/roc_host/src/roc.rs b/examples/BasicRustGUI/crates/roc_host/src/roc.rs new file mode 100644 index 0000000..8352b69 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host/src/roc.rs @@ -0,0 +1,146 @@ +use crate::graphics::colors::Rgba; +use core::ffi::c_void; +use core::mem::{self, ManuallyDrop}; +use roc_std::{RocList, RocStr}; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + libc::malloc(size) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + libc::realloc(c_ptr, new_size) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + libc::free(c_ptr) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(msg: *mut RocStr, tag_id: u32) { + match tag_id { + 0 => { + eprintln!("Roc standard library hit a panic: {}", &*msg); + } + 1 => { + eprintln!("Application hit a panic: {}", &*msg); + } + _ => unreachable!(), + } + std::process::exit(1); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dbg(loc: *mut RocStr, msg: *mut RocStr, src: *mut RocStr) { + eprintln!("[{}] {} = {}", &*loc, &*src, &*msg); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[repr(transparent)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +pub struct RocElem { + entry: *mut RocElemEntry, +} + +impl RocElem { + #[cfg(target_pointer_width = "64")] + pub fn tag(&self) -> RocElemTag { + // On a 64-bit system, the last 3 bits of the pointer store the tag + unsafe { mem::transmute::((self.entry as u8) & 0b0000_0111) } + } + + pub fn entry(&self) -> &RocElemEntry { + // On a 64-bit system, the last 3 bits of the pointer store the tag + let cleared = self.entry as usize & !0b111; + + unsafe { &*(cleared as *const RocElemEntry) } + } +} + +#[repr(u8)] +#[allow(unused)] // This is actually used, just via a mem::transmute from u8 +#[derive(Debug, Clone, Copy)] +pub enum RocElemTag { + Button = 0, + Col, + Row, + Text, +} + +#[repr(C)] +#[derive(Clone)] +pub struct RocButton { + pub child: ManuallyDrop, + pub styles: ButtonStyles, +} + +#[repr(C)] +#[derive(Clone)] +pub struct RocRowOrCol { + pub children: RocList, +} + +impl Clone for RocElem { + fn clone(&self) -> Self { + unsafe { + match self.tag() { + RocElemTag::Button => Self { + entry: &mut RocElemEntry { + button: (*self.entry).button.clone(), + }, + }, + RocElemTag::Text => Self { + entry: &mut RocElemEntry { + text: (*self.entry).text.clone(), + }, + }, + RocElemTag::Col | RocElemTag::Row => Self { + entry: &mut RocElemEntry { + row_or_col: (*self.entry).row_or_col.clone(), + }, + }, + } + } + } +} + +impl Drop for RocElem { + fn drop(&mut self) { + unsafe { + match self.tag() { + RocElemTag::Button => mem::drop(ManuallyDrop::take(&mut (*self.entry).button)), + RocElemTag::Text => mem::drop(ManuallyDrop::take(&mut (*self.entry).text)), + RocElemTag::Col | RocElemTag::Row => { + mem::drop(ManuallyDrop::take(&mut (*self.entry).row_or_col)) + } + } + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct ButtonStyles { + pub bg_color: Rgba, + pub border_color: Rgba, + pub border_width: f32, + pub text_color: Rgba, +} + +#[repr(C)] +pub union RocElemEntry { + pub button: ManuallyDrop, + pub text: ManuallyDrop, + pub row_or_col: ManuallyDrop, +} diff --git a/examples/BasicRustGUI/crates/roc_host_bin/Cargo.toml b/examples/BasicRustGUI/crates/roc_host_bin/Cargo.toml new file mode 100644 index 0000000..2b336dc --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host_bin/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "roc_host_bin" +version = "0.0.1" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2021" + +links = "app" + +[[bin]] +name = "host" +path = "src/main.rs" + +[dependencies] +roc_host = { path = "../roc_host" } diff --git a/examples/BasicRustGUI/crates/roc_host_bin/build.rs b/examples/BasicRustGUI/crates/roc_host_bin/build.rs new file mode 100644 index 0000000..e59f87f --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host_bin/build.rs @@ -0,0 +1,27 @@ +/// Link the stubbed app shared library file with the roc_host_bin exectuable +fn main() { + // The path to the platform directory within the workspace + // where the libapp.so file is generated by the build.roc script + let platform_path = workspace_dir().join("platform"); + + println!("cargo:rustc-link-search={}", platform_path.display()); + + #[cfg(not(windows))] + println!("cargo:rustc-link-lib=dylib=app"); + + #[cfg(windows)] + println!("cargo:rustc-link-lib=dylib=libapp"); +} + +/// Gets the path to the workspace root. +fn workspace_dir() -> std::path::PathBuf { + let output = std::process::Command::new(env!("CARGO")) + .arg("locate-project") + .arg("--workspace") + .arg("--message-format=plain") + .output() + .unwrap() + .stdout; + let cargo_path = std::path::Path::new(std::str::from_utf8(&output).unwrap().trim()); + cargo_path.parent().unwrap().to_path_buf() +} diff --git a/examples/BasicRustGUI/crates/roc_host_bin/src/main.rs b/examples/BasicRustGUI/crates/roc_host_bin/src/main.rs new file mode 100644 index 0000000..69ff418 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host_bin/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(roc_host::rust_main()); +} diff --git a/examples/BasicRustGUI/crates/roc_host_lib/Cargo.toml b/examples/BasicRustGUI/crates/roc_host_lib/Cargo.toml new file mode 100644 index 0000000..4eb86d6 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host_lib/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "host" +version = "0.0.1" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2021" + +[lib] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib"] + +[dependencies] +roc_host = { path = "../roc_host" } diff --git a/examples/BasicRustGUI/crates/roc_host_lib/src/lib.rs b/examples/BasicRustGUI/crates/roc_host_lib/src/lib.rs new file mode 100644 index 0000000..072347a --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_host_lib/src/lib.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn main() { + _ = roc_host::rust_main(); +} diff --git a/examples/BasicRustGUI/crates/roc_std/Cargo.toml b/examples/BasicRustGUI/crates/roc_std/Cargo.toml new file mode 100644 index 0000000..8318bda --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "roc_std" +description = "Rust representations of Roc data structures" + +authors = ["The Roc Contributors"] +edition = "2021" +license = "UPL-1.0" +repository = "https://github.com/roc-lang/roc" +version = "0.0.1" + +[dependencies] +arrayvec = "0.7.2" +serde = { version = "1.0.153", optional = true } +static_assertions = "1.1.0" + +[dev-dependencies] +libc = "0.2.139" +pretty_assertions = "1.3.0" +quickcheck = "1.0.3" +quickcheck_macros = "1.0.0" +serde_json = "1.0.94" + +[features] +serde = ["dep:serde"] +std = [] + +[package.metadata.cargo-udeps.ignore] +development = ["quickcheck_macros", "serde_json"] diff --git a/examples/BasicRustGUI/crates/roc_std/src/lib.rs b/examples/BasicRustGUI/crates/roc_std/src/lib.rs new file mode 100644 index 0000000..30cdcdb --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/src/lib.rs @@ -0,0 +1,559 @@ +//! Provides Rust representations of Roc data structures. +// #![cfg_attr(not(feature = "std"), no_std)] +#![crate_type = "lib"] + +use arrayvec::ArrayString; +use core::cmp::Ordering; +use core::ffi::c_void; +use core::fmt::{self, Debug}; +use core::hash::{Hash, Hasher}; +use core::mem::{ManuallyDrop, MaybeUninit}; +use core::ops::Drop; +use core::str; + +mod roc_box; +mod roc_dict; +mod roc_list; +mod roc_set; +mod roc_str; +mod storage; + +pub use roc_box::RocBox; +pub use roc_dict::RocDict; +pub use roc_list::{RocList, SendSafeRocList}; +pub use roc_set::RocSet; +pub use roc_str::{InteriorNulError, RocStr, SendSafeRocStr}; +pub use storage::Storage; + +// A list of C functions that are being imported +extern "C" { + pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void; + pub fn roc_realloc( + ptr: *mut c_void, + new_size: usize, + old_size: usize, + alignment: u32, + ) -> *mut c_void; + pub fn roc_dealloc(ptr: *mut c_void, alignment: u32); + pub fn roc_panic(c_ptr: *mut c_void, tag_id: u32); + pub fn roc_dbg(loc: *mut c_void, msg: *mut c_void, src: *mut c_void); + pub fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void; +} + +pub fn roc_alloc_refcounted() -> *mut T { + let size = core::mem::size_of::(); + let align = core::mem::align_of::(); + + roc_alloc_refcounted_help(size, align) as *mut T +} + +fn roc_alloc_refcounted_help(mut size: usize, mut align: usize) -> *mut u8 { + let prefix = if align > 8 { 16 } else { 8 }; + size += prefix; + align = align.max(core::mem::size_of::()); + + unsafe { + let allocation_ptr = roc_alloc(size, align as _) as *mut u8; + let data_ptr = allocation_ptr.add(prefix); + let storage_ptr = (data_ptr as *mut crate::Storage).sub(1); + + *storage_ptr = Storage::new_reference_counted(); + + data_ptr + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RocOrder { + Eq = 0, + Gt = 1, + Lt = 2, +} + +/// Like a Rust `Result`, but following Roc's ABI instead of Rust's. +/// (Using Rust's `Result` instead of this will not work properly with Roc code!) +/// +/// This can be converted to/from a Rust `Result` using `.into()` +#[repr(C)] +pub struct RocResult { + payload: RocResultPayload, + tag: RocResultTag, +} + +impl Debug for RocResult +where + T: Debug, + E: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_result_of_refs() { + Ok(payload) => { + f.write_str("RocOk(")?; + payload.fmt(f)?; + f.write_str(")") + } + Err(payload) => { + f.write_str("RocErr(")?; + payload.fmt(f)?; + f.write_str(")") + } + } + } +} + +impl Eq for RocResult +where + T: Eq, + E: Eq, +{ +} + +impl PartialEq for RocResult +where + T: PartialEq, + E: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.as_result_of_refs() == other.as_result_of_refs() + } +} + +impl Ord for RocResult +where + T: Ord, + E: Ord, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.as_result_of_refs().cmp(&other.as_result_of_refs()) + } +} + +impl PartialOrd for RocResult +where + T: PartialOrd, + E: PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.as_result_of_refs() + .partial_cmp(&other.as_result_of_refs()) + } +} + +impl Hash for RocResult +where + T: Hash, + E: Hash, +{ + fn hash(&self, state: &mut H) { + self.as_result_of_refs().hash(state) + } +} + +impl Clone for RocResult +where + T: Clone, + E: Clone, +{ + fn clone(&self) -> Self { + match self.as_result_of_refs() { + Ok(payload) => RocResult::ok(ManuallyDrop::into_inner(payload.clone())), + Err(payload) => RocResult::err(ManuallyDrop::into_inner(payload.clone())), + } + } +} + +impl RocResult { + pub fn ok(payload: T) -> Self { + Self { + tag: RocResultTag::RocOk, + payload: RocResultPayload { + ok: ManuallyDrop::new(payload), + }, + } + } + + pub fn err(payload: E) -> Self { + Self { + tag: RocResultTag::RocErr, + payload: RocResultPayload { + err: ManuallyDrop::new(payload), + }, + } + } + + pub fn is_ok(&self) -> bool { + matches!(self.tag, RocResultTag::RocOk) + } + + pub fn is_err(&self) -> bool { + matches!(self.tag, RocResultTag::RocErr) + } + + fn into_payload(self) -> RocResultPayload { + let mut value = MaybeUninit::uninit(); + + // copy the value into our MaybeUninit memory + unsafe { + core::ptr::copy_nonoverlapping(&self.payload, value.as_mut_ptr(), 1); + } + + // don't run the destructor on self; the `payload` briefly has two owners + // but only `value` is allowed to drop it (after initialization) + core::mem::forget(self); + + unsafe { value.assume_init() } + } + + fn as_result_of_refs(&self) -> Result<&ManuallyDrop, &ManuallyDrop> { + use RocResultTag::*; + + unsafe { + match self.tag { + RocOk => Ok(&self.payload.ok), + RocErr => Err(&self.payload.err), + } + } + } +} + +impl From> for Result { + fn from(roc_result: RocResult) -> Self { + use RocResultTag::*; + + let tag = roc_result.tag; + let payload = roc_result.into_payload(); + + unsafe { + match tag { + RocOk => Ok(ManuallyDrop::into_inner(payload.ok)), + RocErr => Err(ManuallyDrop::into_inner(payload.err)), + } + } + } +} + +impl From> for RocResult { + fn from(result: Result) -> Self { + match result { + Ok(payload) => RocResult::ok(payload), + Err(payload) => RocResult::err(payload), + } + } +} + +#[repr(u8)] +#[derive(Clone, Copy)] +enum RocResultTag { + RocErr = 0, + RocOk = 1, +} + +#[repr(C)] +union RocResultPayload { + ok: ManuallyDrop, + err: ManuallyDrop, +} + +impl Drop for RocResult { + fn drop(&mut self) { + use RocResultTag::*; + + match self.tag { + RocOk => unsafe { ManuallyDrop::drop(&mut self.payload.ok) }, + RocErr => unsafe { ManuallyDrop::drop(&mut self.payload.err) }, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[repr(C, align(16))] +pub struct RocDec([u8; 16]); + +impl Debug for RocDec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("RocDec") + .field(&self.0) + .field(&self.to_str()) + .finish() + } +} + +impl RocDec { + pub const MIN: Self = Self(i128::MIN.to_ne_bytes()); + pub const MAX: Self = Self(i128::MAX.to_ne_bytes()); + + const DECIMAL_PLACES: usize = 18; + const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES as u32); + const MAX_DIGITS: usize = 39; + const MAX_STR_LENGTH: usize = Self::MAX_DIGITS + 2; // + 2 here to account for the sign & decimal dot + + pub fn new(num: i128) -> Self { + Self(num.to_ne_bytes()) + } + + pub fn as_bits(&self) -> (i64, u64) { + let lower_bits = self.as_i128() as u64; + let upper_bits = (self.as_i128() >> 64) as i64; + (upper_bits, lower_bits) + } + + #[allow(clippy::should_implement_trait)] + pub fn from_str(value: &str) -> Option { + // Split the string into the parts before and after the "." + let mut parts = value.split('.'); + + let before_point = match parts.next() { + Some(answer) => answer, + None => { + return None; + } + }; + + let opt_after_point = parts + .next() + .map(|answer| &answer[..Ord::min(answer.len(), Self::DECIMAL_PLACES)]); + + // There should have only been one "." in the string! + if parts.next().is_some() { + return None; + } + + // Calculate the low digits - the ones after the decimal point. + let lo = match opt_after_point { + Some(after_point) => { + match after_point.parse::() { + Ok(answer) => { + // Translate e.g. the 1 from 0.1 into 10000000000000000000 + // by "restoring" the elided trailing zeroes to the number! + let trailing_zeroes = Self::DECIMAL_PLACES - after_point.len(); + let lo = answer * 10i128.pow(trailing_zeroes as u32); + + if !before_point.starts_with('-') { + lo + } else { + -lo + } + } + Err(_) => { + return None; + } + } + } + None => 0, + }; + + // Calculate the high digits - the ones before the decimal point. + let (is_pos, digits) = match before_point.chars().next() { + Some('+') => (true, &before_point[1..]), + Some('-') => (false, &before_point[1..]), + _ => (true, before_point), + }; + + let mut hi: i128 = 0; + macro_rules! adjust_hi { + ($op:ident) => {{ + for digit in digits.chars() { + if digit == '_' { + continue; + } + + let digit = digit.to_digit(10)?; + hi = hi.checked_mul(10)?; + hi = hi.$op(digit as _)?; + } + }}; + } + + if is_pos { + adjust_hi!(checked_add); + } else { + adjust_hi!(checked_sub); + } + + match hi.checked_mul(Self::ONE_POINT_ZERO) { + Some(hi) => hi.checked_add(lo).map(|num| Self(num.to_ne_bytes())), + None => None, + } + } + + /// This is private because RocDec being an i128 is an implementation detail + #[inline(always)] + fn as_i128(&self) -> i128 { + i128::from_ne_bytes(self.0) + } + + pub fn from_ne_bytes(bytes: [u8; 16]) -> Self { + Self(bytes) + } + + pub fn to_ne_bytes(&self) -> [u8; 16] { + self.0 + } + + fn to_str_helper(self, string: &mut ArrayString<{ Self::MAX_STR_LENGTH }>) -> &str { + use core::fmt::Write; + + if self.as_i128() == 0 { + return "0"; + } + + // The :019 in the following write! is computed as Self::DECIMAL_PLACES + 1. If you change + // Self::DECIMAL_PLACES, this assert should remind you to change that format string as well. + static_assertions::const_assert!(RocDec::DECIMAL_PLACES + 1 == 19); + + // By using the :019 format, we're guaranteeing that numbers less than 1, say 0.01234 + // get their leading zeros placed in bytes for us. i.e. `string = b"0012340000000000000"` + write!(string, "{:019}", self.as_i128()).unwrap(); + + let decimal_location = string.len() - Self::DECIMAL_PLACES; + // skip trailing zeros + let last_nonzero_byte = string.trim_end_matches('0').len(); + + if last_nonzero_byte <= decimal_location { + // This means that we've removed trailing zeros and are left with an integer. Our + // convention is to print these without a decimal point or trailing zeros, so we're done. + string.truncate(decimal_location); + return string.as_str(); + } + + // otherwise, we're dealing with a fraction, and need to insert the decimal dot + + // truncate all extra zeros off + string.truncate(last_nonzero_byte); + + // push a dummy character so we have space for the decimal dot + string.push('$'); + + // Safety: at any time, the string only contains ascii characters, so it is always valid utf8 + let bytes = unsafe { string.as_bytes_mut() }; + + // shift the fractional part by one + bytes.copy_within(decimal_location..last_nonzero_byte, decimal_location + 1); + + // and put in the decimal dot in the right place + bytes[decimal_location] = b'.'; + + string.as_str() + } + + pub fn to_str(&self) -> RocStr { + RocStr::from(self.to_str_helper(&mut ArrayString::new())) + } +} + +impl From for RocDec { + fn from(value: i32) -> Self { + RocDec::from_ne_bytes((RocDec::ONE_POINT_ZERO * value as i128).to_ne_bytes()) + } +} + +impl fmt::Display for RocDec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.to_str_helper(&mut ArrayString::new())) + } +} + +#[repr(C, align(16))] +#[derive(Clone, Copy, Eq, Default)] +pub struct I128([u8; 16]); + +impl From for I128 { + fn from(other: i128) -> Self { + Self(other.to_ne_bytes()) + } +} + +impl From for i128 { + fn from(other: I128) -> Self { + unsafe { core::mem::transmute::(other) } + } +} + +impl fmt::Debug for I128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + i128::from(*self).fmt(f) + } +} + +impl fmt::Display for I128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&i128::from(*self), f) + } +} + +impl PartialEq for I128 { + fn eq(&self, other: &Self) -> bool { + i128::from(*self).eq(&i128::from(*other)) + } +} + +impl PartialOrd for I128 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for I128 { + fn cmp(&self, other: &Self) -> Ordering { + i128::from(*self).cmp(&i128::from(*other)) + } +} + +impl Hash for I128 { + fn hash(&self, state: &mut H) { + i128::from(*self).hash(state); + } +} + +#[repr(C, align(16))] +#[derive(Clone, Copy, Eq, Default)] +pub struct U128([u8; 16]); + +impl From for U128 { + fn from(other: u128) -> Self { + Self(other.to_ne_bytes()) + } +} + +impl From for u128 { + fn from(other: U128) -> Self { + unsafe { core::mem::transmute::(other) } + } +} + +impl fmt::Debug for U128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + u128::from(*self).fmt(f) + } +} + +impl fmt::Display for U128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&u128::from(*self), f) + } +} + +impl PartialEq for U128 { + fn eq(&self, other: &Self) -> bool { + u128::from(*self).eq(&u128::from(*other)) + } +} + +impl PartialOrd for U128 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for U128 { + fn cmp(&self, other: &Self) -> Ordering { + u128::from(*self).cmp(&u128::from(*other)) + } +} + +impl Hash for U128 { + fn hash(&self, state: &mut H) { + u128::from(*self).hash(state); + } +} diff --git a/examples/BasicRustGUI/crates/roc_std/src/roc_box.rs b/examples/BasicRustGUI/crates/roc_std/src/roc_box.rs new file mode 100644 index 0000000..bb69f44 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/src/roc_box.rs @@ -0,0 +1,183 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::{roc_alloc, roc_dealloc, storage::Storage}; +use core::{ + cell::Cell, + cmp::{self, Ordering}, + fmt::Debug, + mem, + ops::Deref, + ptr::{self, NonNull}, +}; + +#[repr(C)] +pub struct RocBox { + contents: NonNull, +} + +impl RocBox { + pub fn new(contents: T) -> Self { + let alignment = Self::alloc_alignment(); + let bytes = mem::size_of::() + alignment; + + let ptr = unsafe { roc_alloc(bytes, alignment as u32) }; + + if ptr.is_null() { + todo!("Call roc_panic with the info that an allocation failed."); + } + + // Initialize the reference count. + let refcount_one = Storage::new_reference_counted(); + unsafe { ptr.cast::().write(refcount_one) }; + + let contents = unsafe { + let contents_ptr = ptr.cast::().add(alignment).cast::(); + + core::ptr::write(contents_ptr, contents); + + // We already verified that the original alloc pointer was non-null, + // and this one is the alloc pointer with `alignment` bytes added to it, + // so it should be non-null too. + NonNull::new_unchecked(contents_ptr) + }; + + Self { contents } + } + + /// # Safety + /// + /// The box must be unique in order to leak it safely + pub unsafe fn leak(self) -> *mut T { + let ptr = self.contents.as_ptr(); + core::mem::forget(self); + ptr + } + + #[inline(always)] + fn alloc_alignment() -> usize { + mem::align_of::().max(mem::align_of::()) + } + + pub fn into_inner(self) -> T { + unsafe { ptr::read(self.contents.as_ptr()) } + } + + fn storage(&self) -> &Cell { + let alignment = Self::alloc_alignment(); + + unsafe { + &*self + .contents + .as_ptr() + .cast::() + .sub(alignment) + .cast::>() + } + } +} + +impl Deref for RocBox { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { self.contents.as_ref() } + } +} + +impl PartialEq> for RocBox +where + T: PartialEq, +{ + fn eq(&self, other: &RocBox) -> bool { + self.deref() == other.deref() + } +} + +impl Eq for RocBox where T: Eq {} + +impl PartialOrd> for RocBox +where + T: PartialOrd, +{ + fn partial_cmp(&self, other: &RocBox) -> Option { + let self_contents = unsafe { self.contents.as_ref() }; + let other_contents = unsafe { other.contents.as_ref() }; + + self_contents.partial_cmp(other_contents) + } +} + +impl Ord for RocBox +where + T: Ord, +{ + fn cmp(&self, other: &Self) -> Ordering { + let self_contents = unsafe { self.contents.as_ref() }; + let other_contents = unsafe { other.contents.as_ref() }; + + self_contents.cmp(other_contents) + } +} + +impl core::hash::Hash for RocBox { + fn hash(&self, state: &mut H) { + self.contents.hash(state) + } +} + +impl Debug for RocBox +where + T: Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +impl Clone for RocBox { + fn clone(&self) -> Self { + let storage = self.storage(); + let mut new_storage = storage.get(); + + // Increment the reference count + if !new_storage.is_readonly() { + new_storage.increment_reference_count(); + storage.set(new_storage); + } + + Self { + contents: self.contents, + } + } +} + +impl Drop for RocBox { + fn drop(&mut self) { + let storage = self.storage(); + let contents = self.contents; + + // Decrease the list's reference count. + let mut new_storage = storage.get(); + let needs_dealloc = new_storage.decrease(); + + if needs_dealloc { + unsafe { + // Drop the stored contents. + let contents_ptr = contents.as_ptr(); + + mem::drop::(ptr::read(contents_ptr)); + + let alignment = Self::alloc_alignment(); + + // Release the memory. + roc_dealloc( + contents.as_ptr().cast::().sub(alignment).cast(), + alignment as u32, + ); + } + } else if !new_storage.is_readonly() { + // Write the storage back. + storage.set(new_storage); + } + } +} diff --git a/examples/BasicRustGUI/crates/roc_std/src/roc_dict.rs b/examples/BasicRustGUI/crates/roc_std/src/roc_dict.rs new file mode 100644 index 0000000..e46c3a4 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/src/roc_dict.rs @@ -0,0 +1,208 @@ +use crate::roc_list::RocList; +use core::{ + fmt::{self, Debug}, + hash::Hash, + mem::{align_of, ManuallyDrop}, +}; + +/// At the moment, Roc's Dict is just an association list. Its lookups are O(n) but +/// we haven't grown such big programs that it's a problem yet! +/// +/// We do some things in this data structure that only make sense because the +/// memory is managed in Roc: +/// +/// 1. We don't implement an [`IntoIterator`] that iterates over owned values, +/// since Roc owns the memory, not rust. +/// 2. We use a union for [`RocDictItem`] instead of just a struct. See the +/// comment on that data structure for why. +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct RocDict(RocList>); + +impl RocDict { + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn with_capacity(capacity: usize) -> Self { + Self(RocList::with_capacity(capacity)) + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|item| (item.key(), item.value())) + } + + pub fn iter_keys(&self) -> impl Iterator { + self.0.iter().map(|item| item.key()) + } + + pub fn iter_values(&self) -> impl Iterator { + self.0.iter().map(|item| item.value()) + } +} + +impl RocDict { + unsafe fn insert_unchecked(&mut self, _key: K, _val: V) { + todo!(); + } +} + +impl FromIterator<(K, V)> for RocDict { + fn from_iter>(into_iter: T) -> Self { + let src = into_iter.into_iter(); + let mut ret = Self::with_capacity(src.size_hint().0); + + for (key, val) in src { + unsafe { + ret.insert_unchecked(key, val); + } + } + + ret + } +} + +impl<'a, K, V> IntoIterator for &'a RocDict { + type Item = (&'a K, &'a V); + type IntoIter = IntoIter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + index: 0, + items: self.0.as_slice(), + } + } +} + +pub struct IntoIter<'a, K, V> { + index: usize, + items: &'a [RocDictItem], +} + +impl<'a, K, V> Iterator for IntoIter<'a, K, V> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option { + let item = self + .items + .get(self.index) + .map(|item| (item.key(), item.value())); + + self.index += 1; + + item + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.items.len() - self.index; + + (remaining, Some(remaining)) + } +} + +impl Debug for RocDict { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("RocDict ")?; + + f.debug_map().entries(self.iter()).finish() + } +} + +/// Roc is constructing these values according to its memory layout rules. +/// Specifically: +/// +/// 1. fields with the highest alignment go first +/// 2. then fields are sorted alphabetically +/// +/// Taken together, these mean that if we have a value with higher alignment +/// than the key, it'll be first in memory. Otherwise, the key will be first. +/// Fortunately, the total amount of memory doesn't change, so we can use a +/// union and disambiguate by examining the alignment of the key and value. +/// +/// However, note that this only makes sense while we're storing KV pairs +/// contiguously in memory. If we separate them at some point, we'll need to +/// change this implementation drastically! +#[derive(Eq)] +#[repr(C)] +union RocDictItem { + key_first: ManuallyDrop>, + value_first: ManuallyDrop>, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +struct KeyFirst { + key: K, + value: V, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +struct ValueFirst { + value: V, + key: K, +} + +impl RocDictItem { + fn key(&self) -> &K { + if align_of::() >= align_of::() { + unsafe { &self.key_first.key } + } else { + unsafe { &self.value_first.key } + } + } + + fn value(&self) -> &V { + if align_of::() >= align_of::() { + unsafe { &self.key_first.value } + } else { + unsafe { &self.value_first.value } + } + } +} + +impl Drop for RocDictItem { + fn drop(&mut self) { + if align_of::() >= align_of::() { + unsafe { ManuallyDrop::drop(&mut self.key_first) } + } else { + unsafe { ManuallyDrop::drop(&mut self.value_first) } + } + } +} + +impl PartialEq for RocDictItem { + fn eq(&self, other: &Self) -> bool { + self.key() == other.key() && self.value() == other.value() + } +} + +impl PartialOrd for RocDictItem { + fn partial_cmp(&self, other: &Self) -> Option { + self.key().partial_cmp(other.key()).map(|key_cmp| { + match self.value().partial_cmp(other.value()) { + Some(value_cmp) => key_cmp.then(value_cmp), + None => key_cmp, + } + }) + } +} + +impl Ord for RocDictItem { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.key() + .cmp(other.key()) + .then(self.value().cmp(other.value())) + } +} + +impl Hash for RocDictItem { + fn hash(&self, state: &mut H) { + self.key().hash(state); + self.value().hash(state); + } +} diff --git a/examples/BasicRustGUI/crates/roc_std/src/roc_list.rs b/examples/BasicRustGUI/crates/roc_std/src/roc_list.rs new file mode 100644 index 0000000..a7623bb --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/src/roc_list.rs @@ -0,0 +1,938 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use core::{ + cell::Cell, + cmp::{self, Ordering}, + ffi::c_void, + fmt::Debug, + hash::Hash, + intrinsics::copy_nonoverlapping, + iter::FromIterator, + mem::{self, ManuallyDrop}, + ops::{Deref, DerefMut}, + ptr::{self, NonNull}, +}; +use std::ops::Range; + +use crate::{roc_alloc, roc_dealloc, roc_realloc, storage::Storage}; + +#[cfg(feature = "serde")] +use core::marker::PhantomData; +#[cfg(feature = "serde")] +use serde::{ + de::{Deserializer, Visitor}, + ser::{SerializeSeq, Serializer}, + Deserialize, Serialize, +}; + +#[repr(C)] +pub struct RocList { + elements: Option>>, + length: usize, + // This technically points to directly after the refcount. + // This is an optimization that enables use one code path for regular lists and slices for geting the refcount ptr. + capacity_or_ref_ptr: usize, +} + +impl RocList { + #[inline(always)] + fn alloc_alignment() -> u32 { + mem::align_of::().max(mem::align_of::()) as u32 + } + + pub fn empty() -> Self { + Self { + elements: None, + length: 0, + capacity_or_ref_ptr: 0, + } + } + + /// Create an empty RocList with enough space preallocated to store + /// the requested number of elements. + pub fn with_capacity(num_elems: usize) -> Self { + Self { + elements: Some(Self::elems_with_capacity(num_elems)), + length: 0, + capacity_or_ref_ptr: num_elems, + } + } + + pub fn iter(&self) -> impl Iterator { + self.into_iter() + } + + /// Used for both roc_alloc and roc_realloc - given the number of elements, + /// returns the number of bytes needed to allocate, taking into account both the + /// size of the elements as well as the size of Storage. + fn alloc_bytes(num_elems: usize) -> usize { + next_multiple_of(mem::size_of::(), mem::align_of::()) + + (num_elems * mem::size_of::()) + } + + fn elems_with_capacity(num_elems: usize) -> NonNull> { + let alloc_ptr = unsafe { roc_alloc(Self::alloc_bytes(num_elems), Self::alloc_alignment()) }; + + Self::elems_from_allocation(NonNull::new(alloc_ptr).unwrap_or_else(|| { + todo!("Call roc_panic with the info that an allocation failed."); + })) + } + + fn elems_from_allocation(allocation: NonNull) -> NonNull> { + let offset = Self::alloc_alignment() - core::mem::size_of::<*const u8>() as u32; + let alloc_ptr = allocation.as_ptr(); + + unsafe { + let elem_ptr = Self::elem_ptr_from_alloc_ptr(alloc_ptr).cast::>(); + + // Initialize the reference count. + let rc_ptr = alloc_ptr.offset(offset as isize); + rc_ptr + .cast::() + .write(Storage::new_reference_counted()); + + // The original alloc pointer was non-null, and this one is the alloc pointer + // with `alignment` bytes added to it, so it should be non-null too. + NonNull::new_unchecked(elem_ptr) + } + } + + pub fn len(&self) -> usize { + self.length & (isize::MAX as usize) + } + + pub fn is_seamless_slice(&self) -> bool { + ((self.length | self.capacity_or_ref_ptr) as isize) < 0 + } + + pub fn capacity(&self) -> usize { + if !self.is_seamless_slice() { + self.capacity_or_ref_ptr + } else { + self.len() + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn is_unique(&self) -> bool { + if let Some(storage) = self.storage() { + storage.is_unique() + } else { + // If there is no storage, this list is empty. + // An empty list is always unique. + true + } + } + + pub fn is_readonly(&self) -> bool { + if let Some(storage) = self.storage() { + storage.is_readonly() + } else { + false + } + } + + pub fn as_mut_ptr(&mut self) -> *mut T { + self.as_mut_slice().as_mut_ptr() + } + + pub fn as_ptr(&self) -> *const T { + self.as_slice().as_ptr() + } + + /// Marks a list as readonly. This means that it will be leaked. + /// For constants passed in from platform to application, this may be reasonable. + /// + /// # Safety + /// + /// A value can be read-only in Roc for 3 reasons: + /// 1. The value is stored in read-only memory like a constant in the app. + /// 2. Our refcounting maxes out. When that happens, we saturate to read-only. + /// 3. This function is called + /// + /// Any value that is set to read-only will be leaked. + /// There is no way to tell how many references it has and if it is safe to free. + /// As such, only values that should have a static lifetime for the entire application run + /// should be considered for marking read-only. + pub unsafe fn set_readonly(&self) { + if let Some((_, storage)) = self.elements_and_storage() { + storage.set(Storage::Readonly); + } + } + + /// Note that there is no way to convert directly to a Vec. + /// + /// This is because RocList values are not allocated using the system allocator, so + /// handing off any heap-allocated bytes to a Vec would not work because its Drop + /// implementation would try to free those bytes using the wrong allocator. + /// + /// Instead, if you want a Rust Vec, you need to do a fresh allocation and copy the + /// bytes over - in other words, calling this `as_slice` method and then calling `to_vec` + /// on that. + pub fn as_slice(&self) -> &[T] { + self + } + + /// Note that there is no way to convert directly to a Vec. + /// + /// This is because RocList values are not allocated using the system allocator, so + /// handing off any heap-allocated bytes to a Vec would not work because its Drop + /// implementation would try to free those bytes using the wrong allocator. + /// + /// Instead, if you want a Rust Vec, you need to do a fresh allocation and copy the + /// bytes over - in other words, calling this `as_slice` method and then calling `to_vec` + /// on that. + pub fn as_mut_slice(&mut self) -> &mut [T] { + &mut *self + } + + #[inline(always)] + fn elements_and_storage(&self) -> Option<(NonNull>, &Cell)> { + let elements = self.elements?; + + let offset = match mem::align_of::() { + 16 => 1, + 8 | 4 | 2 | 1 => 0, + other => unreachable!("invalid alignment {other}"), + }; + + let storage = unsafe { &*self.ptr_to_allocation().cast::>().add(offset) }; + Some((elements, storage)) + } + + pub(crate) fn storage(&self) -> Option { + self.elements_and_storage() + .map(|(_, storage)| storage.get()) + } + + /// Useful for doing memcpy on the elements. Returns NULL if list is empty. + pub(crate) fn ptr_to_first_elem(&self) -> *const T { + unsafe { core::mem::transmute(self.elements) } + } + + /// Useful for doing memcpy on the underlying allocation. Returns NULL if list is empty. + pub(crate) fn ptr_to_allocation(&self) -> *mut c_void { + let alignment = Self::alloc_alignment() as usize; + if self.is_seamless_slice() { + ((self.capacity_or_ref_ptr << 1) - alignment) as *mut _ + } else { + unsafe { self.ptr_to_first_elem().cast::().sub(alignment) as *mut _ } + } + } + + #[allow(unused)] + pub(crate) fn ptr_to_refcount(&self) -> *mut c_void { + if self.is_seamless_slice() { + ((self.capacity_or_ref_ptr << 1) - std::mem::size_of::()) as *mut _ + } else { + unsafe { self.ptr_to_first_elem().cast::().sub(1) as *mut _ } + } + } + + unsafe fn elem_ptr_from_alloc_ptr(alloc_ptr: *mut c_void) -> *mut c_void { + unsafe { + alloc_ptr + .cast::() + .add(Self::alloc_alignment() as usize) + .cast() + } + } + + pub fn append(&mut self, value: T) { + self.push(value) + } + + pub fn push(&mut self, value: T) { + if self.capacity() <= self.len() { + // reserve space for (at least!) one more element + self.reserve(1); + } + + let elements = self.elements.unwrap().as_ptr(); + let append_ptr = unsafe { elements.add(self.len()) }; + + unsafe { + // Write the element into the slot, without dropping it. + ptr::write(append_ptr, ManuallyDrop::new(value)); + } + + // It's important that the length is increased one by one, to + // make sure that we don't drop uninitialized elements, even when + // a incrementing the reference count panics. + self.length += 1; + } + + /// # Safety + /// + /// - `bytes` must be allocated for `cap` elements + /// - `bytes` must be initialized for `len` elements + /// - `bytes` must be preceded by a correctly-aligned refcount (usize) + /// - `cap` >= `len` + pub unsafe fn from_raw_parts(bytes: *mut T, len: usize, cap: usize) -> Self { + Self { + elements: NonNull::new(bytes.cast()), + length: len, + capacity_or_ref_ptr: cap, + } + } +} + +impl RocList +where + T: Clone, +{ + pub fn from_slice(slice: &[T]) -> Self { + let mut list = Self::empty(); + list.extend_from_slice(slice); + list + } + + pub fn extend_from_slice(&mut self, slice: &[T]) { + // TODO: Can we do better for ZSTs? Alignment might be a problem. + if slice.is_empty() { + return; + } + + let new_len = self.len() + slice.len(); + let non_null_elements = if let Some((elements, storage)) = self.elements_and_storage() { + // Decrement the list's refence count. + let mut copy = storage.get(); + let is_unique = copy.decrease(); + + if is_unique { + // If we have enough capacity, we can add to the existing elements in-place. + if self.capacity() >= slice.len() { + elements + } else { + // There wasn't enough capacity, so we need a new allocation. + // Since this is a unique RocList, we can use realloc here. + let new_ptr = unsafe { + roc_realloc( + storage.as_ptr().cast(), + Self::alloc_bytes(new_len), + Self::alloc_bytes(self.capacity()), + Self::alloc_alignment(), + ) + }; + + self.capacity_or_ref_ptr = new_len; + + Self::elems_from_allocation(NonNull::new(new_ptr).unwrap_or_else(|| { + todo!("Reallocation failed"); + })) + } + } else { + if !copy.is_readonly() { + // Write the decremented reference count back. + storage.set(copy); + } + + // Allocate new memory. + self.capacity_or_ref_ptr = slice.len(); + let new_elements = Self::elems_with_capacity(slice.len()); + + // Copy the old elements to the new allocation. + unsafe { + copy_nonoverlapping(elements.as_ptr(), new_elements.as_ptr(), self.len()); + } + // Clear the seamless slice bit since we now have clear ownership. + self.length = self.len(); + + new_elements + } + } else { + self.capacity_or_ref_ptr = slice.len(); + Self::elems_with_capacity(slice.len()) + }; + + self.elements = Some(non_null_elements); + + let elements = self.elements.unwrap().as_ptr(); + + let append_ptr = unsafe { elements.add(self.len()) }; + + // Use .cloned() to increment the elements' reference counts, if needed. + for (i, new_elem) in slice.iter().cloned().enumerate() { + let dst = unsafe { append_ptr.add(i) }; + unsafe { + // Write the element into the slot, without dropping it. + ptr::write(dst, ManuallyDrop::new(new_elem)); + } + + // It's important that the length is increased one by one, to + // make sure that we don't drop uninitialized elements, even when + // a incrementing the reference count panics. + self.length += 1; + } + } +} + +impl RocList { + #[track_caller] + pub fn slice_range(&self, range: Range) -> Self { + match self.try_slice_range(range) { + Some(x) => x, + None => panic!("slice index out of range"), + } + } + + pub fn try_slice_range(&self, range: Range) -> Option { + if self.as_slice().get(range.start..range.end).is_none() { + None + } else { + // increment the refcount + std::mem::forget(self.clone()); + + let element_ptr = self.as_slice()[range.start..] + .as_ptr() + .cast::>(); + + let capacity_or_ref_ptr = + (self.ptr_to_first_elem() as usize) >> 1 | isize::MIN as usize; + + let roc_list = RocList { + elements: NonNull::new(element_ptr as *mut ManuallyDrop), + length: range.end - range.start, + capacity_or_ref_ptr, + }; + + Some(roc_list) + } + } + + /// Increase a RocList's capacity by at least the requested number of elements (possibly more). + /// + /// May return a new RocList, if the provided one was not unique. + pub fn reserve(&mut self, num_elems: usize) { + let new_len = num_elems + self.len(); + let new_elems; + let old_elements_ptr; + + match self.elements_and_storage() { + Some((elements, storage)) => { + if storage.get().is_unique() && !self.is_seamless_slice() { + unsafe { + let old_alloc = self.ptr_to_allocation(); + + // Try to reallocate in-place. + let new_alloc = roc_realloc( + old_alloc, + Self::alloc_bytes(new_len), + Self::alloc_bytes(self.capacity()), + Self::alloc_alignment(), + ); + + if new_alloc == old_alloc { + // We successfully reallocated in-place; we're done! + return; + } else { + // We got back a different allocation; copy the existing elements + // into it. We don't need to increment their refcounts because + // The existing allocation that references to them is now gone and + // no longer referencing them. + new_elems = Self::elems_from_allocation( + NonNull::new(new_alloc).unwrap_or_else(|| { + todo!("Reallocation failed"); + }), + ); + } + + // Note that realloc automatically deallocates the old allocation, + // so we don't need to call roc_dealloc here. + } + } else { + // Make a new allocation + new_elems = Self::elems_with_capacity(new_len); + old_elements_ptr = elements.as_ptr(); + + unsafe { + // Copy the old elements to the new allocation. + copy_nonoverlapping(old_elements_ptr, new_elems.as_ptr(), self.len()); + } + + // Decrease the current allocation's reference count. + let mut new_storage = storage.get(); + + if !new_storage.is_readonly() { + let needs_dealloc = new_storage.decrease(); + + if needs_dealloc { + // Unlike in Drop, do *not* decrement the refcounts of all the elements! + // The new allocation is referencing them, so instead of incrementing them all + // all just to decrement them again here, we neither increment nor decrement them. + unsafe { + roc_dealloc(self.ptr_to_allocation(), Self::alloc_alignment()); + } + } else { + // Write the storage back. + storage.set(new_storage); + } + } + } + } + None => { + // This is an empty list, so `reserve` is the same as `with_capacity`. + self.update_to(Self::with_capacity(new_len)); + + return; + } + } + + self.update_to(Self { + elements: Some(new_elems), + length: self.len(), + capacity_or_ref_ptr: new_len, + }); + } + + /// Replace self with a new version, without letting `drop` run in between. + fn update_to(&mut self, mut updated: Self) { + // We want to replace `self` with `updated` in a way that makes sure + // `self`'s `drop` never runs. This is the proper way to do that: + // swap them, and then forget the "updated" one (which is now pointing + // to the original allocation). + mem::swap(self, &mut updated); + mem::forget(updated); + } +} + +impl Deref for RocList { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + if let Some(elements) = self.elements { + let elements = ptr::slice_from_raw_parts(elements.as_ptr().cast::(), self.len()); + + unsafe { &*elements } + } else { + &[] + } + } +} + +impl DerefMut for RocList { + fn deref_mut(&mut self) -> &mut Self::Target { + if let Some(elements) = self.elements { + let ptr = elements.as_ptr().cast::(); + let elements = ptr::slice_from_raw_parts_mut(ptr, self.length); + + unsafe { &mut *elements } + } else { + &mut [] + } + } +} + +impl Default for RocList { + fn default() -> Self { + Self::empty() + } +} + +impl PartialEq> for RocList +where + T: PartialEq, +{ + fn eq(&self, other: &RocList) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl Eq for RocList where T: Eq {} + +impl PartialOrd> for RocList +where + T: PartialOrd, +{ + fn partial_cmp(&self, other: &RocList) -> Option { + // If one is longer than the other, use that as the ordering. + match self.len().partial_cmp(&other.len()) { + Some(Ordering::Equal) => {} + ord => return ord, + } + + // If they're the same length, compare their elements + for index in 0..self.len() { + match self[index].partial_cmp(&other[index]) { + Some(Ordering::Equal) => {} + ord => return ord, + } + } + + // Capacity is ignored for ordering purposes. + Some(Ordering::Equal) + } +} + +impl Ord for RocList +where + T: Ord, +{ + fn cmp(&self, other: &Self) -> Ordering { + // If one is longer than the other, use that as the ordering. + match self.len().cmp(&other.len()) { + Ordering::Equal => {} + ord => return ord, + } + + // If they're the same length, compare their elements + for index in 0..self.len() { + match self[index].cmp(&other[index]) { + Ordering::Equal => {} + ord => return ord, + } + } + + // Capacity is ignored for ordering purposes. + Ordering::Equal + } +} + +impl Debug for RocList +where + T: Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +impl Clone for RocList { + fn clone(&self) -> Self { + // Increment the reference count + if let Some((_, storage)) = self.elements_and_storage() { + let mut new_storage = storage.get(); + + if !new_storage.is_readonly() { + new_storage.increment_reference_count(); + storage.set(new_storage); + } + } + + Self { + elements: self.elements, + length: self.length, + capacity_or_ref_ptr: self.capacity_or_ref_ptr, + } + } +} + +impl Drop for RocList { + fn drop(&mut self) { + if let Some((elements, storage)) = self.elements_and_storage() { + // Decrease the list's reference count. + let mut new_storage = storage.get(); + + if !new_storage.is_readonly() { + let needs_dealloc = new_storage.decrease(); + + if needs_dealloc { + unsafe { + // Drop the stored elements. + for index in 0..self.len() { + ManuallyDrop::drop(&mut *elements.as_ptr().add(index)); + } + + // Release the memory. + roc_dealloc(self.ptr_to_allocation(), Self::alloc_alignment()); + } + } else { + // Write the storage back. + storage.set(new_storage); + } + } + } + } +} + +impl From<&[T]> for RocList +where + T: Clone, +{ + fn from(slice: &[T]) -> Self { + Self::from_slice(slice) + } +} + +impl From<[T; SIZE]> for RocList { + fn from(array: [T; SIZE]) -> Self { + Self::from_iter(array) + } +} + +impl<'a, T> IntoIterator for &'a RocList { + type Item = &'a T; + type IntoIter = core::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.as_slice().iter() + } +} + +impl Hash for RocList { + fn hash(&self, state: &mut H) { + // This is the same as Rust's Vec implementation, which + // just delegates to the slice implementation. It's a bit surprising + // that Hash::hash_slice doesn't automatically incorporate the length, + // but the slice implementation indeed does explicitly call self.len().hash(state); + // + // To verify, click the "source" links for: + // Vec: https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-Hash + // slice: https://doc.rust-lang.org/std/primitive.slice.html#impl-Hash + self.len().hash(state); + + Hash::hash_slice(self.as_slice(), state); + } +} + +impl FromIterator for RocList { + fn from_iter(into: I) -> Self + where + I: IntoIterator, + { + let iter = into.into_iter(); + + if core::mem::size_of::() == 0 { + let count = iter.count(); + return Self { + elements: Some(Self::elems_with_capacity(count)), + length: count, + capacity_or_ref_ptr: count, + }; + } + + let mut list = { + let (min_len, maybe_max_len) = iter.size_hint(); + let init_capacity = maybe_max_len.unwrap_or(min_len); + Self::with_capacity(init_capacity) + }; + + let mut elements = list.elements.unwrap().as_ptr(); + for new_elem in iter { + // If the size_hint didn't give us a max, we may need to grow. 1.5x seems to be good, based on: + // https://archive.ph/Z2R8w and https://github.com/facebook/folly/blob/1f2706/folly/docs/FBVector.md + if list.length == list.capacity() { + list.reserve(list.capacity() / 2); + elements = list.elements.unwrap().as_ptr(); + } + + unsafe { + elements + .add(list.length) + .write(ptr::read(&ManuallyDrop::new(new_elem))); + } + list.length += 1; + } + + list + } +} + +#[cfg(feature = "serde")] +impl Serialize for RocList { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for item in self { + seq.serialize_element(item)?; + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de, T> Deserialize<'de> for RocList +where + // TODO: I'm not sure about requiring clone here. Is that fine? Is that + // gonna mean lots of extra allocations? + T: Deserialize<'de> + core::clone::Clone, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(RocListVisitor::new()) + } +} + +// This is a RocList that is checked to ensure it is unique or readonly such that it can be sent between threads safely. +#[repr(transparent)] +pub struct SendSafeRocList(RocList); + +unsafe impl Send for SendSafeRocList where T: Send {} + +impl Clone for SendSafeRocList +where + T: Clone, +{ + fn clone(&self) -> Self { + if self.0.is_readonly() { + SendSafeRocList(self.0.clone()) + } else { + // To keep self send safe, this must copy. + SendSafeRocList(RocList::from_slice(&self.0)) + } + } +} + +impl From> for SendSafeRocList +where + T: Clone, +{ + fn from(l: RocList) -> Self { + if l.is_unique() || l.is_readonly() { + SendSafeRocList(l) + } else { + // This is not unique, do a deep copy. + // TODO: look into proper into_iter that takes ownership. + // Then this won't need clone and will skip and refcount inc and dec for each element. + SendSafeRocList(RocList::from_slice(&l)) + } + } +} + +impl From> for RocList { + fn from(l: SendSafeRocList) -> Self { + l.0 + } +} + +#[cfg(feature = "serde")] +struct RocListVisitor { + marker: PhantomData, +} + +#[cfg(feature = "serde")] +impl RocListVisitor { + fn new() -> Self { + RocListVisitor { + marker: PhantomData, + } + } +} + +#[cfg(feature = "serde")] +impl<'de, T> Visitor<'de> for RocListVisitor +where + T: Deserialize<'de> + core::clone::Clone, +{ + type Value = RocList; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(formatter, "a list of strings") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut out = match seq.size_hint() { + Some(hint) => RocList::with_capacity(hint), + None => RocList::empty(), + }; + + while let Some(next) = seq.next_element()? { + // TODO: it would be ideal to call `out.push` here, but we haven't + // implemented that yet! I think this is also why we need Clone. + out.extend_from_slice(&[next]) + } + + Ok(out) + } +} + +const fn next_multiple_of(lhs: usize, rhs: usize) -> usize { + match lhs % rhs { + 0 => lhs, + r => lhs + (rhs - r), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::RocDec; + + #[no_mangle] + pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + unsafe { libc::malloc(size) } + } + + #[no_mangle] + pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, + ) -> *mut c_void { + unsafe { libc::realloc(c_ptr, new_size) } + } + + #[no_mangle] + pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + unsafe { libc::free(c_ptr) } + } + + #[test] + fn compare_list_dec() { + // RocDec is special because it's alignment is 16 + let a = RocList::from_slice(&[RocDec::from(1), RocDec::from(2)]); + let b = RocList::from_slice(&[RocDec::from(1), RocDec::from(2)]); + + assert_eq!(a, b); + } + + #[test] + fn clone_list_dec() { + // RocDec is special because it's alignment is 16 + let a = RocList::from_slice(&[RocDec::from(1), RocDec::from(2)]); + let b = a.clone(); + + assert_eq!(a, b); + + drop(a); + drop(b); + } + + #[test] + fn compare_list_str() { + let a = RocList::from_slice(&[crate::RocStr::from("ab")]); + let b = RocList::from_slice(&[crate::RocStr::from("ab")]); + + assert_eq!(a, b); + + drop(a); + drop(b); + } + + #[test] + fn readonly_list_is_sendsafe() { + let x = RocList::from_slice(&[1, 2, 3, 4, 5]); + unsafe { x.set_readonly() }; + assert!(x.is_readonly()); + + let y = x.clone(); + let z = y.clone(); + + let safe_x = SendSafeRocList::from(x); + let new_x = RocList::from(safe_x); + assert!(new_x.is_readonly()); + assert!(y.is_readonly()); + assert!(z.is_readonly()); + assert_eq!(new_x.as_slice(), &[1, 2, 3, 4, 5]); + + let ptr = new_x.ptr_to_allocation(); + + drop(y); + drop(z); + drop(new_x); + + // free the underlying memory + unsafe { crate::roc_dealloc(ptr, std::mem::align_of::() as u32) } + } +} diff --git a/examples/BasicRustGUI/crates/roc_std/src/roc_set.rs b/examples/BasicRustGUI/crates/roc_std/src/roc_set.rs new file mode 100644 index 0000000..683045b --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/src/roc_set.rs @@ -0,0 +1,44 @@ +use crate::roc_dict::RocDict; +use core::{ + fmt::{self, Debug}, + hash::Hash, +}; + +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RocSet(RocDict); + +impl RocSet { + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + #[allow(unused)] + pub fn with_capacity(capacity: usize) -> Self { + Self(RocDict::with_capacity(capacity)) + } + + #[allow(unused)] + pub fn iter(&self) -> impl Iterator { + self.0.iter_keys() + } +} + +impl FromIterator for RocSet { + fn from_iter>(into_iter: I) -> Self { + Self(RocDict::from_iter( + into_iter.into_iter().map(|elem| (elem, ())), + )) + } +} + +impl Debug for RocSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("RocSet ")?; + + f.debug_set().entries(self.iter()).finish() + } +} diff --git a/examples/BasicRustGUI/crates/roc_std/src/roc_str.rs b/examples/BasicRustGUI/crates/roc_std/src/roc_str.rs new file mode 100644 index 0000000..ad170e7 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/src/roc_str.rs @@ -0,0 +1,1116 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +#[cfg(feature = "serde")] +use serde::{ + de::{Deserializer, Visitor}, + ser::Serializer, + Deserialize, Serialize, +}; + +use core::{ + cmp, + convert::TryFrom, + fmt, + hash::{self, Hash}, + mem::{self, size_of, ManuallyDrop}, + ops::{Deref, DerefMut}, + ptr, +}; + +#[cfg(feature = "std")] +use std::ffi::{CStr, CString}; +use std::{ops::Range, ptr::NonNull}; + +use crate::{roc_realloc, RocList}; + +#[repr(transparent)] +pub struct RocStr(RocStrInner); + +fn with_stack_bytes(length: usize, closure: F) -> T +where + F: FnOnce(*mut E) -> T, +{ + use crate::{roc_alloc, roc_dealloc}; + use core::mem::MaybeUninit; + + if length < RocStr::TEMP_STR_MAX_STACK_BYTES { + // TODO: once https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.uninit_array + // has become stabilized, use that here in order to do a precise + // stack allocation instead of always over-allocating to 64B. + let mut bytes: MaybeUninit<[u8; RocStr::TEMP_STR_MAX_STACK_BYTES]> = MaybeUninit::uninit(); + + closure(bytes.as_mut_ptr() as *mut E) + } else { + let align = core::mem::align_of::() as u32; + // The string is too long to stack-allocate, so + // do a heap allocation and then free it afterwards. + let ptr = unsafe { roc_alloc(length, align) } as *mut E; + let answer = closure(ptr); + + // Free the heap allocation. + unsafe { roc_dealloc(ptr.cast(), align) }; + + answer + } +} + +impl RocStr { + pub const SIZE: usize = core::mem::size_of::(); + pub const MASK: u8 = 0b1000_0000; + + pub const fn empty() -> Self { + Self(RocStrInner { + small_string: SmallString::empty(), + }) + } + + /// Create a string from bytes. + /// + /// # Safety + /// + /// `slice` must be valid UTF-8. + pub unsafe fn from_slice_unchecked(slice: &[u8]) -> Self { + if let Some(small_string) = unsafe { SmallString::try_from_utf8_bytes(slice) } { + Self(RocStrInner { small_string }) + } else { + let heap_allocated = RocList::from_slice(slice); + let big_string = + unsafe { std::mem::transmute::, BigString>(heap_allocated) }; + Self(RocStrInner { + heap_allocated: ManuallyDrop::new(big_string), + }) + } + } + + /// # Safety + /// + /// - `bytes` must be allocated for `cap` bytes + /// - `bytes` must be initialized for `len` bytes + /// - `bytes` must be preceded by a correctly-aligned refcount (usize) + /// - `bytes` must represent valid UTF-8 + /// - `cap` >= `len` + pub unsafe fn from_raw_parts(bytes: *mut u8, len: usize, cap: usize) -> Self { + if len <= SmallString::CAPACITY { + unsafe { + let slice = std::slice::from_raw_parts(bytes, len); + let small_string = SmallString::try_from_utf8_bytes(slice).unwrap_unchecked(); + Self(RocStrInner { small_string }) + } + } else { + Self(RocStrInner { + heap_allocated: ManuallyDrop::new(BigString { + elements: unsafe { NonNull::new_unchecked(bytes) }, + length: len, + capacity_or_alloc_ptr: cap, + }), + }) + } + } + + fn is_small_str(&self) -> bool { + unsafe { self.0.small_string.is_small_str() } + } + + fn as_enum_ref(&self) -> RocStrInnerRef { + if self.is_small_str() { + unsafe { RocStrInnerRef::SmallString(&self.0.small_string) } + } else { + unsafe { RocStrInnerRef::HeapAllocated(&self.0.heap_allocated) } + } + } + + pub fn capacity(&self) -> usize { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(big_string) => big_string.capacity(), + RocStrInnerRef::SmallString(_) => SmallString::CAPACITY, + } + } + + pub fn len(&self) -> usize { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(h) => h.len(), + RocStrInnerRef::SmallString(s) => s.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn is_unique(&self) -> bool { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(roc_list) => roc_list.is_unique(), + RocStrInnerRef::SmallString(_) => true, + } + } + + pub fn is_readonly(&self) -> bool { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(roc_list) => roc_list.is_readonly(), + RocStrInnerRef::SmallString(_) => false, + } + } + + /// Marks a str as readonly. This means that it will be leaked. + /// For constants passed in from platform to application, this may be reasonable. + /// + /// # Safety + /// + /// A value can be read-only in Roc for 3 reasons: + /// 1. The value is stored in read-only memory like a constant in the app. + /// 2. Our refcounting maxes out. When that happens, we saturate to read-only. + /// 3. This function is called + /// + /// Any value that is set to read-only will be leaked. + /// There is no way to tell how many references it has and if it is safe to free. + /// As such, only values that should have a static lifetime for the entire application run + /// should be considered for marking read-only. + pub unsafe fn set_readonly(&mut self) { + if self.is_small_str() { + /* do nothing */ + } else { + let big = unsafe { &mut self.0.heap_allocated }; + big.set_readonly() + } + } + + /// Note that there is no way to convert directly to a String. + /// + /// This is because RocStr values are not allocated using the system allocator, so + /// handing off any heap-allocated bytes to a String would not work because its Drop + /// implementation would try to free those bytes using the wrong allocator. + /// + /// Instead, if you want a Rust String, you need to do a fresh allocation and copy the + /// bytes over - in other words, calling this `as_str` method and then calling `to_string` + /// on that. + pub fn as_str(&self) -> &str { + self + } + + /// Create an empty RocStr with enough space preallocated to store + /// the requested number of bytes. + pub fn with_capacity(bytes: usize) -> Self { + if bytes <= SmallString::CAPACITY { + RocStr(RocStrInner { + small_string: SmallString::empty(), + }) + } else { + // The requested capacity won't fit in a small string; we need to go big. + RocStr(RocStrInner { + heap_allocated: ManuallyDrop::new(BigString::with_capacity(bytes)), + }) + } + } + + /// Increase a RocStr's capacity by at least the requested number of bytes (possibly more). + /// + /// May return a new RocStr, if the provided one was not unique. + pub fn reserve(&mut self, bytes: usize) { + if self.is_small_str() { + let small_str = unsafe { self.0.small_string }; + let target_cap = small_str.len() + bytes; + + if target_cap > SmallString::CAPACITY { + // The requested capacity won't fit in a small string; we need to go big. + let mut big_string = BigString::with_capacity(target_cap); + + unsafe { + std::ptr::copy_nonoverlapping( + self.as_bytes().as_ptr(), + big_string.ptr_to_first_elem(), + self.len(), + ) + }; + + big_string.length = self.len(); + big_string.capacity_or_alloc_ptr = target_cap; + + let mut updated = RocStr(RocStrInner { + heap_allocated: ManuallyDrop::new(big_string), + }); + + mem::swap(self, &mut updated); + mem::forget(updated); + } + } else { + let mut big_string = unsafe { ManuallyDrop::take(&mut self.0.heap_allocated) }; + + big_string.reserve(bytes); + + let mut updated = RocStr(RocStrInner { + heap_allocated: ManuallyDrop::new(big_string), + }); + + mem::swap(self, &mut updated); + mem::forget(updated); + } + } + + #[track_caller] + pub fn slice_range(&self, range: Range) -> Self { + match self.try_slice_range(range) { + Some(x) => x, + None => panic!("slice index out of range"), + } + } + + pub fn try_slice_range(&self, range: Range) -> Option { + if self.as_str().get(range.start..range.end).is_none() { + None + } else if range.end - range.start <= SmallString::CAPACITY && self.is_small_str() { + let slice = &self.as_bytes()[range]; + let small_string = + unsafe { SmallString::try_from_utf8_bytes(slice).unwrap_unchecked() }; + + // NOTE decrements `self` + Some(RocStr(RocStrInner { small_string })) + } else { + // increment the refcount + std::mem::forget(self.clone()); + + let big = unsafe { &self.0.heap_allocated }; + let ptr = unsafe { (self.as_bytes().as_ptr() as *mut u8).add(range.start) }; + + let heap_allocated = ManuallyDrop::new(BigString { + elements: unsafe { NonNull::new_unchecked(ptr) }, + length: (isize::MIN as usize) | (range.end - range.start), + capacity_or_alloc_ptr: (big.ptr_to_first_elem() as usize) >> 1, + }); + + Some(RocStr(RocStrInner { heap_allocated })) + } + } + + pub fn split_once(&self, delimiter: &str) -> Option<(Self, Self)> { + let (a, b) = self.as_str().split_once(delimiter)?; + + let x = self.slice_range(0..a.len()); + let y = self.slice_range(self.len() - b.len()..self.len()); + + Some((x, y)) + } + + pub fn split_whitespace(&self) -> SplitWhitespace<'_> { + SplitWhitespace(self.as_str().char_indices().peekable(), self) + } + + /// Returns the index of the first interior \0 byte in the string, or None if there are none. + fn first_nul_byte(&self) -> Option { + self.as_bytes().iter().position(|byte| *byte == 0) + } + + // If the string is under this many bytes, the with_terminator family + // of methods will allocate the terminated string on the stack when + // the RocStr is non-unique. + const TEMP_STR_MAX_STACK_BYTES: usize = 64; + + /// Like calling with_utf8_terminator passing \0 for the terminator, + /// except it can fail because a RocStr may contain \0 characters, + /// which a nul-terminated string must not. + pub fn utf8_nul_terminated T>( + self, + func: F, + ) -> Result { + if let Some(pos) = self.first_nul_byte() { + Err(InteriorNulError { pos, roc_str: self }) + } else { + Ok(self.with_utf8_terminator(b'\0', func)) + } + } + + /// Turn this RocStr into a UTF-8 `*mut u8`, terminate it with the given character + /// (commonly either `b'\n'` or b`\0`) and then provide access to that + /// `*mut u8` (as well as its length) for the duration of a given function. This is + /// designed to be an efficient way to turn a `RocStr` received from an application into + /// either the nul-terminated UTF-8 `char*` needed by UNIX syscalls, or into a + /// newline-terminated string to write to stdout or stderr (for a "println"-style effect). + /// + /// **NOTE:** The length passed to the function is the same value that `RocStr::len` will + /// return; it does not count the terminator. So to convert it to a nul-terminated slice + /// of Rust bytes (for example), call `slice::from_raw_parts` passing the given length + 1. + /// + /// This operation achieves efficiency by reusing allocated bytes from the RocStr itself, + /// and sometimes allocating on the stack. It does not allocate on the heap when given a + /// a small string or a string with unique refcount, but may allocate when given a large + /// string with non-unique refcount. (It will do a stack allocation if the string is under + /// 64 bytes; the stack allocation will only live for the duration of the called function.) + /// + /// If the given (owned) RocStr is unique, this can overwrite the underlying bytes + /// to terminate the string in-place. Small strings have an extra byte at the end + /// where the length is stored, which can be replaced with the terminator. Heap-allocated + /// strings can have excess capacity which can hold a terminator, or if they have no + /// excess capacity, all the bytes can be shifted over the refcount in order to free up + /// a `usize` worth of free space at the end - which can easily fit a 1-byte terminator. + pub fn with_utf8_terminator T>(self, terminator: u8, func: F) -> T { + // Note that this function does not use with_terminator because it can be + // more efficient than that - due to knowing that it's already in UTF-8 and always + // has room for a 1-byte terminator in the existing allocation (either in the refcount + // bytes, or, in a small string, in the length at the end of the string). + + let terminate = |alloc_ptr: *mut u8, len: usize| unsafe { + *(alloc_ptr.add(len)) = terminator; + + func(alloc_ptr, len) + }; + + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(big_string) => { + unsafe { + if big_string.is_unique() { + // The backing RocList was unique, so we can mutate it in-place. + let len = big_string.len(); + let ptr = if len < big_string.capacity() { + // We happen to have excess capacity already, so we will be able + // to write the terminator into the first byte of excess capacity. + big_string.ptr_to_first_elem() + } else { + // We always have an allocation that's even bigger than necessary, + // because the refcount bytes take up more than the 1B needed for + // the terminator. We just need to shift the bytes over on top + // of the refcount. + let alloc_ptr = big_string.ptr_to_allocation() as *mut u8; + + // First, copy the bytes over the original allocation - effectively + // shifting everything over by one `usize`. Now we no longer have a + // refcount (but the terminated won't use that anyway), but we do + // have a free `usize` at the end. + // + // IMPORTANT: Must use ptr::copy instead of ptr::copy_nonoverlapping + // because the regions definitely overlap! + ptr::copy(big_string.ptr_to_first_elem(), alloc_ptr, len); + + alloc_ptr + }; + + terminate(ptr, len) + } else { + let len = big_string.len(); + + // The backing list was not unique, so we can't mutate it in-place. + // ask for `len + 1` to store the original string and the terminator + with_stack_bytes(len + 1, |alloc_ptr: *mut u8| { + let elem_ptr = big_string.ptr_to_first_elem(); + + // memcpy the bytes into the stack allocation + std::ptr::copy_nonoverlapping(elem_ptr, alloc_ptr, len); + + terminate(alloc_ptr, len) + }) + } + } + } + RocStrInnerRef::SmallString(small_str) => { + let mut bytes = [0; size_of::>()]; + let mut it = small_str.bytes.iter(); + bytes = bytes.map(|_| it.next().copied().unwrap_or_default()); + + // Even if the small string is at capacity, there will be room to write + // a terminator in the byte that's used to store the length. + terminate(bytes.as_mut_ptr(), small_str.len()) + } + } + } + + /// Like calling with_utf16_terminator passing \0 for the terminator, + /// except it can fail because a RocStr may contain \0 characters, + /// which a nul-terminated string must not. + pub fn utf16_nul_terminated T>( + self, + func: F, + ) -> Result { + if let Some(pos) = self.first_nul_byte() { + Err(InteriorNulError { pos, roc_str: self }) + } else { + Ok(self.with_utf16_terminator(0, func)) + } + } + + /// Turn this RocStr into a nul-terminated UTF-16 `*mut u16` and then provide access to + /// that `*mut u16` (as well as its length) for the duration of a given function. This is + /// designed to be an efficient way to turn a RocStr received from an application into + /// the nul-terminated UTF-16 `wchar_t*` needed by Windows API calls. + /// + /// **NOTE:** The length passed to the function is the same value that `RocStr::len` will + /// return; it does not count the terminator. So to convert it to a nul-terminated + /// slice of Rust bytes, call `slice::from_raw_parts` passing the given length + 1. + /// + /// This operation achieves efficiency by reusing allocated bytes from the RocStr itself, + /// and sometimes allocating on the stack. It does not allocate on the heap when given a + /// a small string or a string with unique refcount, but may allocate when given a large + /// string with non-unique refcount. (It will do a stack allocation if the string is under + /// 64 bytes; the stack allocation will only live for the duration of the called function.) + /// + /// Because this works on an owned RocStr, it's able to overwrite the underlying bytes + /// to nul-terminate the string in-place. Small strings have an extra byte at the end + /// where the length is stored, which can become 0 for nul-termination. Heap-allocated + /// strings can have excess capacity which can hold a termiator, or if they have no + /// excess capacity, all the bytes can be shifted over the refcount in order to free up + /// a `usize` worth of free space at the end - which can easily fit a terminator. + /// + /// This operation can fail because a RocStr may contain \0 characters, which a + /// nul-terminated string must not. + pub fn with_utf16_terminator T>( + self, + terminator: u16, + func: F, + ) -> T { + self.with_terminator(terminator, |dest_ptr: *mut u16, str_slice: &str| { + // Translate UTF-8 source bytes into UTF-16 and write them into the destination. + for (index, wchar) in str_slice.encode_utf16().enumerate() { + unsafe { std::ptr::write_unaligned(dest_ptr.add(index), wchar) }; + } + + func(dest_ptr, str_slice.len()) + }) + } + + pub fn with_windows_path T>( + self, + func: F, + ) -> Result { + if let Some(pos) = self.first_nul_byte() { + Err(InteriorNulError { pos, roc_str: self }) + } else { + let answer = self.with_terminator(0u16, |dest_ptr: *mut u16, str_slice: &str| { + // Translate UTF-8 source bytes into UTF-16 and write them into the destination. + for (index, mut wchar) in str_slice.encode_utf16().enumerate() { + // Replace slashes with backslashes + if wchar == '/' as u16 { + wchar = '\\' as u16 + }; + + unsafe { + *(dest_ptr.add(index)) = wchar; + } + } + + func(dest_ptr, str_slice.len()) + }); + + Ok(answer) + } + } + + /// Generic version of temp_c_utf8 and temp_c_utf16. The given function will be + /// passed a pointer to elements of type E. The pointer will have enough room to hold + /// one element for each byte of the given `&str`'s length, plus the terminator element. + /// + /// The terminator will be written right after the end of the space for the other elements, + /// but all the memory in that space before the terminator will be uninitialized. This means + /// if you want to do something like copy the contents of the `&str` into there, that will + /// need to be done explicitly. + /// + /// The terminator is always written - even if there are no other elements present before it. + /// (In such a case, the `&str` argument will be empty and the `*mut E` will point directly + /// to the terminator). + /// + /// One use for this is to convert slashes to backslashes in Windows paths; + /// this function provides the most efficient way to do that, because no extra + /// iteration pass is necessary; the conversion can be done after each translation + /// of a UTF-8 character to UTF-16. Here's how that would look: + /// + /// use roc_std::{RocStr, InteriorNulError}; + /// + /// pub fn with_windows_path T>( + /// roc_str: RocStr, + /// func: F, + /// ) -> Result { + /// let answer = roc_str.with_terminator(0u16, |dest_ptr: *mut u16, str_slice: &str| { + /// // Translate UTF-8 source bytes into UTF-16 and write them into the destination. + /// for (index, mut wchar) in str_slice.encode_utf16().enumerate() { + /// // Replace slashes with backslashes + /// if wchar == '/' as u16 { + /// wchar = '\\' as u16 + /// }; + /// + /// unsafe { + /// *(dest_ptr.add(index)) = wchar; + /// } + /// } + /// + /// func(dest_ptr, str_slice.len()) + /// }); + /// + /// Ok(answer) + /// } + pub fn with_terminator A>( + self, + terminator: E, + func: F, + ) -> A { + use crate::Storage; + use core::mem::align_of; + + let terminate = |alloc_ptr: *mut E, str_slice: &str| unsafe { + std::ptr::write_unaligned(alloc_ptr.add(str_slice.len()), terminator); + + func(alloc_ptr, str_slice) + }; + + // When we don't have an existing allocation that can work, fall back on this. + // It uses either a stack allocation, or, if that would be too big, a heap allocation. + let fallback = |str_slice: &str| { + // We need 1 extra elem for the terminator. It must be an elem, + // not a byte, because we'll be providing a pointer to elems. + let needed_bytes = (str_slice.len() + 1) * size_of::(); + + with_stack_bytes(needed_bytes, |alloc_ptr: *mut E| { + terminate(alloc_ptr, str_slice) + }) + }; + + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(big_string) => { + let len = big_string.len(); + + unsafe { + if big_string.is_unique() { + // The backing RocList was unique, so we can mutate it in-place. + + // We need 1 extra elem for the terminator. It must be an elem, + // not a byte, because we'll be providing a pointer to elems. + let needed_bytes = (len + 1) * size_of::(); + + // We can use not only the capacity on the heap, but also + // the bytes originally used for the refcount. + let available_bytes = big_string.capacity() + size_of::(); + + if needed_bytes < available_bytes { + debug_assert!(align_of::() >= align_of::()); + + // We happen to have sufficient excess capacity already, + // so we will be able to write the new elements as well as + // the terminator into the existing allocation. + let ptr = big_string.ptr_to_allocation() as *mut E; + let answer = terminate(ptr, self.as_str()); + + // We cannot rely on the RocStr::drop implementation, because + // it tries to use the refcount - which we just overwrote + // with string bytes. + mem::forget(self); + crate::roc_dealloc(ptr.cast(), mem::align_of::() as u32); + + answer + } else { + // We didn't have sufficient excess capacity already, + // so we need to do either a new stack allocation or a new + // heap allocation. + fallback(self.as_str()) + } + } else { + // The backing list was not unique, so we can't mutate it in-place. + fallback(self.as_str()) + } + } + } + RocStrInnerRef::SmallString(small_str) => { + let len = small_str.len(); + + // We need 1 extra elem for the terminator. It must be an elem, + // not a byte, because we'll be providing a pointer to elems. + let needed_bytes = (len + 1) * size_of::(); + let available_bytes = size_of::(); + + if needed_bytes < available_bytes { + let mut bytes = small_str.bytes; + terminate(&mut bytes as *mut u8 as *mut E, self.as_str()) + } else { + fallback(self.as_str()) + } + } + } + } +} + +pub struct SplitWhitespace<'a>(std::iter::Peekable>, &'a RocStr); + +impl Iterator for SplitWhitespace<'_> { + type Item = RocStr; + + fn next(&mut self) -> Option { + let start = 'blk: { + while let Some((pos, c)) = self.0.peek() { + if c.is_whitespace() { + self.0.next(); + } else { + break 'blk *pos; + } + } + + return None; + }; + + let end = 'blk: { + for (pos, c) in self.0.by_ref() { + if c.is_whitespace() { + break 'blk pos; + } + } + + break 'blk self.1.len(); + }; + + self.1.try_slice_range(start..end) + } +} + +impl Deref for RocStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(h) => h.as_str(), + RocStrInnerRef::SmallString(s) => s, + } + } +} + +/// This can fail because a CStr may contain invalid UTF-8 characters +#[cfg(feature = "std")] +impl TryFrom<&CStr> for RocStr { + type Error = core::str::Utf8Error; + + fn try_from(c_str: &CStr) -> Result { + c_str.to_str().map(RocStr::from) + } +} + +/// This can fail because a CString may contain invalid UTF-8 characters +#[cfg(feature = "std")] +impl TryFrom for RocStr { + type Error = core::str::Utf8Error; + + fn try_from(c_string: CString) -> Result { + c_string.to_str().map(RocStr::from) + } +} + +#[cfg(not(feature = "no_std"))] +/// Like https://doc.rust-lang.org/std/ffi/struct.NulError.html but +/// only for interior nuls, not for missing nul terminators. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InteriorNulError { + pub pos: usize, + pub roc_str: RocStr, +} + +impl Default for RocStr { + fn default() -> Self { + Self::empty() + } +} + +impl From<&str> for RocStr { + fn from(s: &str) -> Self { + unsafe { Self::from_slice_unchecked(s.as_bytes()) } + } +} + +impl PartialEq for RocStr { + fn eq(&self, other: &Self) -> bool { + self.deref() == other.deref() + } +} + +impl Eq for RocStr {} + +impl PartialOrd for RocStr { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for RocStr { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl fmt::Debug for RocStr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +impl fmt::Display for RocStr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +impl Clone for RocStr { + fn clone(&self) -> Self { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(h) => Self(RocStrInner { + heap_allocated: ManuallyDrop::new(h.clone()), + }), + RocStrInnerRef::SmallString(s) => Self(RocStrInner { small_string: *s }), + } + } +} + +impl Drop for RocStr { + fn drop(&mut self) { + if !self.is_small_str() { + unsafe { + ManuallyDrop::drop(&mut self.0.heap_allocated); + } + } + } +} + +// This is a RocStr that is checked to ensure it is unique or readonly such that it can be sent between threads safely. +#[repr(transparent)] +pub struct SendSafeRocStr(RocStr); + +unsafe impl Send for SendSafeRocStr {} + +impl Clone for SendSafeRocStr { + fn clone(&self) -> Self { + if self.0.is_readonly() { + SendSafeRocStr(self.0.clone()) + } else { + // To keep self send safe, this must copy. + SendSafeRocStr(RocStr::from(self.0.as_str())) + } + } +} + +impl From for SendSafeRocStr { + fn from(s: RocStr) -> Self { + if s.is_unique() || s.is_readonly() { + SendSafeRocStr(s) + } else { + // This is not unique, do a deep copy. + SendSafeRocStr(RocStr::from(s.as_str())) + } + } +} + +impl From for RocStr { + fn from(s: SendSafeRocStr) -> Self { + s.0 + } +} + +#[repr(C)] +struct BigString { + elements: NonNull, + length: usize, + capacity_or_alloc_ptr: usize, +} + +const SEAMLESS_SLICE_BIT: usize = isize::MIN as usize; + +impl BigString { + fn len(&self) -> usize { + self.length & !SEAMLESS_SLICE_BIT + } + + fn capacity(&self) -> usize { + if self.is_seamless_slice() { + self.len() + } else { + self.capacity_or_alloc_ptr + } + } + + fn is_seamless_slice(&self) -> bool { + (self.length as isize) < 0 + } + + fn ptr_to_first_elem(&self) -> *mut u8 { + unsafe { core::mem::transmute(self.elements) } + } + + fn ptr_to_allocation(&self) -> *mut usize { + // these are the same because the alignment of u8 is just 1 + self.ptr_to_refcount() + } + + fn ptr_to_refcount(&self) -> *mut usize { + if self.is_seamless_slice() { + unsafe { ((self.capacity_or_alloc_ptr << 1) as *mut usize).sub(1) } + } else { + unsafe { self.ptr_to_first_elem().cast::().sub(1) } + } + } + + fn as_bytes(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.ptr_to_first_elem(), self.len()) } + } + + fn as_str(&self) -> &str { + unsafe { std::str::from_utf8_unchecked(self.as_bytes()) } + } + + fn is_unique(&self) -> bool { + if self.capacity() == 0 { + return false; + } + + let ptr = self.ptr_to_refcount(); + let rc = unsafe { std::ptr::read(ptr) as isize }; + + rc == isize::MIN + } + + fn is_readonly(&self) -> bool { + if self.capacity() == 0 { + return true; + } + + let ptr = self.ptr_to_refcount(); + let rc = unsafe { std::ptr::read(ptr) as isize }; + + rc == 0 + } + + fn set_readonly(&mut self) { + assert_ne!(self.capacity(), 0); + + let ptr = self.ptr_to_refcount(); + unsafe { std::ptr::write(ptr, 0) } + } + + fn inc(&mut self, n: usize) { + let ptr = self.ptr_to_refcount(); + unsafe { + let value = std::ptr::read(ptr); + std::ptr::write(ptr, Ord::max(0, ((value as isize) + n as isize) as usize)); + } + } + + fn dec(&mut self) { + if self.capacity() == 0 { + // no valid allocation, elements pointer is dangling + return; + } + + let ptr = self.ptr_to_refcount(); + unsafe { + let value = std::ptr::read(ptr) as isize; + match value { + 0 => { + // static lifetime, do nothing + } + isize::MIN => { + // refcount becomes zero; free allocation + crate::roc_dealloc(self.ptr_to_allocation().cast(), 1); + } + _ => { + std::ptr::write(ptr, (value - 1) as usize); + } + } + } + } + + fn with_capacity(cap: usize) -> Self { + let mut this = Self { + elements: NonNull::dangling(), + length: 0, + capacity_or_alloc_ptr: 0, + }; + + this.reserve(cap); + + this + } + + /// Increase a BigString's capacity by at least the requested number of elements (possibly more). + /// + /// May return a new BigString, if the provided one was not unique. + fn reserve(&mut self, n: usize) { + let align = std::mem::size_of::(); + let desired_cap = self.len() + n; + let desired_alloc = align + desired_cap; + + if self.is_unique() && !self.is_seamless_slice() { + if self.capacity() >= desired_cap { + return; + } + + let new_alloc = unsafe { + roc_realloc( + self.ptr_to_allocation().cast(), + desired_alloc as _, + align + self.capacity(), + align as _, + ) + }; + + let elements = unsafe { NonNull::new_unchecked(new_alloc.cast::().add(align)) }; + + let mut this = Self { + elements, + length: self.len(), + capacity_or_alloc_ptr: desired_cap, + }; + + std::mem::swap(&mut this, self); + std::mem::forget(this); + } else { + let ptr = unsafe { crate::roc_alloc(desired_alloc, align as _) } as *mut u8; + let elements = unsafe { NonNull::new_unchecked(ptr.cast::().add(align)) }; + + unsafe { + // Copy the old elements to the new allocation. + std::ptr::copy_nonoverlapping(self.ptr_to_first_elem(), ptr.add(align), self.len()); + } + + let mut this = Self { + elements, + length: self.len(), + capacity_or_alloc_ptr: desired_cap, + }; + + std::mem::swap(&mut this, self); + std::mem::drop(this); + } + } +} + +impl Clone for BigString { + fn clone(&self) -> Self { + let mut this = Self { + elements: self.elements, + length: self.length, + capacity_or_alloc_ptr: self.capacity_or_alloc_ptr, + }; + + this.inc(1); + + this + } +} + +impl Drop for BigString { + fn drop(&mut self) { + self.dec() + } +} + +#[repr(C)] +union RocStrInner { + // TODO: this really should be separated from the List type. + // Due to length specifying seamless slices for Str and capacity for Lists they should not share the same code. + // Currently, there are work arounds in RocList to handle both via removing the highest bit of length in many cases. + // With glue changes, we should probably rewrite these cleanly to match what is in the zig bitcode. + // It is definitely a bit stale now and I think the storage mechanism can be quite confusing with our extra pieces of state. + heap_allocated: ManuallyDrop, + small_string: SmallString, +} + +enum RocStrInnerRef<'a> { + HeapAllocated(&'a BigString), + SmallString(&'a SmallString), +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +struct SmallString { + bytes: [u8; Self::CAPACITY], + len: u8, +} + +impl SmallString { + const CAPACITY: usize = size_of::>() - 1; + + const fn empty() -> Self { + Self { + bytes: [0; Self::CAPACITY], + len: RocStr::MASK, + } + } + + /// # Safety + /// + /// `slice` must be valid UTF-8. + unsafe fn try_from_utf8_bytes(slice: &[u8]) -> Option { + // Check the size of the slice. + let len_as_u8 = u8::try_from(slice.len()).ok()?; + if (len_as_u8 as usize) > Self::CAPACITY { + return None; + } + + // Construct the small string. + let mut bytes = [0; Self::CAPACITY]; + bytes[..slice.len()].copy_from_slice(slice); + Some(Self { + bytes, + len: len_as_u8 | RocStr::MASK, + }) + } + + fn is_small_str(&self) -> bool { + self.len & RocStr::MASK != 0 + } + + fn len(&self) -> usize { + usize::from(self.len & !RocStr::MASK) + } +} + +impl Deref for SmallString { + type Target = str; + + fn deref(&self) -> &Self::Target { + let len = self.len(); + unsafe { core::str::from_utf8_unchecked(self.bytes.get_unchecked(..len)) } + } +} + +impl DerefMut for SmallString { + fn deref_mut(&mut self) -> &mut Self::Target { + let len = self.len(); + unsafe { core::str::from_utf8_unchecked_mut(self.bytes.get_unchecked_mut(..len)) } + } +} + +impl Hash for RocStr { + fn hash(&self, state: &mut H) { + self.as_str().hash(state) + } +} + +#[cfg(feature = "serde")] +impl Serialize for RocStr { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for RocStr { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // TODO: using deserialize_string here instead of deserialize_str here + // because I think we'd "benefit from taking ownership of buffered data + // owned by the Deserializer." is that correct? + deserializer.deserialize_string(RocStrVisitor {}) + } +} + +#[cfg(feature = "serde")] +struct RocStrVisitor {} + +#[cfg(feature = "serde")] +impl<'de> Visitor<'de> for RocStrVisitor { + type Value = RocStr; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(formatter, "a string") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + Ok(RocStr::from(value)) + } +} diff --git a/examples/BasicRustGUI/crates/roc_std/src/storage.rs b/examples/BasicRustGUI/crates/roc_std/src/storage.rs new file mode 100644 index 0000000..6bbb6d1 --- /dev/null +++ b/examples/BasicRustGUI/crates/roc_std/src/storage.rs @@ -0,0 +1,66 @@ +use core::num::NonZeroIsize; + +/// # Safety +/// +/// isize::MIN is definitely not zero. This can become +/// https://doc.rust-lang.org/std/num/struct.NonZeroIsize.html#associatedconstant.MIN +/// once it has been stabilized. +const REFCOUNT_1: NonZeroIsize = unsafe { NonZeroIsize::new_unchecked(isize::MIN) }; + +const _ASSERT_STORAGE_SIZE: () = + assert!(core::mem::size_of::() == core::mem::size_of::()); + +#[derive(Clone, Copy, Debug)] +pub enum Storage { + Readonly, + ReferenceCounted(NonZeroIsize), +} + +impl Storage { + pub fn new_reference_counted() -> Self { + Self::ReferenceCounted(REFCOUNT_1) + } + + /// Increment the reference count. + pub fn increment_reference_count(&mut self) { + match self { + Storage::Readonly => { + // Do nothing. + } + Storage::ReferenceCounted(rc) => { + let new_rc = rc.get() + 1; + if let Some(new_rc) = NonZeroIsize::new(new_rc) { + *self = Storage::ReferenceCounted(new_rc); + } else { + *self = Storage::Readonly; + } + } + } + } + + /// Decrease the reference count. + /// + /// Returns `true` once there are no more references left. + pub fn decrease(&mut self) -> bool { + match self { + Storage::Readonly => false, + Storage::ReferenceCounted(rc) => { + if *rc == REFCOUNT_1 { + true + } else { + *rc = NonZeroIsize::new(rc.get() - 1).expect("A reference count was decremented all the way to zero, which should never happen."); + + false + } + } + } + } + + pub fn is_readonly(&self) -> bool { + matches!(self, Self::Readonly) + } + + pub fn is_unique(&self) -> bool { + matches!(self, Self::ReferenceCounted(REFCOUNT_1)) + } +} diff --git a/examples/BasicRustGUI/glue-gen.sh b/examples/BasicRustGUI/glue-gen.sh new file mode 100644 index 0000000..7903141 --- /dev/null +++ b/examples/BasicRustGUI/glue-gen.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +## This script is used to re-generate the glue files for the platform, +## specifically roc_app and roc_std. + +# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +set -euxo pipefail + +# Remove old glue flies +rm -rf crates/roc_app +rm -rf crates/roc_std + +# Generate manual glue - NOTE this is a workaround until glue generates for all types +roc glue ../../../roc/crates/glue/src/RustGlue.roc crates/ platform/main.roc diff --git a/examples/BasicRustGUI/hello-gui.roc b/examples/BasicRustGUI/hello-gui.roc new file mode 100644 index 0000000..dc7d66c --- /dev/null +++ b/examples/BasicRustGUI/hello-gui.roc @@ -0,0 +1,16 @@ +app [render] { pf: platform "platform/main.roc" } + +render = + rgba = \r, g, b, a -> { r: r / 255, g: g / 255, b: b / 255, a } + + styles = { bgColor: rgba 100 50 50 1, borderColor: rgba 10 20 30 1, borderWidth: 10, textColor: rgba 220 220 250 1 } + + Col [ + Row [ + Button (Text "Corner ") styles, + Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 }, + Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 }, + ], + Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 }, + Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 }, + ] diff --git a/examples/BasicRustGUI/inspect-gui.roc b/examples/BasicRustGUI/inspect-gui.roc new file mode 100644 index 0000000..6efd67a --- /dev/null +++ b/examples/BasicRustGUI/inspect-gui.roc @@ -0,0 +1,35 @@ +# +# Visualizes Roc values in a basic GUI +# +app [render] { pf: platform "platform/main.roc" } + +import Community +import GuiFormatter + +render = + Community.empty + |> Community.addPerson { + firstName: "John", + lastName: "Smith", + age: 27, + hasBeard: Bool.true, + favoriteColor: Blue, + } + |> Community.addPerson { + firstName: "Debby", + lastName: "Johnson", + age: 47, + hasBeard: Bool.false, + favoriteColor: Green, + } + |> Community.addPerson { + firstName: "Jane", + lastName: "Doe", + age: 33, + hasBeard: Bool.false, + favoriteColor: RGB (255, 255, 0), + } + |> Community.addFriend 0 2 + |> Community.addFriend 1 2 + |> Inspect.inspect + |> GuiFormatter.toGui diff --git a/examples/BasicRustGUI/platform/Action.roc b/examples/BasicRustGUI/platform/Action.roc new file mode 100644 index 0000000..073c517 --- /dev/null +++ b/examples/BasicRustGUI/platform/Action.roc @@ -0,0 +1,15 @@ +module [Action, none, update, map] + +Action state : [None, Update state] + +none : Action * +none = None + +update : state -> Action state +update = Update + +map : Action a, (a -> b) -> Action b +map = \action, transform -> + when action is + None -> None + Update state -> Update (transform state) diff --git a/examples/BasicRustGUI/platform/Elem.roc b/examples/BasicRustGUI/platform/Elem.roc new file mode 100644 index 0000000..96994c6 --- /dev/null +++ b/examples/BasicRustGUI/platform/Elem.roc @@ -0,0 +1,192 @@ +module [Elem, PressEvent, row, col, text, button, none, translate, list] + +import Action exposing [Action] + +Elem state : [ + # PERFORMANCE NOTE: + # If there are 8 or fewer tags here, then on a 64-bit system, the tag can be stored + # in the pointer - for massive memory savings. Try extremely hard to always limit the number + # of tags in this union to 8 or fewer! + Button (ButtonConfig state) (Elem state), + Text Str, + Col (List (Elem state)), + Row (List (Elem state)), + Lazy (Result { state, elem : Elem state } [NotCached] -> { state, elem : Elem state }), + # TODO FIXME: using this definition of Lazy causes a stack overflow in the compiler! + # Lazy (Result (Cached state) [NotCached] -> Cached state), + None, +] + +## Used internally in the type definition of Lazy +# Cached state : { state, elem : Elem state } + +ButtonConfig state : { onPress : state, PressEvent -> Action state } + +PressEvent : { button : [Touch, Mouse [Left, Right, Middle]] } + +text : Str -> Elem * +text = \str -> + Text str + +button : { onPress : state, PressEvent -> Action state }, Elem state -> Elem state +button = \config, label -> + Button config label + +row : List (Elem state) -> Elem state +row = \children -> + Row children + +col : List (Elem state) -> Elem state +col = \children -> + Col children + +lazy : state, (state -> Elem state) -> Elem state +lazy = \state, render -> + # This function gets called by the host during rendering. It will + # receive the cached state and element (wrapped in Ok) if we've + # ever rendered this before, and Err otherwise. + Lazy + \result -> + when result is + Ok cached if cached.state == state -> + # If we have a cached value, and the new state is the + # same as the cached one, then we can return exactly + # what we had cached. + cached + + _ -> + # Either the state changed or else we didn't have a + # cached value to use. Either way, we need to render + # with the new state and store that for future use. + { state, elem: render state } + +none : Elem * +none = None # I've often wanted this in elm/html. Usually end up resorting to (Html.text "") - this seems nicer. +## Change an element's state type. +## +## TODO: indent the following once https://github.com/roc-lang/roc/issues/2585 is fixed. +## State : { photo : Photo } +## +## render : State -> Elem State +## render = \state -> +## child : Elem State +## child = +## Photo.render state.photo +## |> Elem.translate .photo &photo +## +## col {} [child, otherElems] +## +translate = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translate elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translate elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + toChild parentState + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Button { onPress } (translate label toChild toParent) + + Lazy renderChild -> + Lazy + \parentState -> + { elem, state } = renderChild (toChild parentState) + + { + elem: translate toChild toParent newChild, + state: toParent parentState state, + } + + None -> + None + +## Render a list of elements, using [Elem.translate] on each of them. +## +## Convenient when you have a [List] in your state and want to make +## a [List] of child elements out of it. +## +## TODO: indent the following once https://github.com/roc-lang/roc/issues/2585 is fixed. +## State : { photos : List Photo } +## +## render : State -> Elem State +## render = \state -> +## children : List (Elem State) +## children = +## Elem.list Photo.render state .photos &photos +## +## col {} children +## TODO: format as multiline type annotation once https://github.com/roc-lang/roc/issues/2586 is fixed +list : (child -> Elem child), parent, (parent -> List child), (parent, List child -> parent) -> List (Elem parent) +list = \renderChild, parent, toChildren, toParent -> + List.mapWithIndex + (toChildren parent) + \index, child -> + toChild = \par -> List.get (toChildren par) index + + newChild = translateOrDrop + child + toChild + \par, ch -> + toChildren par + |> List.set ch index + |> toParent + + renderChild newChild + +## Internal helper function for Elem.list +## +## Tries to translate a child to a parent, but +## if the child has been removed from the parent, +## drops it. +## +## TODO: format as multiline type annotation once https://github.com/roc-lang/roc/issues/2586 is fixed +translateOrDrop : Elem child, (parent -> Result child *), (parent, child -> parent) -> Elem parent +translateOrDrop = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + when toChild parentState is + Ok newChild -> + newChild + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Err _ -> + # The child was removed from the list before this onPress handler resolved. + # (For example, by a previous event handler that fired simultaneously.) + Action.none + + Button { onPress } (translateOrDrop label toChild toParent) + + Lazy childState renderChild -> + Lazy + (toParent childState) + \parentState -> + when toChild parentState is + Ok newChild -> + renderChild newChild + |> translateOrDrop toChild toParent + + Err _ -> + None + + # I don't think this should ever happen in practice. + None -> + None diff --git a/examples/BasicRustGUI/platform/libapp.roc b/examples/BasicRustGUI/platform/libapp.roc new file mode 100644 index 0000000..0a7af41 --- /dev/null +++ b/examples/BasicRustGUI/platform/libapp.roc @@ -0,0 +1,3 @@ +app [render] { pf: platform "main.roc" } + +render = Text "stubbed app" diff --git a/examples/BasicRustGUI/platform/main.roc b/examples/BasicRustGUI/platform/main.roc new file mode 100644 index 0000000..296de4f --- /dev/null +++ b/examples/BasicRustGUI/platform/main.roc @@ -0,0 +1,15 @@ +platform "gui" + requires {} { render : Elem } + exposes [] + packages {} + imports [] + provides [renderForHost] + +Rgba : { r : F32, g : F32, b : F32, a : F32 } + +ButtonStyles : { bgColor : Rgba, borderColor : Rgba, borderWidth : F32, textColor : Rgba } + +Elem : [Button Elem ButtonStyles, Col (List Elem), Row (List Elem), Text Str] + +renderForHost : Elem +renderForHost = render diff --git a/examples/BasicRustGUI/rust-toolchain.toml b/examples/BasicRustGUI/rust-toolchain.toml new file mode 100644 index 0000000..628740b --- /dev/null +++ b/examples/BasicRustGUI/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.79.0" diff --git a/examples/BasicRustGUI/screenshot-hello-gui.png b/examples/BasicRustGUI/screenshot-hello-gui.png new file mode 100644 index 0000000..a953ae8 Binary files /dev/null and b/examples/BasicRustGUI/screenshot-hello-gui.png differ diff --git a/examples/BasicRustGUI/screenshot-inspect-gui.png b/examples/BasicRustGUI/screenshot-inspect-gui.png new file mode 100644 index 0000000..5217b90 Binary files /dev/null and b/examples/BasicRustGUI/screenshot-inspect-gui.png differ diff --git a/examples/CommandLineArgs/main.roc b/examples/CommandLineArgs/main.roc index b504599..a37124b 100644 --- a/examples/CommandLineArgs/main.roc +++ b/examples/CommandLineArgs/main.roc @@ -1,7 +1,7 @@ # Run with `roc ./examples/CommandLineArgs/main.roc some_argument` # !! This currently does not work in combination with --linker=legacy, see https://github.com/roc-lang/basic-cli/issues/82 app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", } import pf.Stdout diff --git a/examples/CommandLineArgsFile/main.roc b/examples/CommandLineArgsFile/main.roc index c2f45e1..d317f3f 100644 --- a/examples/CommandLineArgsFile/main.roc +++ b/examples/CommandLineArgsFile/main.roc @@ -1,7 +1,7 @@ # Run with `roc ./examples/CommandLineArgsFile/main.roc -- examples/CommandLineArgsFile/input.txt` # This currently does not work in combination with --linker=legacy, see https://github.com/roc-lang/basic-cli/issues/82 app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", } import pf.Stdout diff --git a/examples/EncodeDecode/main.roc b/examples/EncodeDecode/main.roc index 1bc0d65..b81c4df 100644 --- a/examples/EncodeDecode/main.roc +++ b/examples/EncodeDecode/main.roc @@ -1,5 +1,5 @@ app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.8.0/BlWJJh_ouV7c_IwvecYpgpR3jOCzVO-oyk-7ISdl2S4.tar.br", } diff --git a/examples/FizzBuzz/main.roc b/examples/FizzBuzz/main.roc index 47b2578..bbdffa5 100644 --- a/examples/FizzBuzz/main.roc +++ b/examples/FizzBuzz/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task diff --git a/examples/HelloWorld/main.roc b/examples/HelloWorld/main.roc index 75be284..e7c3ec1 100644 --- a/examples/HelloWorld/main.roc +++ b/examples/HelloWorld/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task diff --git a/examples/ImportFromDirectory/main.roc b/examples/ImportFromDirectory/main.roc index 5df74fc..f1b661c 100644 --- a/examples/ImportFromDirectory/main.roc +++ b/examples/ImportFromDirectory/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task diff --git a/examples/IngestFiles/main.roc b/examples/IngestFiles/main.roc index d140680..c67b056 100644 --- a/examples/IngestFiles/main.roc +++ b/examples/IngestFiles/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task diff --git a/examples/Json/main.roc b/examples/Json/main.roc index 3a39ff4..c92c5ff 100644 --- a/examples/Json/main.roc +++ b/examples/Json/main.roc @@ -1,5 +1,5 @@ app [main] { - cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.8.0/BlWJJh_ouV7c_IwvecYpgpR3jOCzVO-oyk-7ISdl2S4.tar.br", } diff --git a/examples/LeastSquares/main.roc b/examples/LeastSquares/main.roc index b75357e..71dc1d7 100644 --- a/examples/LeastSquares/main.roc +++ b/examples/LeastSquares/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task diff --git a/examples/MultipleRocFiles/main.roc b/examples/MultipleRocFiles/main.roc index d1d0cc0..a85de62 100644 --- a/examples/MultipleRocFiles/main.roc +++ b/examples/MultipleRocFiles/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task diff --git a/examples/Parser/main.roc b/examples/Parser/main.roc index eca608d..206cc74 100644 --- a/examples/Parser/main.roc +++ b/examples/Parser/main.roc @@ -1,5 +1,5 @@ app [main] { - cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.7.1/MvLlME9RxOBjl0QCxyn3LIaoG9pSlaNxCa-t3BfbPNc.tar.br", } diff --git a/examples/RandomNumbers/main.roc b/examples/RandomNumbers/main.roc index 9bc910e..1eec422 100644 --- a/examples/RandomNumbers/main.roc +++ b/examples/RandomNumbers/main.roc @@ -1,5 +1,5 @@ app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", rand: "https://github.com/lukewilliamboswell/roc-random/releases/download/0.0.1/x_XwrgehcQI4KukXligrAkWTavqDAdE5jGamURpaX-M.tar.br", } diff --git a/examples/SafeMath/main.roc b/examples/SafeMath/main.roc index f44566c..3c8189a 100644 --- a/examples/SafeMath/main.roc +++ b/examples/SafeMath/main.roc @@ -1,4 +1,4 @@ -app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import cli.Stdout diff --git a/examples/TaskLoop/main.roc b/examples/TaskLoop/main.roc index 911e78a..93ed3a2 100644 --- a/examples/TaskLoop/main.roc +++ b/examples/TaskLoop/main.roc @@ -1,5 +1,5 @@ app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", } import pf.Stdin @@ -32,4 +32,3 @@ printErr = \err -> when err is NotNum text -> Stderr.line "Error: \"$(text)\" is not a valid I64 number." _ -> Stderr.line "Error: $(Inspect.toStr err)" - diff --git a/examples/Tasks/main.roc b/examples/Tasks/main.roc index 614f7c3..476af6d 100644 --- a/examples/Tasks/main.roc +++ b/examples/Tasks/main.roc @@ -1,5 +1,5 @@ app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br", } import pf.Stdout diff --git a/examples/Tuples/main.roc b/examples/Tuples/main.roc index 01ae434..4bf7d82 100644 --- a/examples/Tuples/main.roc +++ b/examples/Tuples/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/zsDHOdxyAcj6EhyNZx_3qhIICVlnho-OZ5X2lFDLi0k.tar.br" } import pf.Stdout import pf.Task @@ -16,7 +16,7 @@ main = Stdout.line! """ First is: $(firstItem), - Second is: $(secondItem), + Second is: $(secondItem), Third is: $(thirdItem). """ diff --git a/examples/index.md b/examples/index.md index 7da4cdc..f5b451d 100644 --- a/examples/index.md +++ b/examples/index.md @@ -29,6 +29,7 @@ You can find the source code for all of these at [github.com/roc-lang/examples]( - [Go Platform](/GoPlatform/README.html) - [.NET Platform](/DotNetPlatform/README.html) - [Encoding & Decoding Abilities](/EncodeDecode/README.html) +- [Basic GUI using Rust](/BasicRustGUI/README.html) ## External examples