From 95e060567e3f896042c5598262e601b7f145586d Mon Sep 17 00:00:00 2001 From: Maciej Jur Date: Sun, 8 Sep 2024 15:38:21 +0200 Subject: [PATCH] content: lambda calculus --- Cargo.lock | 453 +------------------ content/posts/lambda-calculus/index.bib | 10 + content/posts/lambda-calculus/index.md | 85 ++++ js/flox/lambda.ts | 36 ++ js/flox/main.ts | 30 +- js/flox/wasm.ts | 10 + src/html/post.rs | 144 +++--- src/main.rs | 1 + src/text/md.rs | 567 ++++++++++++------------ styles/_flox.scss | 69 +-- 10 files changed, 557 insertions(+), 848 deletions(-) create mode 100644 content/posts/lambda-calculus/index.bib create mode 100644 content/posts/lambda-calculus/index.md create mode 100644 js/flox/lambda.ts create mode 100644 js/flox/wasm.ts diff --git a/Cargo.lock b/Cargo.lock index 8f0e044..0455fdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,12 +29,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aligned-vec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" - [[package]] name = "allocator-api2" version = "0.2.18" @@ -105,70 +99,18 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - [[package]] name = "arraydeque" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "av1-grain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" -dependencies = [ - "arrayvec", -] - [[package]] name = "base64" version = "0.22.1" @@ -206,12 +148,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -[[package]] -name = "bitstream-io" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499" - [[package]] name = "block-buffer" version = "0.10.4" @@ -221,12 +157,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "built" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" - [[package]] name = "bumpalo" version = "3.16.0" @@ -245,12 +175,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.6.0" @@ -271,21 +195,6 @@ name = "cc" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" -dependencies = [ - "jobserver", - "libc", - "once_cell", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] [[package]] name = "cfg-if" @@ -956,43 +865,22 @@ dependencies = [ [[package]] name = "image" -version = "0.25.2" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", - "byteorder-lite", + "byteorder", "color_quant", "exr", "gif", - "image-webp", + "jpeg-decoder", "num-traits", "png", "qoi", - "ravif", - "rayon", - "rgb", "tiff", - "zune-core", - "zune-jpeg", ] -[[package]] -name = "image-webp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imgref" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" - [[package]] name = "indexmap" version = "2.2.6" @@ -1024,17 +912,6 @@ dependencies = [ "libc", ] -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -1050,35 +927,20 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" @@ -1132,7 +994,7 @@ checksum = "4bdbc7a1823f188f56ac9486993536b70a2686a58d47095dcc10507a7d242bf5" dependencies = [ "cfg-if", "derive_builder", - "itertools 0.10.5", + "itertools", "quick-js", "thiserror", ] @@ -1184,17 +1046,6 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "libfuzzer-sys" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" -dependencies = [ - "arbitrary", - "cc", - "once_cell", -] - [[package]] name = "libquickjs-sys" version = "0.9.0" @@ -1221,36 +1072,12 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", -] - [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1273,28 +1100,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - [[package]] name = "notify" version = "6.1.1" @@ -1335,47 +1140,6 @@ dependencies = [ "tree-sitter", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.18" @@ -1451,12 +1215,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - [[package]] name = "png" version = "0.17.13" @@ -1520,25 +1278,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "profiling" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" -dependencies = [ - "quote", - "syn 2.0.53", -] - [[package]] name = "pulldown-cmark" version = "0.12.0" @@ -1567,12 +1306,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quick-js" version = "0.4.1" @@ -1632,55 +1365,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rav1e" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" -dependencies = [ - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools 0.12.1", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "once_cell", - "paste", - "profiling", - "rand", - "rand_chacha", - "simd_helpers", - "system-deps", - "thiserror", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5797d09f9bd33604689e87e8380df4951d4912f01b63f71205e2abd4ae25e6b6" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rgb", -] - [[package]] name = "rayon" version = "1.10.0" @@ -1739,15 +1423,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "rgb" -version = "0.8.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" -dependencies = [ - "bytemuck", -] - [[package]] name = "rstml" version = "0.11.2" @@ -1820,15 +1495,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" -dependencies = [ - "serde", -] - [[package]] name = "serde_yaml" version = "0.9.33" @@ -1870,15 +1536,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - [[package]] name = "siphasher" version = "0.3.11" @@ -1968,25 +1625,6 @@ dependencies = [ "syn 2.0.53", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "thiserror" version = "1.0.59" @@ -2042,40 +1680,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "tree-sitter" version = "0.22.6" @@ -2344,23 +1948,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "v_frame" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.4" @@ -2615,15 +2202,6 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" -[[package]] -name = "winnow" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" -dependencies = [ - "memchr", -] - [[package]] name = "yaml-rust2" version = "0.8.1" @@ -2655,12 +2233,6 @@ dependencies = [ "syn 2.0.53", ] -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - [[package]] name = "zune-inflate" version = "0.2.54" @@ -2669,12 +2241,3 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] - -[[package]] -name = "zune-jpeg" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" -dependencies = [ - "zune-core", -] diff --git a/content/posts/lambda-calculus/index.bib b/content/posts/lambda-calculus/index.bib new file mode 100644 index 0000000..33fb65e --- /dev/null +++ b/content/posts/lambda-calculus/index.bib @@ -0,0 +1,10 @@ +@book{DBLP:books/daglib/0005958, + author = {Benjamin C. Pierce}, + title = {Types and programming languages}, + publisher = {{MIT} Press}, + year = {2002}, + isbn = {978-0-262-16209-8}, + timestamp = {Thu, 03 Feb 2011 10:51:35 +0100}, + biburl = {https://dblp.org/rec/books/daglib/0005958.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} diff --git a/content/posts/lambda-calculus/index.md b/content/posts/lambda-calculus/index.md new file mode 100644 index 0000000..2d3bad8 --- /dev/null +++ b/content/posts/lambda-calculus/index.md @@ -0,0 +1,85 @@ +--- +title: Lambda calculus is the DNA of all computation +date: 2024-09-07T20:32:00.281Z +desc: > + Lambda calculus can be used to express any computation, but what does it entail? As it turns out first class functions are the single most powerful abstraction. +--- + +In lambda calculus the entire grammar of the language comprises of just three kinds of expressions + +1. variables +2. lambda abstraction +3. application + +The grammar can be formulated recursively as + +``` +t ::= + x // variable + λx.t // lambda abstraction + t t // application +``` + +In lambda calculus functions accept only a single argument, but they can be nested, this is referred to as currying in the literature. For example, we can have a simple function that just returns its argument $\lambda x. x$ which is generally known as the identity function. + +Any language that has first class functions with closures can be used to simulate lambda calculus. As an example I will use my own custom toy language. + +``` +// variable declaration +let a = 10; + +// function declaration (id) +let f x = x; + +// anonymous function (id) +let g = fn x -> x; + +// function application +f g 1 +``` + +The result of evaluating `f g 1` is `1`, because `f g` → `g` and `g 1` → `1`. + +As we can see the language can be used to express every single term we have in lambda calculus. Can these terms be used to express any computation? As it turns out yes, in fact we can encode data type using just functions. These encodings are called [Church encodings](https://en.wikipedia.org/wiki/Church_encoding) in the literature. + +Let's start with booleans, they can be defined as follows: + +$$ +\begin{align} + \text{tru} = \lambda \text{t}.\: \lambda \text{f}.\: \text{t}; \\ + \text{fls} = \lambda \text{t}.\: \lambda \text{f}.\: \text{f}; +\end{align} +$$ + +``` +let tru t f = t; +let fls t f = f; +``` + +And then we can defined a function that will work just like `if ... then ... else ...` in general purpose programming languages. + +$$\text{test} = \lambda \text{l}.\: \lambda \text{m}.\: \lambda \text{n}.\: \text{l}\, \text{m}\, \text{n};$$ + +``` +let test l m n = l m n; +``` + +Let's also defined the `and` combinator which checks if two values are true. + +$$\text{and} = \lambda \text{b}.\: \lambda \text{c}.\: \text{b}\, \text{c}\, \text{fls};$$ + +``` +let and_ b c = b c fls; +``` + +Let's see if this works! Feel free to play around with the code... + +
+let tru t f = t;
+let fls t f = f;
+
+let test l m n = l m n;
+let and_ b c = b c fls;
+
+test (and_ tru tru) "both true!" "nope"
+
diff --git a/js/flox/lambda.ts b/js/flox/lambda.ts new file mode 100644 index 0000000..147478c --- /dev/null +++ b/js/flox/lambda.ts @@ -0,0 +1,36 @@ +import { EditorState } from "@codemirror/state"; +import { EditorView, keymap, lineNumbers } from "@codemirror/view"; +import { defaultKeymap } from "@codemirror/commands"; +import { init, evaluate } from "./wasm"; + +function instantiate(element: HTMLElement): EditorView { + const divEditor = document.createElement("div"); + const divOutput = document.createElement("pre"); + const initState = element.innerText.trim(); + + element.innerText = ""; + element.appendChild(divEditor); + element.appendChild(divOutput); + divEditor.className = "editor"; + divOutput.className = "output"; + init.then((_) => (divOutput.innerText = evaluate(initState))); + + const state = EditorState.create({ + doc: initState, + extensions: [ + keymap.of(defaultKeymap), + lineNumbers(), + EditorView.updateListener.of((update) => { + if (update.docChanged) { + divOutput.innerText = evaluate(update.state.doc.toString()); + } + }), + ], + }); + + return new EditorView({ state, parent: divEditor }); +} + +for (const el of document.getElementsByClassName("flox-eval")) { + const editor = instantiate(el as HTMLElement); +} diff --git a/js/flox/main.ts b/js/flox/main.ts index cf16de0..cfd45a2 100644 --- a/js/flox/main.ts +++ b/js/flox/main.ts @@ -1,7 +1,7 @@ -import { EditorState } from '@codemirror/state'; -import { EditorView, keymap, lineNumbers } from '@codemirror/view'; -import { defaultKeymap } from '@codemirror/commands'; -import * as wasm from './pkg/flox_wasm'; +import { EditorState } from "@codemirror/state"; +import { EditorView, keymap, lineNumbers } from "@codemirror/view"; +import { defaultKeymap } from "@codemirror/commands"; +import { evaluate } from "./wasm"; const doc = ` let n1 = 2; @@ -15,27 +15,17 @@ let add a b = a + b; |> fn n -> n * -1 `; -const htmlEditor = document.getElementById('editor')!; -const htmlOutput = document.getElementById('output')!; -const htmlRun = document.getElementById('run')!; +const htmlEditor = document.getElementById("editor")!; +const htmlOutput = document.getElementById("output")!; +const htmlRun = document.getElementById("run")!; const state = EditorState.create({ doc, - extensions: [ - keymap.of(defaultKeymap), - lineNumbers() - ], + extensions: [keymap.of(defaultKeymap), lineNumbers()], }); const view = new EditorView({ state, parent: htmlEditor }); -WebAssembly.compileStreaming(fetch('flox_wasm_bg.wasm')).then((asm) => wasm.initSync(asm)); - -function run(code: string) { - return wasm.eval_expression(code); -} - -htmlRun.addEventListener('click', () => { - const code = view.state.doc.toString(); - htmlOutput.textContent = run(code); +htmlRun.addEventListener("click", () => { + htmlOutput.textContent = evaluate(view.state.doc.toString()); }); diff --git a/js/flox/wasm.ts b/js/flox/wasm.ts new file mode 100644 index 0000000..8d27b9f --- /dev/null +++ b/js/flox/wasm.ts @@ -0,0 +1,10 @@ +import * as wasm from "./pkg/flox_wasm"; + +const PATH = "/static/flox_wasm_bg.wasm"; +const INIT = WebAssembly.compileStreaming(fetch(PATH)).then(wasm.initSync); + +export const init = INIT; + +export function evaluate(source: string): string { + return wasm.eval_expression(source); +} diff --git a/src/html/post.rs b/src/html/post.rs index 062c655..538d729 100644 --- a/src/html/post.rs +++ b/src/html/post.rs @@ -8,54 +8,56 @@ use serde::Deserialize; /// Represents a simple post. #[derive(Deserialize, Debug, Clone)] pub(crate) struct Post { - pub(crate) title: String, - #[serde(with = "super::isodate")] - pub(crate) date: DateTime, - pub(crate) desc: Option, + pub(crate) title: String, + #[serde(with = "super::isodate")] + pub(crate) date: DateTime, + pub(crate) desc: Option, } impl Content for Post { - fn parse_content( - content: &str, - sack: &Sack, - path: &Utf8Path, - library: Option<&Library>, - ) -> (String, Outline, Bibliography) { - crate::text::md::parse(content, sack, path, library) - } + fn parse_content( + content: &str, + sack: &Sack, + path: &Utf8Path, + library: Option<&Library>, + ) -> (String, Outline, Bibliography) { + crate::text::md::parse(content, sack, path, library) + } - fn as_html( - &self, - parsed: &str, - sack: &Sack, - outline: Outline, - bibliography: Bibliography, - ) -> String { - post(self, parsed, sack, outline, bibliography).render().into() - } + fn as_html( + &self, + parsed: &str, + sack: &Sack, + outline: Outline, + bibliography: Bibliography, + ) -> String { + post(self, parsed, sack, outline, bibliography) + .render() + .into() + } - fn as_link(&self, path: Utf8PathBuf) -> Option { - Some(Linkable::Date(LinkDate { - link: Link { - path, - name: self.title.to_owned(), - desc: self.desc.to_owned(), - }, - date: self.date.to_owned(), - })) - } + fn as_link(&self, path: Utf8PathBuf) -> Option { + Some(Linkable::Date(LinkDate { + link: Link { + path, + name: self.title.to_owned(), + desc: self.desc.to_owned(), + }, + date: self.date.to_owned(), + })) + } } pub fn post<'s, 'p, 'html>( - metadata: &'p Post, - parsed: &'p str, - sack: &'s Sack, - outline: Outline, - bibliography: Bibliography, + metadata: &'p Post, + parsed: &'p str, + sack: &'s Sack, + outline: Outline, + bibliography: Bibliography, ) -> impl Renderable + 'html where - 's: 'html, - 'p: 'html, + 's: 'html, + 'p: 'html, { let main = maud_move!( main { @@ -63,46 +65,48 @@ where } ); - crate::html::page(sack, main, metadata.title.clone()) + crate::html::page(sack, main, metadata.title.clone()) } pub fn article<'p, 's, 'html>( - title: &'p str, - parsed: &'p str, - _: &'s Sack, - outline: Outline, - bibliography: Bibliography, + title: &'p str, + parsed: &'p str, + _: &'s Sack, + outline: Outline, + bibliography: Bibliography, ) -> impl Renderable + 'html where - 's: 'html, - 'p: 'html, + 's: 'html, + 'p: 'html, { - maud_move!( - div .wiki-main { + maud_move!( + div .wiki-main { - // Slide in/out for mobile - input #wiki-aside-shown type="checkbox" hidden; + // Slide in/out for mobile + input #wiki-aside-shown type="checkbox" hidden; - aside .wiki-aside { - // Slide button - label .wiki-aside__slider for="wiki-aside-shown" { - img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24"; - } - (crate::html::misc::show_outline(outline)) - } + aside .wiki-aside { + // Slide button + label .wiki-aside__slider for="wiki-aside-shown" { + img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24"; + } + (crate::html::misc::show_outline(outline)) + } - article .wiki-article /*class:list={classlist)*/ { - header class="markdown" { - h1 #top { (title) } - } - section .wiki-article__markdown.markdown { - (Raw(parsed)) - } + article .wiki-article /*class:list={classlist)*/ { + header class="markdown" { + h1 #top { (title) } + } + section .wiki-article__markdown.markdown { + (Raw(parsed)) + } - @if let Some(bib) = bibliography.0 { - (crate::html::misc::show_bibliography(bib)) - } - } - } - ) + @if let Some(bib) = bibliography.0 { + (crate::html::misc::show_bibliography(bib)) + } + + script type="module" {(Raw(r#"import "lambda";"#))} + } + } + ) } diff --git a/src/main.rs b/src/main.rs index 2dddd1f..8e68c73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ fn main() { .js("photos", "./js/vanilla/photos.js") .js("reveal", "./js/vanilla/reveal.js") .js("editor", "./js/flox/main.ts") + .js("lambda", "./js/flox/lambda.ts") .add_virtual( |sack| crate::html::map(sack).render().to_owned().into(), "map/index.html".into(), diff --git a/src/text/md.rs b/src/text/md.rs index d0b9b12..5a581a9 100644 --- a/src/text/md.rs +++ b/src/text/md.rs @@ -3,10 +3,10 @@ use std::collections::HashMap; use camino::{Utf8Path, Utf8PathBuf}; use hauchiwa::{Bibliography, Outline, Sack}; use hayagriva::{ - archive::ArchivedStyle, - citationberg::{IndependentStyle, Locale, Style}, - BibliographyDriver, BibliographyRequest, BufWriteFormat, CitationItem, CitationRequest, - Library, + archive::ArchivedStyle, + citationberg::{IndependentStyle, Locale, Style}, + BibliographyDriver, BibliographyRequest, BufWriteFormat, CitationItem, CitationRequest, + Library, }; use hypertext::Renderable; use once_cell::sync::Lazy; @@ -16,370 +16,369 @@ use regex::Regex; use crate::ts; static OPTS: Lazy = Lazy::new(|| { - Options::empty() - .union(Options::ENABLE_MATH) - .union(Options::ENABLE_TABLES) - .union(Options::ENABLE_TASKLISTS) - .union(Options::ENABLE_STRIKETHROUGH) - .union(Options::ENABLE_SMART_PUNCTUATION) + Options::empty() + .union(Options::ENABLE_MATH) + .union(Options::ENABLE_TABLES) + .union(Options::ENABLE_TASKLISTS) + .union(Options::ENABLE_STRIKETHROUGH) + .union(Options::ENABLE_SMART_PUNCTUATION) }); static KATEX_I: Lazy = Lazy::new(|| { - katex::opts::Opts::builder() - .output_type(katex::OutputType::Mathml) - .build() - .unwrap() + katex::opts::Opts::builder() + .output_type(katex::OutputType::Mathml) + .build() + .unwrap() }); static KATEX_B: Lazy = Lazy::new(|| { - katex::opts::Opts::builder() - .output_type(katex::OutputType::Mathml) - .display_mode(true) - .build() - .unwrap() + katex::opts::Opts::builder() + .output_type(katex::OutputType::Mathml) + .display_mode(true) + .build() + .unwrap() }); static LOCALE: Lazy> = Lazy::new(hayagriva::archive::locales); static STYLE: Lazy = - Lazy::new(|| match ArchivedStyle::InstituteOfPhysicsNumeric.get() { - Style::Independent(style) => style, - Style::Dependent(_) => unreachable!(), - }); + Lazy::new( + || match ArchivedStyle::InstituteOfElectricalAndElectronicsEngineers.get() { + Style::Independent(style) => style, + Style::Dependent(_) => unreachable!(), + }, + ); pub fn parse( - content: &str, - sack: &Sack, - path: &Utf8Path, - library: Option<&Library>, + content: &str, + sack: &Sack, + path: &Utf8Path, + library: Option<&Library>, ) -> (String, Outline, Bibliography) { - let (outline, stream) = { - let stream = Parser::new_ext(content, *OPTS); - let mut stream: Vec<_> = TextMergeStream::new(stream).collect(); - let outline = set_heading_ids(&mut stream); - (outline, stream) - }; + let (outline, stream) = { + let stream = Parser::new_ext(content, *OPTS); + let mut stream: Vec<_> = TextMergeStream::new(stream).collect(); + let outline = set_heading_ids(&mut stream); + (outline, stream) + }; - let stream = stream - .into_iter() - .map(make_math) - .map(make_emoji) - .map(swap_hashed_image(path, sack)) - .collect::>(); + let stream = stream + .into_iter() + .map(make_math) + .map(make_emoji) + .map(swap_hashed_image(path, sack)) + .collect::>(); - let stream = make_code(stream) - .into_iter() - .flat_map(make_ruby) - .flat_map(make_cite) - .collect::>(); + let stream = make_code(stream) + .into_iter() + .flat_map(make_ruby) + .flat_map(make_cite) + .collect::>(); - let (stream, bib) = match library { - Some(lib) => make_bib(stream, lib), - None => (stream, None), - }; + let (stream, bib) = match library { + Some(lib) => make_bib(stream, lib), + None => (stream, None), + }; - let mut parsed = String::new(); - pulldown_cmark::html::push_html(&mut parsed, stream.into_iter()); + let mut parsed = String::new(); + pulldown_cmark::html::push_html(&mut parsed, stream.into_iter()); - (parsed, outline, Bibliography(bib)) + (parsed, outline, Bibliography(bib)) } fn make_bib<'a, 'b>( - stream: Vec>, - lib: &'b Library, + stream: Vec>, + lib: &'b Library, ) -> (Vec>, Option>) { - let mut driver = BibliographyDriver::new(); + let mut driver = BibliographyDriver::new(); - for event in stream.iter() { - match event { - Event::InlineMath(ref text) => match lib.get(text) { - Some(entry) => driver.citation(CitationRequest::from_items( - vec![CitationItem::with_entry(entry)], - &STYLE, - &LOCALE, - )), - None => (), - }, - _ => (), - } - } + for event in stream.iter() { + match event { + Event::InlineMath(ref text) => match lib.get(text) { + Some(entry) => driver.citation(CitationRequest::from_items( + vec![CitationItem::with_entry(entry)], + &STYLE, + &LOCALE, + )), + None => (), + }, + _ => (), + } + } - // add fake citation to make all entries show up - driver.citation(CitationRequest::from_items( - lib.iter().map(CitationItem::with_entry).collect(), - &STYLE, - &LOCALE, - )); + // add fake citation to make all entries show up + driver.citation(CitationRequest::from_items( + lib.iter().map(CitationItem::with_entry).collect(), + &STYLE, + &LOCALE, + )); - let res = driver.finish(BibliographyRequest { - style: &STYLE, - locale: None, - locale_files: &LOCALE, - }); + let res = driver.finish(BibliographyRequest { + style: &STYLE, + locale: None, + locale_files: &LOCALE, + }); - let mut n = 0; - let stream = stream - .into_iter() - .map(|event| match event { - Event::InlineMath(name) => { - let mut buffer = String::from(""); - match res.citations.get(n) { - Some(rf) => rf - .citation - .write_buf(&mut buffer, BufWriteFormat::Html) - .unwrap(), - None => buffer.push_str(&name), - }; - buffer.push_str(""); - n += 1; - Event::InlineHtml(buffer.into()) - } - _ => event, - }) - .collect(); + let mut n = 0; + let stream = stream + .into_iter() + .map(|event| match event { + Event::InlineMath(name) => { + let mut buffer = String::from(""); + match res.citations.get(n) { + Some(rf) => rf + .citation + .write_buf(&mut buffer, BufWriteFormat::Html) + .unwrap(), + None => buffer.push_str(&name), + }; + buffer.push_str(""); + n += 1; + Event::InlineHtml(buffer.into()) + } + _ => event, + }) + .collect(); - let bib = res.bibliography.map(|bib| { - bib.items - .iter() - .map(|x| { - let mut buffer = String::new(); - x.content - .write_buf(&mut buffer, BufWriteFormat::Html) - .unwrap(); - buffer - }) - .collect::>() - }); + let bib = res.bibliography.map(|bib| { + bib.items + .iter() + .map(|x| { + let mut buffer = String::new(); + x.content + .write_buf(&mut buffer, BufWriteFormat::Html) + .unwrap(); + buffer + }) + .collect::>() + }); - (stream, bib) + (stream, bib) } static RE_CITE: Lazy = Lazy::new(|| Regex::new(r":cite\[([^\]]+)\]").unwrap()); #[derive(Debug)] enum Annotated_<'a> { - Text(&'a str), - Cite(&'a str), + Text(&'a str), + Cite(&'a str), } fn annotate_(input: &str) -> Vec { - let mut parts: Vec = Vec::new(); - let mut last_index = 0; + let mut parts: Vec = Vec::new(); + let mut last_index = 0; - for cap in RE_CITE.captures_iter(input) { - let cite = cap.get(1).unwrap().as_str(); - let index = cap.get(0).unwrap().start(); + for cap in RE_CITE.captures_iter(input) { + let cite = cap.get(1).unwrap().as_str(); + let index = cap.get(0).unwrap().start(); - if index > last_index { - parts.push(Annotated_::Text(&input[last_index..index])); - } + if index > last_index { + parts.push(Annotated_::Text(&input[last_index..index])); + } - parts.push(Annotated_::Cite(cite)); - last_index = cap.get(0).unwrap().end(); - } + parts.push(Annotated_::Cite(cite)); + last_index = cap.get(0).unwrap().end(); + } - if last_index < input.len() { - parts.push(Annotated_::Text(&input[last_index..])); - } + if last_index < input.len() { + parts.push(Annotated_::Text(&input[last_index..])); + } - parts + parts } fn make_cite(event: Event) -> Vec { - match event { - Event::Text(ref text) => annotate_(text) - .into_iter() - .map(|e| match e { - Annotated_::Text(text) => Event::Text(text.to_owned().into()), - Annotated_::Cite(cite) => Event::InlineMath(cite.to_owned().into()), - }) - .collect(), - _ => vec![event], - } + match event { + Event::Text(ref text) => annotate_(text) + .into_iter() + .map(|e| match e { + Annotated_::Text(text) => Event::Text(text.to_owned().into()), + Annotated_::Cite(cite) => Event::InlineMath(cite.to_owned().into()), + }) + .collect(), + _ => vec![event], + } } fn set_heading_ids(events: &mut [Event]) -> Outline { - let mut cnt = HashMap::::new(); - let mut out = Vec::new(); - let mut buf = String::new(); - let mut ptr = None; + let mut cnt = HashMap::::new(); + let mut out = Vec::new(); + let mut buf = String::new(); + let mut ptr = None; - for event in events { - match event { - Event::Start(ref mut tag @ Tag::Heading { .. }) => { - ptr = Some(tag); - } - Event::Text(ref text) if ptr.is_some() => buf.push_str(text), - Event::End(TagEnd::Heading(..)) => { - let txt = std::mem::take(&mut buf); - let url = txt.to_lowercase().replace(' ', "-"); - let url = match cnt.get_mut(&url) { - Some(ptr) => { - *ptr += 1; - format!("{url}-{ptr}") - } - None => { - cnt.insert(url.clone(), 0); - url - } - }; - match ptr.take().unwrap() { - Tag::Heading { ref mut id, .. } => *id = Some(url.clone().into()), - _ => unreachable!(), - } - out.push((txt, url)); - } - _ => (), - } - } + for event in events { + match event { + Event::Start(ref mut tag @ Tag::Heading { .. }) => { + ptr = Some(tag); + } + Event::Text(ref text) if ptr.is_some() => buf.push_str(text), + Event::End(TagEnd::Heading(..)) => { + let txt = std::mem::take(&mut buf); + let url = txt.to_lowercase().replace(' ', "-"); + let url = match cnt.get_mut(&url) { + Some(ptr) => { + *ptr += 1; + format!("{url}-{ptr}") + } + None => { + cnt.insert(url.clone(), 0); + url + } + }; + match ptr.take().unwrap() { + Tag::Heading { ref mut id, .. } => *id = Some(url.clone().into()), + _ => unreachable!(), + } + out.push((txt, url)); + } + _ => (), + } + } - Outline(out) + Outline(out) } fn make_math(event: Event) -> Event { - match event { - Event::InlineMath(math) => { - Event::InlineHtml(katex::render_with_opts(&math, &*KATEX_I).unwrap().into()) - } - Event::DisplayMath(math) => { - Event::Html(katex::render_with_opts(&math, &*KATEX_B).unwrap().into()) - } - _ => event, - } + match event { + Event::InlineMath(math) => { + Event::InlineHtml(katex::render_with_opts(&math, &*KATEX_I).unwrap().into()) + } + Event::DisplayMath(math) => { + Event::Html(katex::render_with_opts(&math, &*KATEX_B).unwrap().into()) + } + _ => event, + } } fn make_code(es: Vec) -> Vec { - let mut buff = Vec::new(); - let mut lang = None; - let mut code = String::new(); + let mut buff = Vec::new(); + let mut lang = None; + let mut code = String::new(); - for event in es { - match event { - Event::Start(Tag::CodeBlock(kind)) => match kind { - CodeBlockKind::Indented => (), - CodeBlockKind::Fenced(name) => lang = Some(name), - }, - Event::End(TagEnd::CodeBlock) => { - let lang = lang.take().unwrap_or("".into()); - let html = ts::highlight(&lang, &code).render().as_str().to_owned(); - buff.push(Event::Html(html.into())); - code.clear(); - } - Event::Text(text) => match lang { - None => buff.push(Event::Text(text)), - Some(_) => code.push_str(&text), - }, - _ => buff.push(event), - } - } + for event in es { + match event { + Event::Start(Tag::CodeBlock(kind)) => match kind { + CodeBlockKind::Indented => (), + CodeBlockKind::Fenced(name) => lang = Some(name), + }, + Event::End(TagEnd::CodeBlock) => { + let lang = lang.take().unwrap_or("".into()); + let html = ts::highlight(&lang, &code).render().as_str().to_owned(); + buff.push(Event::Html(html.into())); + code.clear(); + } + Event::Text(text) => match lang { + None => buff.push(Event::Text(text)), + Some(_) => code.push_str(&text), + }, + _ => buff.push(event), + } + } - buff + buff } static RE_RUBY: Lazy = Lazy::new(|| Regex::new(r"\[([^\]]+)\]\{([^}]+)\}").unwrap()); #[derive(Debug)] enum Annotated<'a> { - Text(&'a str), - Ruby(&'a str, &'a str), + Text(&'a str), + Ruby(&'a str, &'a str), } fn annotate(input: &str) -> Vec { - let mut parts: Vec = Vec::new(); - let mut last_index = 0; + let mut parts: Vec = Vec::new(); + let mut last_index = 0; - for cap in RE_RUBY.captures_iter(input) { - let text = cap.get(1).unwrap().as_str(); - let ruby = cap.get(2).unwrap().as_str(); - let index = cap.get(0).unwrap().start(); + for cap in RE_RUBY.captures_iter(input) { + let text = cap.get(1).unwrap().as_str(); + let ruby = cap.get(2).unwrap().as_str(); + let index = cap.get(0).unwrap().start(); - if index > last_index { - parts.push(Annotated::Text(&input[last_index..index])); - } + if index > last_index { + parts.push(Annotated::Text(&input[last_index..index])); + } - parts.push(Annotated::Ruby(text, ruby)); - last_index = cap.get(0).unwrap().end(); - } + parts.push(Annotated::Ruby(text, ruby)); + last_index = cap.get(0).unwrap().end(); + } - if last_index < input.len() { - parts.push(Annotated::Text(&input[last_index..])); - } + if last_index < input.len() { + parts.push(Annotated::Text(&input[last_index..])); + } - parts + parts } fn make_ruby(event: Event) -> Vec { - match event { - Event::Text(ref text) => annotate(text) - .into_iter() - .map(|el| match el { - Annotated::Text(text) => Event::Text(text.to_owned().into()), - Annotated::Ruby(t, f) => Event::InlineHtml( - format!("{t}({f})").into(), - ), - }) - .collect(), - _ => vec![event], - } + match event { + Event::Text(ref text) => annotate(text) + .into_iter() + .map(|el| match el { + Annotated::Text(text) => Event::Text(text.to_owned().into()), + Annotated::Ruby(t, f) => Event::InlineHtml( + format!("{t}({f})").into(), + ), + }) + .collect(), + _ => vec![event], + } } fn make_emoji(event: Event) -> Event { - match event { - Event::Text(ref text) => { - let mut buf = None; - let mut top = 0; - let mut old = 0; + match event { + Event::Text(ref text) => { + let mut buf = None; + let mut top = 0; + let mut old = 0; - for (idx, _) in text.match_indices(':') { - let key = &text[old..idx]; + for (idx, _) in text.match_indices(':') { + let key = &text[old..idx]; - if let Some(emoji) = emojis::get_by_shortcode(key) { - let buf = buf.get_or_insert_with(|| String::with_capacity(text.len())); - buf.push_str(&text[top..old - 1]); - buf.push_str(emoji.as_str()); - top = idx + 1; - } + if let Some(emoji) = emojis::get_by_shortcode(key) { + let buf = buf.get_or_insert_with(|| String::with_capacity(text.len())); + buf.push_str(&text[top..old - 1]); + buf.push_str(emoji.as_str()); + top = idx + 1; + } - old = idx + 1; - } + old = idx + 1; + } - if let Some(ref mut buf) = buf { - buf.push_str(&text[top..]); - } + if let Some(ref mut buf) = buf { + buf.push_str(&text[top..]); + } - match buf { - None => event, - Some(buf) => Event::Text(buf.into()), - } - } - _ => event, - } + match buf { + None => event, + Some(buf) => Event::Text(buf.into()), + } + } + _ => event, + } } -fn swap_hashed_image<'a>( - dir: &'a Utf8Path, - sack: &'a Sack, -) -> impl Fn(Event) -> Event + 'a { - move |event| match event { - Event::Start(start) => match start { - Tag::Image { - dest_url, - link_type, - title, - id, - } => { - let rel = dir.join(dest_url.as_ref()); - let img = sack.get_image(&rel); - let hashed = img.map(|path| path.as_str().to_owned().into()); - Event::Start(Tag::Image { - link_type, - dest_url: hashed.unwrap_or(dest_url), - title, - id, - }) - } - _ => Event::Start(start), - }, - _ => event, - } +fn swap_hashed_image<'a>(dir: &'a Utf8Path, sack: &'a Sack) -> impl Fn(Event) -> Event + 'a { + move |event| match event { + Event::Start(start) => match start { + Tag::Image { + dest_url, + link_type, + title, + id, + } => { + let rel = dir.join(dest_url.as_ref()); + let img = sack.get_image(&rel); + let hashed = img.map(|path| path.as_str().to_owned().into()); + Event::Start(Tag::Image { + link_type, + dest_url: hashed.unwrap_or(dest_url), + title, + id, + }) + } + _ => Event::Start(start), + }, + _ => event, + } } diff --git a/styles/_flox.scss b/styles/_flox.scss index 6b6b87b..4180c66 100644 --- a/styles/_flox.scss +++ b/styles/_flox.scss @@ -1,32 +1,43 @@ - .flox-playground { - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: max(24rem, 0.5vh); - gap: 1em; - margin-block: 0.5em; - padding: 1em; - - .editor, #output { - height: calc(100% - 4em); - overflow-y: auto; - border: 1px dashed black; - border-radius: 0.5em; - background-color: white; - } - - .editor { - position: relative; - - #run { - position: absolute; - right: 0.5em; - bottom: 0.5em; - padding: 0.25em 0.5em; - } - } - - #output { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: max(24rem, 0.5vh); + gap: 1em; + margin-block: 0.5em; padding: 1em; - } + + .editor, + #output { + height: calc(100% - 4em); + overflow-y: auto; + border: 1px dashed black; + border-radius: 0.5em; + background-color: white; + } + + .editor { + position: relative; + + #run { + position: absolute; + right: 0.5em; + bottom: 0.5em; + padding: 0.25em 0.5em; + } + } + + #output { + padding: 1em; + } +} + +.flox-eval { + border-radius: 0.5em; + border: 1px solid black; + overflow: auto; + + > .output { + padding: 0.25em 0.5em; + border-top: 1px solid black; + } }