diff --git a/.gitignore b/.gitignore index b8b7bd2..db9daf4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target -/pkg +cxgraph-web/pkg /.vscode diff --git a/Cargo.lock b/Cargo.lock index 429f045..1613849 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,9 +46,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -108,10 +108,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] -name = "ash" -version = "0.37.2+1.3.238" +name = "ascii-canvas" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ "libloading 0.7.4", ] @@ -160,9 +169,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.2.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" +checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" [[package]] name = "block" @@ -191,9 +200,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" @@ -203,10 +212,11 @@ checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "calloop" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" dependencies = [ + "bitflags 1.3.2", "log", "nix 0.25.1", "slotmap", @@ -338,22 +348,33 @@ dependencies = [ ] [[package]] -name = "cxgraph" +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "cxgraph-desktop" version = "0.1.0" dependencies = [ - "cfg-if", - "cgmath", - "console_error_panic_hook", - "console_log", - "encase", "env_logger", + "libcxgraph", "log", "pollster", - "raw-window-handle", + "winit", +] + +[[package]] +name = "cxgraph-web" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "console_log", + "libcxgraph", + "log", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu", "winit", ] @@ -368,6 +389,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -376,11 +424,11 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.0", ] [[package]] @@ -389,6 +437,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + [[package]] name = "encase" version = "0.6.1" @@ -418,7 +481,7 @@ checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -464,6 +527,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.26" @@ -491,9 +560,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -508,9 +577,9 @@ checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "glow" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +checksum = "807edf58b70c0b5b2181dd39fe1839dbdb3ba02645630dc5f753e23da307f762" dependencies = [ "js-sys", "slotmap", @@ -636,9 +705,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", @@ -657,6 +726,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -674,9 +752,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -692,6 +770,38 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lalrpop" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +dependencies = [ + "regex", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -700,9 +810,23 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "libcxgraph" +version = "0.1.0" +dependencies = [ + "cgmath", + "encase", + "lalrpop", + "lalrpop-util", + "log", + "num-complex", + "raw-window-handle", + "wgpu", +] [[package]] name = "libloading" @@ -726,15 +850,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -742,12 +866,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "malloc_buf" @@ -823,21 +944,21 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "naga" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00ce114f2867153c079d4489629dbd27aa4b5387a8ba5341bd3f6dfe870688f" +checksum = "80cd00bd6180a8790f1c020ed258a46b8d73dd5bd6af104a238c9d71f806938e" dependencies = [ "bit-set", "bitflags 1.3.2", @@ -882,6 +1003,12 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nix" version = "0.24.3" @@ -917,6 +1044,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-complex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -994,18 +1130,18 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "orbclient" @@ -1037,22 +1173,47 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pkg-config" @@ -1079,6 +1240,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1091,9 +1258,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -1106,9 +1273,9 @@ checksum = "332cd62e95873ea4f41f3dfd6bbbfc5b52aec892d7e8d534197c4720a0bbbab2" [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1144,10 +1311,21 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.8.1" +name = "redox_users" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -1156,9 +1334,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "renderdoc-sys" @@ -1192,6 +1370,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1223,6 +1407,12 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slotmap" version = "1.0.6" @@ -1275,9 +1465,22 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strict-num" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] [[package]] name = "syn" @@ -1292,15 +1495,26 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1327,7 +1541,16 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.18", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", ] [[package]] @@ -1357,15 +1580,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", "toml_datetime", @@ -1380,9 +1603,9 @@ checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-width" @@ -1416,9 +1639,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1426,24 +1649,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -1453,9 +1676,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1463,22 +1686,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "wayland-client" @@ -1555,9 +1778,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -1565,9 +1788,9 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13edd72c7b08615b7179dd7e778ee3f0bdc870ef2de9019844ff2cceeee80b11" +checksum = "3059ea4ddec41ca14f356833e2af65e7e38c0a8f91273867ed526fb9bafcca95" dependencies = [ "arrayvec", "cfg-if", @@ -1589,13 +1812,13 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625bea30a0ba50d88025f95c80211d1a85c86901423647fb74f397f614abbd9a" +checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.2.1", + "bitflags 2.3.1", "codespan-reporting", "log", "naga", @@ -1612,15 +1835,15 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41af2ea7d87bd41ad0a37146252d5f7c26490209f47f544b2ee3b3ff34c7732e" +checksum = "74851c2c8e5d97652e74c241d41b0656b31c924a45dcdecde83975717362cfa4" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags 2.2.1", + "bitflags 2.3.1", "block", "core-graphics-types", "d3d12", @@ -1658,7 +1881,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd33a976130f03dcdcd39b3810c0c3fc05daf86f0aaf867db14bfb7c4a9a32b" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.3.1", "js-sys", "web-sys", ] @@ -1907,6 +2130,6 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.10" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc95a04ea24f543cd9be5aab44f963fa35589c99e18415c38fb2b17e133bf8d2" +checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" diff --git a/Cargo.toml b/Cargo.toml index 00f0bf0..5d3b27c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,8 @@ -[package] -name = "cxgraph" -version = "0.1.0" -edition = "2021" +[workspace] -[lib] -crate-type = ["cdylib", "rlib"] +members = [ + "libcxgraph", + "cxgraph-desktop", + "cxgraph-web", +] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -cfg-if = "1" -wgpu = "0.16" -cgmath = "0.18" -encase = { version = "0.6", features = ["cgmath"] } -raw-window-handle = "0.5" -winit = "0.28" -log = "0.4" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -pollster = "0.3" -env_logger = "0.10" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook = "0.1" -console_log = "1.0" -wgpu = { version = "0.16", features = ["webgl"]} -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" -web-sys = { version = "0.3", features = ["Document", "Window", "Element"]} diff --git a/cxgraph-desktop/Cargo.toml b/cxgraph-desktop/Cargo.toml new file mode 100644 index 0000000..64f3201 --- /dev/null +++ b/cxgraph-desktop/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cxgraph-desktop" +version = "0.1.0" +edition = "2021" + +[dependencies] +libcxgraph = { path = "../libcxgraph" } +log = "0.4" +env_logger = "0.10" +winit = "0.28" +pollster = "0.3" diff --git a/src/main.rs b/cxgraph-desktop/src/main.rs similarity index 82% rename from src/main.rs rename to cxgraph-desktop/src/main.rs index 8ff41fd..9706a37 100644 --- a/src/main.rs +++ b/cxgraph-desktop/src/main.rs @@ -1,15 +1,13 @@ -use cxgraph::{renderer::WgpuState, language::compile}; +use libcxgraph::{renderer::WgpuState, language::compile}; use winit::{event_loop::EventLoop, window::Window, event::{Event, WindowEvent}}; fn main() { env_logger::builder() - .filter_level(log::LevelFilter::Info) + .filter_level(log::LevelFilter::Warn) .init(); - let src = r#" - plot(z) = z^2 -> w, z*w - "#; + let src = "plot(z) = 27^z - 9^z - 3^z"; let wgsl = compile(src).unwrap(); println!("{wgsl}"); @@ -24,10 +22,10 @@ async fn run(event_loop: EventLoop<()>, window: Window, code: &str) { let size = window.inner_size(); let mut state = WgpuState::new(&window, size.into()).await; - state.load_shaders(code); + state.load_shaders(code); state.set_bounds((-5.0, -5.0), (5.0, 5.0)); - state.set_shading_intensity(0.01); + state.set_shading_intensity(0.05); event_loop.run(move |event, _, control_flow| { control_flow.set_wait(); diff --git a/cxgraph-web/Cargo.toml b/cxgraph-web/Cargo.toml new file mode 100644 index 0000000..2197b3d --- /dev/null +++ b/cxgraph-web/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cxgraph-web" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +libcxgraph = { path = "../libcxgraph", features = ["webgl"] } +log = "0.4" +winit = "0.28" +console_error_panic_hook = "0.1" +console_log = "1.0" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +web-sys = { version = "0.3", features = ["Document", "Window", "Element"]} diff --git a/cxgraph-web/index.html b/cxgraph-web/index.html new file mode 100644 index 0000000..0e43353 --- /dev/null +++ b/cxgraph-web/index.html @@ -0,0 +1,25 @@ + + + + cxgraph + + + +
+ + +
+ + + + + + diff --git a/cxgraph-web/index.js b/cxgraph-web/index.js new file mode 100644 index 0000000..8973032 --- /dev/null +++ b/cxgraph-web/index.js @@ -0,0 +1,89 @@ +menu_checkbox.addEventListener("change", () => { + menu_inner.hidden = !menu_checkbox.checked; +}); + +import init, * as cxgraph from "./pkg/cxgraph_web.js"; +await init(); + +let graphView = { + xoff: 0, + yoff: 0, + scale: 3, +}; + +function redraw() { + cxgraph.redraw(); +} + +function onViewChange() { + let width = window.innerWidth; + let height = window.innerHeight; + let aspect = width / height; + cxgraph.set_bounds( + graphView.xoff - graphView.scale * aspect, + graphView.yoff - graphView.scale, + graphView.xoff + graphView.scale * aspect, + graphView.yoff + graphView.scale + ); + redraw(); +} + +function onResize() { + let width = window.innerWidth; + let height = window.innerHeight; + cxgraph.resize(width, height); + grid_canvas.width = width; + grid_canvas.height = height; + canvas.style.width = "100vw"; + canvas.style.height = "100vh"; + onViewChange(); +} + +let mouseX = 0.0; +let mouseY = 0.0; +let mousePressed = false; + +function onWheel(e) { + graphView.scale *= Math.exp(e.deltaY * 0.0007); + onViewChange(); +} + +function onMouseDown(e) { + mousePressed = true; + mouseX = e.offsetX; + mouseY = e.offsetY; +} + +function onMouseUp(e) { + mousePressed = false; +} + +function onMouseMove(e) { + if(mousePressed) { + let dX = e.offsetX - mouseX; + let dY = e.offsetY - mouseY; + mouseX = e.offsetX; + mouseY = e.offsetY; + graphView.xoff -= 2.0 * graphView.scale * dX / window.innerHeight; + graphView.yoff += 2.0 * graphView.scale * dY / window.innerHeight; + onViewChange(); + } +} + +function onGraph() { + let src = document.getElementById("source_text").value; + cxgraph.load_shader(src); + redraw(); +} + +window.addEventListener("resize", onResize); +canvas.addEventListener("wheel", onWheel); +canvas.addEventListener("mousedown", onMouseDown); +canvas.addEventListener("mouseup", onMouseUp); +canvas.addEventListener("mousemove", onMouseMove); +button_graph.addEventListener("click", onGraph); + +onResize(); +onGraph(); + +// menu diff --git a/src/wasm.rs b/cxgraph-web/src/lib.rs similarity index 61% rename from src/wasm.rs rename to cxgraph-web/src/lib.rs index 7392763..988c59a 100644 --- a/src/wasm.rs +++ b/cxgraph-web/src/lib.rs @@ -1,8 +1,7 @@ -#![cfg(target_arch="wasm32")] - -use crate::{renderer::WgpuState, language::compile}; -use winit::{event_loop::EventLoop, window::Window}; -use wasm_bindgen::prelude::*; +use libcxgraph::{renderer::WgpuState, language::compile}; +use winit::{window::WindowBuilder, event_loop::EventLoop, platform::web::WindowBuilderExtWebSys}; +use wasm_bindgen::{prelude::*, JsValue}; +use web_sys::HtmlCanvasElement; // wasm is single-threaded so there's no possibility of Bad happening // due to mutable global state. this will be Some(..) once start runs @@ -23,20 +22,22 @@ pub async fn start() { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger"); - let event_loop = EventLoop::new(); - let window = Window::new(&event_loop).unwrap(); - - window.set_inner_size(PhysicalSize::new(100, 100)); - web_sys::window() + let canvas_elem = web_sys::window() .and_then(|win| win.document()) - .and_then(|doc| { - let dst = doc.get_element_by_id("canvas_container")?; - let canvas = web_sys::Element::from(window.canvas()); - dst.append_child(&canvas).ok()?; - Some(()) - }).expect("Couldn't append canvas to document body."); + .and_then(|doc| Some(doc.get_element_by_id("canvas")?)) + .expect("Could not find canvas element"); - window.set_title("window"); + let canvas: HtmlCanvasElement = canvas_elem + .dyn_into() + .expect("Canvas was not a canvas"); + + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .with_canvas(Some(canvas)) + .with_inner_size(PhysicalSize::new(100, 100)) + .with_title("window") + .build(&event_loop) + .expect("Failed to build window"); let size = window.inner_size(); let mut state = WgpuState::new(&window, size.into()).await; @@ -46,19 +47,15 @@ pub async fn start() { } #[wasm_bindgen] -pub fn load_shader(src: &str) { - let wgsl = compile(src).unwrap(); - with_state(|state| { - state.load_shaders(&wgsl); - state.redraw(); - }); +pub fn load_shader(src: &str) -> Result<(), JsValue> { + let wgsl = compile(src).map_err(|e| e.to_string())?; + with_state(|state| state.load_shaders(&wgsl)); + Ok(()) } #[wasm_bindgen] pub fn redraw() { - with_state(|state| { - state.redraw(); - }); + with_state(|state| state.redraw()); } #[wasm_bindgen] diff --git a/cxgraph-web/style.css b/cxgraph-web/style.css new file mode 100644 index 0000000..25e1707 --- /dev/null +++ b/cxgraph-web/style.css @@ -0,0 +1,46 @@ +body, html { + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; +} + +.canvas-container { + position: absolute; + left: 0px; + top: 0px; +} + +canvas { + position: absolute; + left: 0px; + top: 0px; + width: 100vw; + height: 100vh; +} + +#canvas { + z-index: 0; +} + +#grid_canvas { + z-index: 1; + pointer-events: none; +} + +.menu { + position: absolute; + left: 0px; + top: 0px; + margin: 10px; + padding: 10px; + background: #334; + color: #fff; + z-index: 10; +} + + +#source_text { + width: 300px; + height: 500px; +} diff --git a/index.html b/index.html deleted file mode 100644 index ec3e401..0000000 --- a/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - cxgraph - - - -
- -
- -
- -
-
-
- - - diff --git a/libcxgraph/Cargo.toml b/libcxgraph/Cargo.toml new file mode 100644 index 0000000..80cb80c --- /dev/null +++ b/libcxgraph/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "libcxgraph" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[features] +webgl = ["wgpu/webgl"] + +[dependencies] +log = "0.4" +lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] } +num-complex = "0.4" +wgpu = "0.16" +cgmath = "0.18" +encase = { version = "0.6", features = ["cgmath"] } +raw-window-handle = "0.5" + +[build-dependencies] +lalrpop = "0.20.0" diff --git a/libcxgraph/build.rs b/libcxgraph/build.rs new file mode 100644 index 0000000..ca5c283 --- /dev/null +++ b/libcxgraph/build.rs @@ -0,0 +1,3 @@ +fn main() { + lalrpop::process_root().unwrap(); +} diff --git a/libcxgraph/src/language/ast.rs b/libcxgraph/src/language/ast.rs new file mode 100644 index 0000000..b95a2de --- /dev/null +++ b/libcxgraph/src/language/ast.rs @@ -0,0 +1,126 @@ +use std::fmt; + +use num_complex::Complex64 as Complex; + +#[derive(Clone, Copy, Debug)] +pub enum BinaryOp { + Add, Sub, Mul, Div, Pow, +} + +#[derive(Clone, Copy, Debug)] +pub enum UnaryOp { + Pos, Neg, Conj +} + +#[derive(Clone, Copy, Debug)] +pub enum ExpressionType<'a> { + Number(Complex), + Name(&'a str), + Binary(BinaryOp), + Unary(UnaryOp), + FnCall(&'a str), + Store(&'a str), + Sum { countvar: &'a str, min: i32, max: i32 }, + Prod { countvar: &'a str, min: i32, max: i32 }, + Iter { itervar: &'a str, count: i32 }, +} + +#[derive(Clone, Debug)] +pub struct Expression<'a> { + pub ty: ExpressionType<'a>, + pub children: Vec>, +} + +impl<'a> Expression<'a> { + pub fn new_number(x: f64) -> Self { + Self { ty: ExpressionType::Number(Complex::new(x, 0.0)), children: Vec::with_capacity(0) } + } + + pub fn new_name(n: &'a str) -> Self { + Self { ty: ExpressionType::Name(n), children: Vec::with_capacity(0) } + } + + pub fn new_unary(op: UnaryOp, arg: Self) -> Self { + Self { ty: ExpressionType::Unary(op), children: vec![arg] } + } + + pub fn new_binary(op: BinaryOp, arg0: Self, arg1: Self) -> Self { + Self { ty: ExpressionType::Binary(op), children: vec![arg0, arg1] } + } + + pub fn new_fncall(name: &'a str, args: Vec) -> Self { + Self { ty: ExpressionType::FnCall(name), children: args } + } + + pub fn new_store(expr: Self, name: &'a str) -> Self { + Self { ty: ExpressionType::Store(name), children: vec![expr] } + } + + pub fn new_sum(countvar: &'a str, min: i32, max: i32, body: Vec) -> Self { + Self { + ty: ExpressionType::Sum { countvar, min, max }, + children: body, + } + } + + pub fn new_prod(accvar: &'a str, min: i32, max: i32, body: Vec) -> Self { + Self { + ty: ExpressionType::Prod { countvar: accvar, min, max }, + children: body, + } + } + + pub fn new_iter(itervar: &'a str, count: i32, init: Self, mut body: Vec) -> Self { + body.push(init); + Self { + ty: ExpressionType::Iter { itervar, count }, + children: body, + } + } +} + +pub enum Definition<'a> { + Constant { name: &'a str, value: Vec> }, + Function { name: &'a str, args: Vec<&'a str>, value: Vec> }, +} + +fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt::Result { + let indent = depth*2; + match expr.ty { + ExpressionType::Number(n) => write!(w, "{:indent$}NUMBER {n:?}", "", indent=indent)?, + ExpressionType::Name(n) => write!(w, "{:indent$}NAME {n}", "", indent=indent)?, + ExpressionType::Binary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?, + ExpressionType::Unary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?, + ExpressionType::FnCall(f) => write!(w, "{:indent$}CALL {f}", "", indent=indent)?, + ExpressionType::Store(n) => write!(w, "{:indent$}STORE {n}", "", indent=indent)?, + ExpressionType::Sum { countvar, min, max } => write!(w, "{:indent$}SUM {countvar} {min} {max}", "", indent=indent)?, + ExpressionType::Prod { countvar, min, max } => write!(w, "{:indent$}PROD {countvar} {min} {max}", "", indent=indent)?, + ExpressionType::Iter { itervar, count } => write!(w, "{:indent$}ITER {itervar} {count}", "", indent=indent)?, + } + writeln!(w)?; + for child in &expr.children { + display_expr(w, child, depth + 1)?; + } + Ok(()) +} + +pub fn display_def(w: &mut impl fmt::Write, def: &Definition) -> fmt::Result { + match def { + Definition::Constant { name, value } => { + writeln!(w, "CONSTANT {name}")?; + for expr in value { + display_expr(w, expr, 1)?; + } + }, + Definition::Function { name, args, value } => { + writeln!(w, "FUNCTION {name}")?; + for arg in args { + writeln!(w, " ARG {arg}")?; + } + for expr in value { + display_expr(w, expr, 1)?; + } + } + } + Ok(()) +} diff --git a/libcxgraph/src/language/builtins.rs b/libcxgraph/src/language/builtins.rs new file mode 100644 index 0000000..6647422 --- /dev/null +++ b/libcxgraph/src/language/builtins.rs @@ -0,0 +1,105 @@ +use std::{collections::HashMap, ops, f64::consts::{TAU, E}}; + +use num_complex::Complex64 as Complex; + +#[derive(Clone, Copy)] +pub enum Func { + One(fn(Complex) -> Complex), + Two(fn(Complex, Complex) -> Complex), +} + +impl Func { + pub fn argc(&self) -> usize { + match self { + Func::One(_) => 1, + Func::Two(_) => 2, + } + } +} + +fn neg(z: Complex) -> Complex { -z } +fn re(z: Complex) -> Complex { Complex::from(z.re) } +fn im(z: Complex) -> Complex { Complex::from(z.im) } +fn abs_sq(z: Complex) -> Complex { Complex::from(z.norm_sqr()) } +fn abs(z: Complex) -> Complex { Complex::from(z.norm()) } +fn arg(z: Complex) -> Complex { Complex::from(z.arg()) } +fn recip(z: Complex) -> Complex { Complex::new(1.0, 0.0) / z } +fn conj(z: Complex) -> Complex { z.conj() } + + +fn gamma(z: Complex) -> Complex { + let reflect = z.re < 0.5; + let zp = if reflect { 1.0 - z } else { z }; + let mut w = gamma_inner(zp + 3.0) / (zp*(zp+1.0)*(zp+2.0)*(zp+3.0)); + if reflect { + w = TAU * 0.5 / ((TAU * 0.5 * z).sin() * w); + } + return w; +} + +// Yang, ZH., Tian, JF. An accurate approximation formula for gamma function. J Inequal Appl 2018, 56 (2018). +// https://doi.org/10.1186/s13660-018-1646-6 +fn gamma_inner(z: Complex) -> Complex { + let z2 = z * z; + let z3 = z2 * z; + + let a = (TAU * z).sqrt(); + let b = (1.0 / (E * E) * z3 * (1.0/z).sinh()).powc(0.5 * z); + let c = ((7.0/324.0) / (z3 * (35.0 * z2 + 33.0))).exp(); + + return a * b * c; +} + +thread_local! { + pub static BUILTIN_FUNCS: HashMap<&'static str, (&'static str, Func)> = { + let mut m = HashMap::new(); + m.insert("pos", ("c_pos", Func::One(std::convert::identity))); + m.insert("neg", ("c_neg", Func::One(neg))); + m.insert("recip", ("c_recip", Func::One(recip))); + m.insert("conj", ("c_conj", Func::One(conj))); + + m.insert("re", ("c_re", Func::One(re))); + m.insert("im", ("c_im", Func::One(im))); + m.insert("abs_sq", ("c_abs_sq", Func::One(abs_sq))); + m.insert("abs", ("c_abs", Func::One(abs))); + m.insert("arg", ("c_arg", Func::One(arg))); + + m.insert("add", ("c_add", Func::Two(::add))); + m.insert("sub", ("c_sub", Func::Two(::sub))); + m.insert("mul", ("c_mul", Func::Two(::mul))); + m.insert("div", ("c_div", Func::Two(::div))); + m.insert("pow", ("c_pow", Func::Two(Complex::powc))); + + m.insert("exp", ("c_exp", Func::One(Complex::exp))); + m.insert("log", ("c_log", Func::One(Complex::ln))); + m.insert("sqrt", ("c_sqrt", Func::One(Complex::sqrt))); + + m.insert("sin", ("c_sin", Func::One(Complex::sin))); + m.insert("cos", ("c_cos", Func::One(Complex::cos))); + m.insert("tan", ("c_tan", Func::One(Complex::tan))); + m.insert("sinh", ("c_sinh", Func::One(Complex::sinh))); + m.insert("cosh", ("c_cosh", Func::One(Complex::cosh))); + m.insert("tanh", ("c_tanh", Func::One(Complex::tanh))); + + m.insert("asin", ("c_asin", Func::One(Complex::asin))); + m.insert("acos", ("c_acos", Func::One(Complex::acos))); + m.insert("atan", ("c_atan", Func::One(Complex::atan))); + m.insert("asinh", ("c_asinh", Func::One(Complex::asinh))); + m.insert("acosh", ("c_acosh", Func::One(Complex::acosh))); + m.insert("atanh", ("c_atanh", Func::One(Complex::atanh))); + + m.insert("gamma", ("c_gamma", Func::One(gamma))); + m.insert("\u{0393}", ("c_gamma", Func::One(gamma))); + + m + }; + + pub static BUILTIN_CONSTS: HashMap<&'static str, (&'static str, Complex)> = { + let mut m = HashMap::new(); + m.insert("tau", ("C_TAU", Complex::new(std::f64::consts::TAU, 0.0))); + m.insert("\u{03C4}", ("C_TAU", Complex::new(std::f64::consts::TAU, 0.0))); + m.insert("e", ("C_E", Complex::new(std::f64::consts::E, 0.0))); + m.insert("i", ("C_I", Complex::new(0.0, 1.0))); + m + } +} diff --git a/libcxgraph/src/language/compiler.rs b/libcxgraph/src/language/compiler.rs new file mode 100644 index 0000000..d22092c --- /dev/null +++ b/libcxgraph/src/language/compiler.rs @@ -0,0 +1,307 @@ +use std::{collections::{HashSet, HashMap}, fmt}; + +use super::{ast::{Definition, Expression, ExpressionType, BinaryOp, UnaryOp}, builtins::{BUILTIN_CONSTS, BUILTIN_FUNCS}}; + +#[derive(Clone, Debug)] +pub struct CompileError(String); + +impl fmt::Display for CompileError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl std::error::Error for CompileError {} + +impl From for CompileError { + fn from(value: String) -> Self { + Self(value) + } +} + +impl From for CompileError { + fn from(value: fmt::Error) -> Self { + Self(value.to_string()) + } +} + +const GREEK_LOWER: [&str; 25] = [ + "Al", "Be", "Ga", "De", "Ep", + "Ze", "Et", "Th", "Io", "Ka", + "La", "Mu", "Nu", "Xi", "Om", + "Pi", "Rh", "Sj", "Si", "Ta", + "Yp", "Ph", "Ch", "Ps", "Oa", +]; + +const GREEK_UPPER: [&str; 25] = [ + "AL", "BE", "GA", "DE", "EP", + "ZE", "ET", "TH", "IO", "KA", + "LA", "MU", "NU", "XI", "OM", + "PI", "RH", "SJ", "SI", "TA", + "YP", "PH", "CH", "PS", "OA", +]; + +fn format_char(buf: &mut String, c: char) { + match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' => buf.push(c), + '_' => buf.push_str("__"), + '\'' => buf.push_str("_p"), + '\u{0391}'..='\u{03A9}' => { + buf.push('_'); + buf.push_str(GREEK_UPPER[c as usize - 0x0391]) + }, + '\u{03B1}'..='\u{03C9}' => { + buf.push('_'); + buf.push_str(GREEK_LOWER[c as usize - 0x03B1]) + }, + c => { + buf.push('_'); + let mut b = [0u8; 8]; + let s = c.encode_utf8(&mut b); + for b in s.bytes() { + buf.push(char::from_digit(b as u32 >> 4, 16).unwrap()); + buf.push(char::from_digit(b as u32 & 0x0f, 16).unwrap()); + } + } + } +} + +fn format_name(prefix: &str, name: &str) -> String { + let mut result = prefix.to_owned(); + result.reserve(name.len()); + for c in name.chars() { + format_char(&mut result, c); + } + result +} + +fn format_func(name: &str) -> String { format_name("func_", name) } +fn format_const(name: &str) -> String { format_name("const_", name) } +fn format_arg(name: &str) -> String { format_name("arg_", name) } +fn format_local(name: &str) -> String { format_name("local_", name) } +fn format_tmp(idx: usize) -> String { format!("tmp_{}", idx) } + +pub struct Compiler<'w, 'i, W: fmt::Write> { + buf: &'w mut W, + global_funcs: HashMap<&'i str, usize>, + global_consts: HashSet<&'i str>, +} + +#[derive(Clone)] +struct LocalState<'i> { + local_vars: HashSet<&'i str>, + next_tmp: usize, +} + +impl<'i> LocalState<'i> { + pub fn new() -> Self { + Self { + local_vars: HashSet::new(), + next_tmp: 0, + } + } + + pub fn next_tmp(&mut self) -> String { + let n = self.next_tmp; + self.next_tmp += 1; + format_tmp(n) + } +} + +impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> { + pub fn new(buf: &'w mut W) -> Self { + Self { + buf, + global_consts: HashSet::new(), + global_funcs: HashMap::new(), + } + } + + pub fn compile_defn(&mut self, defn: &Definition<'i>) -> Result<(), CompileError> { + match defn { + Definition::Function { name, args, value } => { + if self.global_consts.contains(name) || self.global_funcs.contains_key(name) { + return Err(format!("name {name} is already declared in global scope").into()) + } + write!(self.buf, "fn {}(", format_func(name))?; + for arg in args { + write!(self.buf, "{}: vec2f, ", format_arg(arg))?; + } + writeln!(self.buf, ") -> vec2f {{")?; + + let mut local = LocalState::new(); + for arg in args { + writeln!(self.buf, "var {} = {};", format_local(arg), format_arg(arg))?; + local.local_vars.insert(arg); + } + + let mut last = String::with_capacity(0); + for expr in value { + last = self.compile_expr(&mut local, expr)?; + } + writeln!(self.buf, "return {last};\n}}")?; + + self.global_funcs.insert(name, args.len()); + Ok(()) + } + Definition::Constant { name, value } => { + if self.global_consts.contains(name) || self.global_funcs.contains_key(name) { + return Err(format!("name {name} is already declared in global scope").into()) + } + + writeln!(self.buf, "fn {}() -> vec2f {{", format_const(name))?; + let mut local = LocalState::new(); + + let mut last = String::with_capacity(0); + for expr in value { + last = self.compile_expr(&mut local, expr)?; + } + writeln!(self.buf, "return {last};\n}}")?; + + self.global_consts.insert(name); + Ok(()) + } + } + } + + pub fn ensure_plot_defined(&self) -> Result<(), CompileError> { + if self.global_funcs.contains_key("plot") { + Ok(()) + } else { + Err("No plot function defined".to_owned().into()) + } + } + + fn compile_expr(&mut self, local: &mut LocalState<'i>, expr: &Expression<'i>) + -> Result { + match expr.ty { + ExpressionType::Name(v) => self.resolve_var(local, v), + ExpressionType::Store(var) => { + let a = self.compile_expr(local, &expr.children[0])?; + let name = format_local(var); + + if !local.local_vars.contains(var) { + write!(self.buf, "var ")?; + local.local_vars.insert(var); + } + + writeln!(self.buf, "{name} = {a};")?; + Ok(name) + }, + ExpressionType::Number(n) => { + let name = local.next_tmp(); + writeln!(self.buf, "var {name} = vec2f({:?}, {:?});", n.re, n.im)?; + Ok(name) + }, + ExpressionType::Binary(op) => { + let a = self.compile_expr(local, &expr.children[0])?; + let b = self.compile_expr(local, &expr.children[1])?; + let name = local.next_tmp(); + + match op { + BinaryOp::Add => writeln!(self.buf, "var {name} = {a} + {b};")?, + BinaryOp::Sub => writeln!(self.buf, "var {name} = {a} - {b};")?, + BinaryOp::Mul => writeln!(self.buf, "var {name} = c_mul({a}, {b});")?, + BinaryOp::Div => writeln!(self.buf, "var {name} = c_div({a}, {b});")?, + BinaryOp::Pow => writeln!(self.buf, "var {name} = c_pow({a}, {b});")?, + } + + Ok(name) + }, + ExpressionType::Unary(op) => { + let a = self.compile_expr(local, &expr.children[0])?; + let name = local.next_tmp(); + + match op { + UnaryOp::Pos => writeln!(self.buf, "var {name} = {a};")?, + UnaryOp::Neg => writeln!(self.buf, "var {name} = -{a};")?, + UnaryOp::Conj => writeln!(self.buf, "var {name} = c_conj({a});")?, + } + + Ok(name) + }, + ExpressionType::FnCall(f) => { + let (fname, argc) = self.resolve_func(f)?; + if argc != expr.children.len() { + return Err(format!("function {f} expected {argc} args, got {}", expr.children.len()).into()) + } + + let mut args = Vec::with_capacity(expr.children.len()); + for child in &expr.children { + args.push(self.compile_expr(local, child)?); + } + + let name = local.next_tmp(); + write!(self.buf, "var {name} = {fname}(", )?; + for arg in args { + write!(self.buf, "{arg}, ")?; + } + writeln!(self.buf, ");")?; + + Ok(name) + }, + ExpressionType::Sum { countvar, min, max } + | ExpressionType::Prod { countvar, min, max } => { + let acc = local.next_tmp(); + let ivar = local.next_tmp(); + if matches!(expr.ty, ExpressionType::Sum { .. }) { + writeln!(self.buf, "var {acc} = vec2f(0.0, 0.0);")?; + } else { + writeln!(self.buf, "var {acc} = vec2f(1.0, 0.0);")?; + } + writeln!(self.buf, "for(var {ivar}: i32 = {min}; {ivar} <= {max}; {ivar}++) {{")?; + writeln!(self.buf, "var {} = vec2f(f32({ivar}), 0.0);", format_local(countvar))?; + let mut loop_local = local.clone(); + loop_local.local_vars.insert(countvar); + let mut last = String::new(); + for child in &expr.children { + last = self.compile_expr(&mut loop_local, child)?; + } + if matches!(expr.ty, ExpressionType::Sum { .. }) { + writeln!(self.buf, "{acc} = {acc} + {last};\n}}")?; + } else { + writeln!(self.buf, "{acc} = c_mul({acc}, {last});\n}}")?; + } + Ok(acc) + }, + ExpressionType::Iter { itervar, count } => { + let init = expr.children.last().unwrap(); + let itervar_fmt = format_local(itervar); + let v = self.compile_expr(local, init)?; + writeln!(self.buf, "var {itervar_fmt} = {v};")?; + let ivar = local.next_tmp(); + writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} <= {count}; {ivar}++) {{")?; + let mut loop_local = local.clone(); + loop_local.local_vars.insert(itervar); + let mut last = String::new(); + for child in &expr.children[..expr.children.len() - 1] { + last = self.compile_expr(&mut loop_local, child)?; + } + writeln!(self.buf, "{itervar_fmt} = {last};\n}}")?; + Ok(itervar_fmt) + } + } + } + + fn resolve_func(&self, name: &str) -> Result<(String, usize), CompileError> { + if let Some(argc) = self.global_funcs.get(name) { + Ok((format_func(name), *argc)) + } else if let Some((var, f)) = BUILTIN_FUNCS.with(|c| c.get(name).copied()) { + Ok(((*var).to_owned(), f.argc())) + } else { + Err(format!("use of undeclared function {name}").into()) + } + } + + fn resolve_var(&self, local: &LocalState, name: &str) -> Result { + if local.local_vars.contains(name) { + Ok(format_local(name)) + } else if self.global_consts.contains(name) { + Ok(format_const(name) + "()") + } else if let Some(var) = BUILTIN_CONSTS.with(|c| Some(c.get(name)?.0)) { + Ok(var.to_owned()) + } else { + Err(format!("use of undeclared variable {name}").into()) + } + } +} diff --git a/libcxgraph/src/language/mod.rs b/libcxgraph/src/language/mod.rs new file mode 100644 index 0000000..0163b2d --- /dev/null +++ b/libcxgraph/src/language/mod.rs @@ -0,0 +1,26 @@ +use lalrpop_util::lalrpop_mod; + +use crate::language::token::Lexer; + +use self::compiler::Compiler; + +mod token; +mod ast; +mod compiler; +mod builtins; + +lalrpop_mod!(pub syntax, "/language/syntax.rs"); + +pub fn compile(src: &str) -> Result> { + let lexer = Lexer::new(src); + let result = syntax::ProgramParser::new() + .parse(src, lexer) + .map_err(|e| e.to_string())?; + let mut wgsl = String::new(); + let mut cmp = Compiler::new(&mut wgsl); + for defn in result { + cmp.compile_defn(&defn)?; + } + cmp.ensure_plot_defined()?; + Ok(wgsl) +} diff --git a/libcxgraph/src/language/syntax.lalrpop b/libcxgraph/src/language/syntax.lalrpop new file mode 100644 index 0000000..9b7a87f --- /dev/null +++ b/libcxgraph/src/language/syntax.lalrpop @@ -0,0 +1,123 @@ +use crate::language::ast::*; +use crate::language::token::*; + +grammar<'input>(input: &'input str); + +extern { + type Location = usize; + type Error = LexerError; + + enum Token<'input> { + "(" => Token::LParen, + ")" => Token::RParen, + "{" => Token::LBrace, + "}" => Token::RBrace, + "+" => Token::Plus, + "-" => Token::Minus, + "*" => Token::Star, + "/" => Token::Slash, + "^" => Token::Caret, + "," => Token::Comma, + "->" => Token::Arrow, + "=" => Token::Equal, + ":" => Token::Colon, + "\n" => Token::Newline, + "sum" => Token::Sum, + "prod" => Token::Prod, + "iter" => Token::Iter, + Float => Token::Float(), + Int => Token::Int(), + Name => Token::Name(<&'input str>), + } +} + +// Definitions + +pub Program: Vec> = Definitions; + +Definitions: Vec> = { + "\n"* "\n"+)*> => defs.into_iter().chain(last).collect(), +} + +Definition: Definition<'input> = { + "(" ",")*> ")" "=" => Definition::Function { + name: n, + args: args.into_iter().chain(last).collect(), + value: exs, + }, + "=" => Definition::Constant { + name: n, + value: exs, + }, +} + +// Expressions + +Exprs: Vec> = { + ",")*> => args.into_iter().chain(last).collect(), +} + +Expr: Expression<'input> = Store; + +Store: Expression<'input> = { + "->" => Expression::new_store(a, n), + Sum, +} + +Sum: Expression<'input> = { + "+" => Expression::new_binary(BinaryOp::Add, a, b), + "-" => Expression::new_binary(BinaryOp::Sub, a, b), + Product, +} + +Product: Expression<'input> = { + "*" => Expression::new_binary(BinaryOp::Mul, a, b), + "/" => Expression::new_binary(BinaryOp::Div, a, b), + Unary, +} + +Unary: Expression<'input> = { + "+" => Expression::new_unary(UnaryOp::Pos, a), + "-" => Expression::new_unary(UnaryOp::Neg, a), + "*" => Expression::new_unary(UnaryOp::Conj, a), + => Expression::new_binary(BinaryOp::Mul, a, b), + Power, +} + +Juxtapose: Expression<'input> = { + => Expression::new_binary(BinaryOp::Mul, a, b), + PreJuxtapose, +} + +Power: Expression<'input> = { + "^" => Expression::new_binary(BinaryOp::Pow, a, b), + FnCall, +} + +FnCall: Expression<'input> = { + "(" ")" + => Expression::new_fncall(n, args), + +} + +PreJuxtapose: Expression<'input> = { + Number, + "(" ")", +} + +Item: Expression<'input> = { + Number, + => Expression::new_name(n), + "(" ")", + "sum" "(" ":" "," ")" "{" "}" + => Expression::new_sum(name, min, max, exs), + "prod" "(" ":" "," ")" "{" "}" + => Expression::new_prod(name, min, max, exs), + "iter" "(" "," ":" ")" "{" "}" + => Expression::new_iter(name, count, init, exs), +} + +Number: Expression<'input> = { + => Expression::new_number(n), + => Expression::new_number(n as f64), +} diff --git a/libcxgraph/src/language/token.rs b/libcxgraph/src/language/token.rs new file mode 100644 index 0000000..ad01236 --- /dev/null +++ b/libcxgraph/src/language/token.rs @@ -0,0 +1,159 @@ +use std::{str::CharIndices, iter::Peekable, fmt}; + + +#[derive(Clone, Copy, Debug)] +pub enum Token<'i> { + Float(f64), + Int(i32), + Name(&'i str), + Sum, Prod, Iter, + LParen, RParen, + LBrace, RBrace, + Plus, Minus, Star, Slash, Caret, + Comma, Arrow, Equal, Colon, + Newline, +} + +impl<'i> fmt::Display for Token<'i> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Token::Float(n) => write!(f, "{n}"), + Token::Int(n) => write!(f, "{n}"), + Token::Name(n) => write!(f, "{n}"), + Token::Sum => f.write_str("sum"), + Token::Prod => f.write_str("prod"), + Token::Iter => f.write_str("iter"), + Token::LParen => f.write_str("("), + Token::RParen => f.write_str(")"), + Token::LBrace => f.write_str("{{"), + Token::RBrace => f.write_str("}}"), + Token::Plus => f.write_str("+"), + Token::Minus => f.write_str("-"), + Token::Star => f.write_str("*"), + Token::Slash => f.write_str("/"), + Token::Caret => f.write_str("^"), + Token::Comma => f.write_str(","), + Token::Arrow => f.write_str("->"), + Token::Equal => f.write_str("="), + Token::Colon => f.write_str(":"), + Token::Newline => f.write_str("newline") + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum LexerError { + Unexpected(usize, char), + InvalidNumber(usize, usize), +} + +impl fmt::Display for LexerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"), + LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"), + } + } +} + +pub type Spanned = Result<(L, T, L), E>; + +pub struct Lexer<'i> { + src: &'i str, + chars: Peekable>, +} + +fn is_ident_begin(c: char) -> bool { + c.is_alphabetic() +} + +fn is_ident_middle(c: char) -> bool { + c.is_alphanumeric() || c == '_' || c == '\'' +} + +impl<'i> Lexer<'i> { + pub fn new(src: &'i str) -> Self { + Self { src, chars: src.char_indices().peekable() } + } + + fn next_number(&mut self, i: usize, mut has_dot: bool) -> Spanned, usize, LexerError> { + let mut j = i; + + while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) { + j = self.chars.next().unwrap().0; + } + + if !has_dot && matches!(self.chars.peek(), Some((_, '.'))) { + j = self.chars.next().unwrap().0; + has_dot = true; + while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) { + j = self.chars.next().unwrap().0; + } + } + + let s = &self.src[i..j+1]; + if !has_dot { + if let Ok(n) = s.parse::() { + return Ok((i, Token::Int(n), j+1)) + } + } + match s.parse::() { + Ok(n) => Ok((i, Token::Float(n), j+1)), + Err(_) => Err(LexerError::InvalidNumber(i, j+1)), + } + } + + fn next_word(&mut self, i: usize, mut j: usize) -> Spanned, usize, LexerError> { + while self.chars.peek().is_some_and(|(_, c)| is_ident_middle(*c)) { + j += self.chars.next().unwrap().1.len_utf8(); + } + + let s = &self.src[i..j]; + match s { + "sum" => Ok((i, Token::Sum, j)), + "prod" => Ok((i, Token::Prod, j)), + "iter" => Ok((i, Token::Iter, j)), + _ => Ok((i, Token::Name(s), j)), + } + } + + fn next_token(&mut self) -> Option, usize, LexerError>> { + while matches!(self.chars.peek(), Some((_, ' ' | '\t' | '\r'))) { + self.chars.next(); + } + + Some(match self.chars.next()? { + (i, '(') => Ok((i, Token::LParen, i + 1)), + (i, ')') => Ok((i, Token::RParen, i + 1)), + (i, '{') => Ok((i, Token::LBrace, i + 1)), + (i, '}') => Ok((i, Token::RBrace, i + 1)), + (i, '+') => Ok((i, Token::Plus, i + 1)), + (i, '-') => match self.chars.peek() { + Some((_, '>')) => { + self.chars.next(); + Ok((i, Token::Arrow, i + 2)) + }, + _ => Ok((i, Token::Minus, i + 1)), + } + (i, '*') => Ok((i, Token::Star, i + 1)), + (i, '/') => Ok((i, Token::Slash, i + 1)), + (i, '^') => Ok((i, Token::Caret, i + 1)), + (i, ',') => Ok((i, Token::Comma, i + 1)), + (i, '=') => Ok((i, Token::Equal, i + 1)), + (i, ':') => Ok((i, Token::Colon, i + 1)), + (i, '\n') => Ok((i, Token::Newline, i + 1)), + (i, '0'..='9') => self.next_number(i, false), + (i, '.') => self.next_number(i, true), + (i, c) if is_ident_begin(c) => self.next_word(i, i + c.len_utf8()), + (i, c) => Err(LexerError::Unexpected(i, c)), + }) + } +} + +impl<'i> Iterator for Lexer<'i> { + type Item = Spanned, usize, LexerError>; + + fn next(&mut self) -> Option { + self.next_token() + } +} diff --git a/src/lib.rs b/libcxgraph/src/lib.rs similarity index 53% rename from src/lib.rs rename to libcxgraph/src/lib.rs index e633d0b..1508261 100644 --- a/src/lib.rs +++ b/libcxgraph/src/lib.rs @@ -1,4 +1,2 @@ pub mod language; pub mod renderer; -pub mod complex; -pub mod wasm; diff --git a/src/renderer/shader.wgsl b/libcxgraph/src/renderer/fragment.wgsl similarity index 79% rename from src/renderer/shader.wgsl rename to libcxgraph/src/renderer/fragment.wgsl index 06229fd..117a0b3 100644 --- a/src/renderer/shader.wgsl +++ b/libcxgraph/src/renderer/fragment.wgsl @@ -26,6 +26,10 @@ fn remap(val: vec2f, a1: vec2f, b1: vec2f, a2: vec2f, b2: vec2f) -> vec2f { const TAU = 6.283185307179586; const E = 2.718281828459045; +const C_TAU = vec2f(TAU, 0.0); +const C_E = vec2f(E, 0.0); +const C_I = vec2f(0.0, 1.0); + ///////////////////////// // complex functions // ///////////////////////// @@ -122,6 +126,41 @@ fn c_tanh(z: vec2f) -> vec2f { return vec2(sinh(2.0*z.x), sin(2.0*z.y)) / (cosh(2.0*z.x) + cos(2.0*z.y)); } +fn c_asin(z: vec2f) -> vec2f { + let u = c_sqrt(vec2f(1.0, 0.0) - c_mul(z, z)); + let v = c_log(u + vec2(-z.y, z.x)); + return vec2(v.y, -v.x); +} + +fn c_acos(z: vec2f) -> vec2f { + let u = c_sqrt(vec2f(1.0, 0.0) - c_mul(z, z)); + let v = c_log(u + vec2f(-z.y, z.x)); + return vec2f(TAU*0.25 - v.y, v.x); +} + +fn c_atan(z: vec2f) -> vec2f { + let u = vec2f(1.0, 0.0) - vec2f(-z.y, z.x); + let v = vec2f(1.0, 0.0) + vec2f(-z.y, z.x); + let w = c_log(c_div(u, v)); + return 0.5 * vec2f(-w.y, w.x); +} + +fn c_asinh(z: vec2f) -> vec2f { + let u = c_sqrt(vec2f(1.0, 0.0) + c_mul(z, z)); + return c_log(u + z); +} + +fn c_acosh(z: vec2f) -> vec2f { + let u = c_sqrt(vec2f(-1.0, 0.0) + c_mul(z, z)); + return c_log(u + z); +} + +fn c_atanh(z: vec2f) -> vec2f { + let u = vec2f(1.0, 0.0) + z; + let v = vec2f(1.0, 0.0) - z; + return 0.5 * c_log(c_div(u, v)); +} + fn c_gamma(z: vec2f) -> vec2f { let reflect = z.x < 0.5; var zp = z; @@ -153,46 +192,26 @@ fn c_gamma_inner2(z: vec2f) -> vec2f { return c_div(w, c_mul(c_mul(z, z + vec2(1.0, 0.0)), c_mul(z + vec2(2.0, 0.0), z + vec2(3.0, 0.0)))); } -const D_EPS = 0.01; - ///////////////// -// user code // +// rendering // ///////////////// -//INCLUDE// - -////////////// -// vertex // -////////////// - -@vertex -fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f { - var pos = array( - vec2(-1.0,-1.0), - vec2( 1.0,-1.0), - vec2(-1.0, 1.0), - vec2( 1.0, 1.0), - ); - - return vec4f(pos[in_vertex_index], 0.0, 1.0); -} - -//////////////// -// fragment // -//////////////// - fn hsv2rgb(c: vec3f) -> vec3f { let p = abs(fract(c.xxx + vec3f(1.0, 2.0/3.0, 1.0/3.0)) * 6.0 - vec3f(3.0)); return c.z * mix(vec3f(1.0), clamp(p - vec3f(1.0), vec3f(0.0), vec3f(1.0)), c.y); } fn shademap(r: f32) -> f32 { - return r*inverseSqrt(r * r + 0.0625 * uniforms.shading_intensity)*0.9875 + 0.0125; + return r*inverseSqrt(r * r + 0.0625 * uniforms.shading_intensity); } fn colorfor(w: vec2f) -> vec3f { let z = func_plot(w); + if z.x != z.x || z.y != z.y { + return vec3f(0.5, 0.5, 0.5); + } + let r = sqrt(z.x*z.x + z.y*z.y); let arg = atan2(z.y, z.x); let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/r), shademap(r)); @@ -200,10 +219,9 @@ fn colorfor(w: vec2f) -> vec3f { } @fragment -fn fs_main(@builtin(position) in: vec4f) -> @location(0) vec4f { +fn main(@builtin(position) in: vec4f) -> @location(0) vec4f { let pos = vec2(in.x, f32(uniforms.resolution.y) - in.y); let z = remap(pos, vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max); - //let dz = z - remap(pos + vec2f(1.0), vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max); let col = colorfor(z); diff --git a/src/renderer/mod.rs b/libcxgraph/src/renderer/mod.rs similarity index 91% rename from src/renderer/mod.rs rename to libcxgraph/src/renderer/mod.rs index 755fbe8..4a6424f 100644 --- a/src/renderer/mod.rs +++ b/libcxgraph/src/renderer/mod.rs @@ -1,6 +1,7 @@ use std::num::NonZeroU64; use encase::{ShaderType, ShaderSize, UniformBuffer}; +use log::warn; use wgpu::util::DeviceExt; type Vec2u = cgmath::Vector2; @@ -124,23 +125,28 @@ impl WgpuState { pub fn load_shaders(&mut self, userdefs: &str) { // Shaders // - let src = include_str!("shader.wgsl"); - let src = src.replace("//INCLUDE//\n", userdefs); + let vertex_src = include_str!("vertex.wgsl").to_owned(); + let fragment_src = include_str!("fragment.wgsl").to_owned() + userdefs; - let shader = self.device.create_shader_module(wgpu::ShaderModuleDescriptor { + let vertex_module = self.device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, - source: wgpu::ShaderSource::Wgsl(src.into()) + source: wgpu::ShaderSource::Wgsl(vertex_src.into()) + }); + + let fragment_module = self.device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(fragment_src.into()) }); let vertex = wgpu::VertexState { - module: &shader, - entry_point: "vs_main", + module: &vertex_module, + entry_point: "main", buffers: &[] }; let fragment = wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", + module: &fragment_module, + entry_point: "main", targets: &[Some(wgpu::ColorTargetState { format: self.config.format, blend: Some(wgpu::BlendState { diff --git a/libcxgraph/src/renderer/vertex.wgsl b/libcxgraph/src/renderer/vertex.wgsl new file mode 100644 index 0000000..4d6899c --- /dev/null +++ b/libcxgraph/src/renderer/vertex.wgsl @@ -0,0 +1,11 @@ +@vertex +fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f { + var pos = array( + vec2(-1.0,-1.0), + vec2( 1.0,-1.0), + vec2(-1.0, 1.0), + vec2( 1.0, 1.0), + ); + + return vec4f(pos[in_vertex_index], 0.0, 1.0); +} diff --git a/src/complex.rs b/src/complex.rs deleted file mode 100644 index c9018ed..0000000 --- a/src/complex.rs +++ /dev/null @@ -1,132 +0,0 @@ -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Complex { - pub re: f64, - pub im: f64 -} - -impl Complex { - pub const fn new(re: f64, im: f64) -> Self { - Self { re, im } - } -} - -impl From for Complex { - fn from(value: f64) -> Self { - Self { re: value, im: 0.0 } - } -} - -pub mod cxfn { - use super::Complex; - - pub type Function = fn(args: &[Complex]) -> Complex; - - pub fn pos(a: &[Complex]) -> Complex { - a[0] - } - - pub fn neg(a: &[Complex]) -> Complex { - Complex::new(-a[0].re, -a[0].im) - } - - pub fn recip(a: &[Complex]) -> Complex { - let a = a[0]; - let d = a.re*a.re + a.im*a.im; - Complex::new(a.re / d, -a.im / d) - } - - pub fn re(a: &[Complex]) -> Complex { - Complex::new(a[0].re, 0.0) - } - - pub fn im(a: &[Complex]) -> Complex { - Complex::new(a[0].im, 0.0) - } - - pub fn conj(a: &[Complex]) -> Complex { - Complex::new(a[0].re, -a[0].im) - } - - pub fn abs_sq(a: &[Complex]) -> Complex { - Complex::new(a[0].re*a[0].re + a[0].im*a[0].im, 0.0) - } - - pub fn abs(a: &[Complex]) -> Complex { - Complex::new((a[0].re*a[0].re + a[0].im*a[0].im).sqrt(), 0.0) - } - - pub fn arg(a: &[Complex]) -> Complex { - Complex::new(f64::atan2(a[0].im, a[0].re), 0.0) - } - - pub fn add(a: &[Complex]) -> Complex { - let (a, b) = (a[0], a[1]); - Complex::new(a.re + b.re, a.im + b.im) - } - - pub fn sub(a: &[Complex]) -> Complex { - let (a, b) = (a[0], a[1]); - Complex::new(a.re - b.re, a.im - b.im) - } - - pub fn mul(a: &[Complex]) -> Complex { - let (a, b) = (a[0], a[1]); - Complex::new(a.re*b.re - a.im*b.im, a.im*b.re + a.re*b.im) - } - - pub fn div(a: &[Complex]) -> Complex { - let (a, b) = (a[0], a[1]); - let d = b.re*b.re + b.im*b.im; - Complex::new((a.re*b.re + a.im*b.im)/d, (a.im*b.re - a.re*b.im)/d) - } - - pub fn exp(a: &[Complex]) -> Complex { - let e = a[0].re.exp(); - Complex::new(e * a[0].im.cos(), e * a[0].im.sin()) - } - - pub fn log(a: &[Complex]) -> Complex { - let a = a[0]; - Complex::new(0.5 * (a.re*a.re + a.im*a.im).ln(), f64::atan2(a.im, a.re)) - } - - pub fn pow(a: &[Complex]) -> Complex { - exp(&[mul(&[log(&[a[0]]), a[1]])]) - } - - pub fn sqrt(a: &[Complex]) -> Complex { - pow(&[a[0],Complex::new(0.5, 0.0)]) - } - - pub fn sin(a: &[Complex]) -> Complex { - let a = a[0]; - Complex::new(a.re.sin()*a.im.cosh(), a.re.cos()*a.im.sinh()) - } - - pub fn cos(a: &[Complex]) -> Complex { - let a = a[0]; - Complex::new(a.re.cos()*a.im.cosh(), -a.re.sin()*a.re.sinh()) - } - - pub fn tan(a: &[Complex]) -> Complex { - let a = a[0]; - let d = (2.0*a.re).cos() + (2.0*a.im).cosh(); - Complex::new((2.0*a.re).sin() / d, (2.0*a.im).sinh() / d) - } - - pub fn sinh(a: &[Complex]) -> Complex { - let a = a[0]; - Complex::new(a.re.sinh()*a.im.cos(), a.re.cosh()*a.re.sin()) - } - - pub fn cosh(a: &[Complex]) -> Complex { - let a = a[0]; - Complex::new(a.re.cosh()*a.im.cos(), a.re.sinh()*a.re.sin()) - } - - pub fn tanh(a: &[Complex]) -> Complex { - let a = a[0]; - let d = (2.0*a.re).cosh() + (2.0*a.im).cos(); - Complex::new((2.0*a.re).sinh() / d, (2.0*a.im).sin() / d) - } -} diff --git a/src/language/compiler.rs b/src/language/compiler.rs deleted file mode 100644 index fc5e349..0000000 --- a/src/language/compiler.rs +++ /dev/null @@ -1,344 +0,0 @@ -use std::{collections::{HashMap, HashSet}, fmt::Write}; - -use crate::complex::{Complex, cxfn}; - -use super::parser::{Expr, Stmt, UnaryOp, BinaryOp, Defn}; - -#[derive(Clone, Debug)] -pub enum CompileError<'s> { - FmtError, - TypeError(&'s str), - UndefinedVar(&'s str), - Reassignment(&'s str), -} - -impl <'s> From for CompileError<'s> { - fn from(_: std::fmt::Error) -> Self { - Self::FmtError - } -} - -thread_local! { - pub static BUILTINS: HashMap<&'static str, (&'static str, Type, Option)> = { - let mut m: HashMap<&'static str, (&'static str, Type, Option)> = HashMap::new(); - m.insert("i", ("CONST_I", Type::Number, Some(|_| Complex::new(0.0, 1.0)))); - m.insert("e", ("CONST_E", Type::Number, Some(|_| Complex::new(std::f64::consts::E, 0.0)))); - m.insert("tau", ("CONST_TAU",Type::Number, Some(|_| Complex::new(std::f64::consts::TAU, 0.0)))); - m.insert("re", ("c_re", Type::Function(1), Some(cxfn::re))); - m.insert("im", ("c_im", Type::Function(1), Some(cxfn::im))); - m.insert("conj", ("c_conj", Type::Function(1), Some(cxfn::conj))); - m.insert("abs_sq", ("c_abs_sq", Type::Function(1), Some(cxfn::abs_sq))); - m.insert("abs", ("c_abs", Type::Function(1), Some(cxfn::abs))); - m.insert("arg", ("c_arg", Type::Function(1), Some(cxfn::arg))); - m.insert("pos", ("c_pos", Type::Function(1), Some(cxfn::pos))); - m.insert("neg", ("c_neg", Type::Function(1), Some(cxfn::neg))); - m.insert("recip", ("c_recip", Type::Function(1), Some(cxfn::recip))); - m.insert("add", ("c_add", Type::Function(2), Some(cxfn::add))); - m.insert("sub", ("c_sub", Type::Function(2), Some(cxfn::sub))); - m.insert("mul", ("c_mul", Type::Function(2), Some(cxfn::mul))); - m.insert("div", ("c_div", Type::Function(2), Some(cxfn::div))); - m.insert("recip", ("c_recip", Type::Function(1), Some(cxfn::recip))); - m.insert("exp", ("c_exp", Type::Function(1), Some(cxfn::exp))); - m.insert("log", ("c_log", Type::Function(1), Some(cxfn::log))); - m.insert("sqrt", ("c_sqrt", Type::Function(1), Some(cxfn::sqrt))); - m.insert("sin", ("c_sin", Type::Function(1), Some(cxfn::sin))); - m.insert("cos", ("c_cos", Type::Function(1), Some(cxfn::cos))); - m.insert("tan", ("c_tan", Type::Function(1), Some(cxfn::tan))); - m.insert("sinh", ("c_sinh", Type::Function(1), Some(cxfn::sinh))); - m.insert("cosh", ("c_cosh", Type::Function(1), Some(cxfn::cosh))); - m.insert("tanh", ("c_tanh", Type::Function(1), Some(cxfn::tanh))); - m.insert("gamma", ("c_gamma", Type::Function(1), None)); - m - }; -} - -#[derive(Clone, Copy)] -pub enum Type { - Number, - Function(u32), -} - -#[derive(Clone, Copy)] -enum NameScope { - Local, Global, Builtin -} - -struct NameInfo<'s> { - scope: NameScope, - name: &'s str, - ty: Type -} - -impl <'s> NameInfo<'s> { - pub fn get_cname(&self) -> String { - let name = self.name; - match (self.scope, self.ty) { - (NameScope::Local, _) => format!("arg_{name}"), - (NameScope::Global, Type::Number) => format!("VAR_{name}"), - (NameScope::Global, Type::Function(_)) => format!("func_{name}"), - (NameScope::Builtin, _) => name.to_owned(), - } - } -} - -type LocalTable<'s> = HashSet<&'s str>; - -type CompileResult<'s,T=()> = Result>; - -pub fn compile<'s>(buf: &mut impl Write, defns: &[Defn<'s>]) -> CompileResult<'s> { - let mut compiler = Compiler::new(buf); - for defn in defns { - compiler.compile_defn(defn)?; - } - Ok(()) -} - -struct Compiler<'s, 'w, W> where W: Write { - buf: &'w mut W, - globals: HashMap<&'s str, Type>, -} - -impl <'s, 'w, W: Write> Compiler<'s, 'w, W> { - fn new(buf: &'w mut W) -> Self { - Self { - buf, - globals: HashMap::new(), - } - } - - ////////////////// - // Statements // - ////////////////// - - fn compile_defn(&mut self, defn: &Defn<'s>) -> CompileResult<'s> { - let res = match defn { - Defn::Const { name, body } => self.defn_const(name, body), - Defn::Func { name, args, body } => self.defn_func(name, args, body), - }; - writeln!(self.buf)?; - res - } - - fn defn_const(&mut self, name: &'s str, body: &Expr<'s>) -> CompileResult<'s> { - if self.name_info(name, None).is_some() { - return Err(CompileError::Reassignment(name)) - } - - self.globals.insert(name, Type::Number); - write!(self.buf, "const VAR_{name} = ")?; - - let locals = LocalTable::with_capacity(0); - self.compile_expr(&locals, body)?; - - write!(self.buf, ";")?; - Ok(()) - } - - fn defn_func(&mut self, name: &'s str, args: &[&'s str], body: &(Vec>, Expr<'s>)) -> CompileResult<'s> { - if self.name_info(name, None).is_some() { - return Err(CompileError::Reassignment(name)) - } - - self.globals.insert(name, Type::Function(args.len() as u32)); - write!(self.buf, "fn func_{name}(")?; - - let mut locals = LocalTable::with_capacity(args.len()); - for arg in args { - write!(self.buf, "arg_{arg}:vec2f,")?; - locals.insert(arg); - } - write!(self.buf, ")->vec2f{{return ")?; - self.compile_expr(&locals, &body.1)?; - write!(self.buf, ";}}")?; - Ok(()) - } - - fn stmt_deriv(&mut self, name: &'s str, func: &'s str) -> CompileResult<'s> { - if self.name_info(name, None).is_some() { - return Err(CompileError::Reassignment(name)) - } - - let Some(name_info) = self.name_info(func, None) else { - return Err(CompileError::UndefinedVar(name)) - }; - let Type::Function(argc) = name_info.ty else { - return Err(CompileError::TypeError(name)) - }; - - let func_name = name_info.get_cname(); - - self.globals.insert(name, Type::Function(argc)); - - write!(self.buf, "fn func_{name}(")?; - - for i in 0..argc { - write!(self.buf, "arg_{i}:vec2f,")?; - } - let args: String = (1..argc).map(|i| format!(",arg_{i}")).collect(); - - write!(self.buf, ")->vec2f{{\ - let a = c_mul({func_name}(arg_0 + vec2( D_EPS, 0.0){args}), vec2( 0.25/D_EPS, 0.0));\ - let b = c_mul({func_name}(arg_0 + vec2(-D_EPS, 0.0){args}), vec2(-0.25/D_EPS, 0.0));\ - let c = c_mul({func_name}(arg_0 + vec2(0.0, D_EPS){args}), vec2(0.0, -0.25/D_EPS));\ - let d = c_mul({func_name}(arg_0 + vec2(0.0, -D_EPS){args}), vec2(0.0, 0.25/D_EPS));\ - return a + b + c + d;}}\ - ")?; - Ok(()) - } - - fn stmt_iter(&mut self, name: &'s str, func: &'s str, count: u32) -> CompileResult<'s> { - if self.name_info(name, None).is_some() { - return Err(CompileError::Reassignment(name)) - } - - let Some(name_info) = self.name_info(func, None) else { - return Err(CompileError::UndefinedVar(name)) - }; - let Type::Function(argc) = name_info.ty else { - return Err(CompileError::TypeError(name)) - }; - - let func_name = name_info.get_cname(); - - self.globals.insert(name, Type::Function(argc)); - - write!(self.buf, "fn func_{name}(")?; - - for i in 0..argc { - write!(self.buf, "arg_{i}:vec2f,")?; - } - let args: String = (1..argc).map(|i| format!(",arg_{i}")).collect(); - - write!(self.buf, ")->vec2f{{\ - var r=arg_0;\ - for(var i=0;i<{count};i++){{\ - r={func_name}(r{args});\ - }}\ - return r;}}\ - ")?; - Ok(()) - } - - /////////////////// - // Expressions // - /////////////////// - - fn compile_expr(&mut self, locals: &LocalTable<'s>, expr: &Expr<'s>) -> CompileResult<'s> { - match expr { - Expr::Number(z) => self.expr_number(*z), - Expr::Name(name) => self.expr_var(locals, name), - Expr::Unary(op, arg) => self.expr_unary(locals, *op, arg), - Expr::Binary(op, lhs, rhs) => self.expr_binary(locals, *op, lhs, rhs), - Expr::FnCall(name, args) => self.expr_fncall(locals, name, args), - } - } - - fn expr_number(&mut self, z: Complex) -> CompileResult<'s> { - write!(self.buf, "vec2f({:?},{:?})", z.re, z.im)?; - Ok(()) - } - - fn expr_unary(&mut self, locals: &LocalTable<'s>, op: UnaryOp, arg: &Expr<'s>) -> CompileResult<'s> { - let strings = unop_strings(op); - write!(self.buf, "{}", strings[0])?; - self.compile_expr(locals, arg)?; - write!(self.buf, "{}", strings[1])?; - Ok(()) - } - - fn expr_binary(&mut self, locals: &LocalTable<'s>, op: BinaryOp, lhs: &Expr<'s>, rhs: &Expr<'s>) -> CompileResult<'s> { - let strings = binop_strings(op); - write!(self.buf, "{}", strings[0])?; - self.compile_expr(locals, lhs)?; - write!(self.buf, "{}", strings[1])?; - self.compile_expr(locals, rhs)?; - write!(self.buf, "{}", strings[2])?; - Ok(()) - } - - fn expr_var(&mut self, locals: &LocalTable<'s>, name: &'s str) -> CompileResult<'s> { - let Some(name_info) = self.name_info(name, Some(locals)) else { - return Err(CompileError::UndefinedVar(name)) - }; - if !matches!(name_info.ty, Type::Number) { - return Err(CompileError::TypeError(name)) - } - write!(self.buf, "{}", name_info.get_cname())?; - Ok(()) - } - - fn expr_fncall(&mut self, locals: &LocalTable<'s>, name: &'s str, args: &Vec>) -> CompileResult<'s> { - let Some(name_info) = self.name_info(name, Some(locals)) else { - return Err(CompileError::UndefinedVar(name)) - }; - if !matches!(name_info.ty, Type::Function(n) if n as usize == args.len()) { - return Err(CompileError::TypeError(name)) - } - write!(self.buf, "{}", name_info.get_cname())?; - write!(self.buf, "(")?; - for arg in args { - self.compile_expr(locals, arg)?; - write!(self.buf, ",")?; - } - write!(self.buf, ")")?; - Ok(()) - } - - ///////////// - // Names // - ///////////// - - fn name_info(&self, name: &'s str, locals: Option<&LocalTable<'s>>) -> Option { - if let Some(locals) = locals { - if locals.contains(name) { - return Some(NameInfo { scope: NameScope::Local, name, ty: Type::Number }); - } - } - if let Some(ty) = self.globals.get(name).copied() { - return Some(NameInfo { scope: NameScope::Global, name, ty }) - } - if let Some((bname, ty, _)) = BUILTINS.with(|m| m.get(name).copied()) { - return Some(NameInfo { scope: NameScope::Builtin, name: bname, ty }) - } - None - } - - // fn generate_iter(&mut self, argc: u32) -> CompileResult<'s> { - // if !self.generate.contains_key(&format!("invoke{argc}")) { - // self.generate.insert(format!("invoke{argc}"), Generate::Iter { argc }); - // self.generate_invoke(argc)?; - // writeln!(self.buf)?; - // } - // write!(self.buf, "fn iter{argc}(func:vec2f,n:vec2f,")?; - // for i in 0..argc { - // write!(self.buf, "arg_{i}:vec2f")?; - // write!(self.buf, ",")?; - // } - // write!(self.buf, ")->vec2f{{var result=arg_0;")?; - // write!(self.buf, "for(var i=0;i [&'static str; 2] { - match op { - UnaryOp::Pos => ["+(", ")"], - UnaryOp::Neg => ["-(", ")"], - UnaryOp::Recip => ["c_recip(", ")"], - } -} - -const fn binop_strings(op: BinaryOp) -> [&'static str; 3] { - match op { - BinaryOp::Add => ["(", ")+(", ")"], - BinaryOp::Sub => ["(", ")-(", ")"], - BinaryOp::Mul => ["c_mul(", ",", ")"], - BinaryOp::Div => ["c_div(", ",", ")"], - BinaryOp::Pow => ["c_pow(", ",", ")"], - } -} diff --git a/src/language/mod.rs b/src/language/mod.rs deleted file mode 100644 index 781f8e1..0000000 --- a/src/language/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -mod scanner; -mod parser; -mod compiler; -mod optimizer; - -pub use parser::ParseError; -pub use compiler::CompileError; - -use self::optimizer::optimize; - -#[derive(Debug, Clone, Copy)] -pub struct Position { - pub pos: u32, - pub line: u32, - pub col: u32, -} - -#[derive(Clone, Debug)] -pub enum Error<'s> { - Parse(ParseError), - Compile(CompileError<'s>), -} - -impl <'s> From> for Error<'s> { - fn from(value: CompileError<'s>) -> Self { - Self::Compile(value) - } -} - -impl <'s> From for Error<'s> { - fn from(value: ParseError) -> Self { - Self::Parse(value) - } -} - -pub fn compile(src: &str) -> Result { - let mut buf = String::new(); - println!("parsing"); - let defns = parser::Parser::new(src).parse()?; - println!("optimizing"); - let defns = optimize(defns); - println!("compiling"); - compiler::compile(&mut buf, &defns)?; - Ok(buf) -} diff --git a/src/language/optimizer.rs b/src/language/optimizer.rs deleted file mode 100644 index 0c88cba..0000000 --- a/src/language/optimizer.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::complex::{Complex, cxfn}; - -use super::{parser::{Expr, UnaryOp, BinaryOp, Stmt, Defn}, compiler::{BUILTINS, Type}}; - -fn apply_unary(op: UnaryOp, arg: Complex) -> Complex { - match op { - UnaryOp::Pos => cxfn::pos(&[arg]), - UnaryOp::Neg => cxfn::neg(&[arg]), - UnaryOp::Recip => cxfn::recip(&[arg]), - } -} - -fn apply_binary(op: BinaryOp, u: Complex, v: Complex) -> Complex { - match op { - BinaryOp::Add => cxfn::add(&[u, v]), - BinaryOp::Sub => cxfn::sub(&[u, v]), - BinaryOp::Mul => cxfn::mul(&[u, v]), - BinaryOp::Div => cxfn::div(&[u, v]), - BinaryOp::Pow => cxfn::pow(&[u, v]), - } -} - -pub fn optimize(defns: Vec) -> Vec { - defns.into_iter().map(|s| match s { - Defn::Const { name, body } => Defn::Const { name, body: optimize_expr(body) }, - Defn::Func { name, args, body } => { - let stmts = body.0.into_iter() - .map(optimize_stmt).collect(); - let expr = optimize_expr(body.1); - Defn::Func { name, args, body: (stmts, expr) } - } - _ => s, - }).collect() -} - -fn optimize_stmt(stmt: Stmt) -> Stmt { - match stmt { - Stmt::Expr(e) => Stmt::Expr(optimize_expr(e)), - Stmt::Store(v, e) => Stmt::Store(v, optimize_expr(e)), - Stmt::Iter(v, min, max, stmts) => Stmt::Iter(v, min, max, - stmts.into_iter().map(optimize_stmt).collect() - ) - } -} - -fn optimize_expr<'s>(e: Expr<'s>) -> Expr<'s> { - match e { - Expr::Unary(op, arg) => { - match optimize_expr(*arg) { - Expr::Number(z) => Expr::Number(apply_unary(op, z)), - e => Expr::Unary(op, Box::new(e)), - } - }, - Expr::Binary(op, lhs, rhs) => { - match (optimize_expr(*lhs), optimize_expr(*rhs)) { - (Expr::Number(u), Expr::Number(v)) => Expr::Number(apply_binary(op, u, v)), - (u, v) => Expr::Binary(op, Box::new(u), Box::new(v)) - } - }, - Expr::FnCall(name, args) => { - let args: Vec> = args.into_iter().map(optimize_expr).collect(); - if let Some((_, Type::Function(argc), Some(func))) = BUILTINS.with(|m| m.get(name).copied()) { - if argc as usize == args.len() - && args.iter().all(|e| matches!(e, Expr::Number(_))) { - let ns: Vec = args.into_iter().map(|a| match a { Expr::Number(n) => n, _ => unreachable!() }).collect(); - return Expr::Number(func(&ns)) - } - } - Expr::FnCall(name, args) - } - Expr::Name(name) => { - if let Some((_, Type::Number, Some(func))) = BUILTINS.with(|m| m.get(name).copied()) { - Expr::Number(func(&[])) - } else { - e - } - } - Expr::Number(_) => e, - } -} diff --git a/src/language/parser.rs b/src/language/parser.rs deleted file mode 100644 index 4594f7e..0000000 --- a/src/language/parser.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::iter::Peekable; - -use crate::complex::Complex; - -use super::{scanner::{Scanner, Token}, Position}; - -#[derive(Clone, Debug)] -pub struct ParseError { - msg: String, - pos: Option, -} - -#[derive(Debug, Clone, Copy)] -pub enum BinaryOp { - Add, Sub, Mul, Div, Pow, -} - -impl BinaryOp { - pub const fn from_token(tok: Token) -> Option { - match tok { - Token::Plus => Some(Self::Add), - Token::Minus => Some(Self::Sub), - Token::Star => Some(Self::Mul), - Token::Slash => Some(Self::Div), - Token::Caret => Some(Self::Pow), - _ => None, - } - } - - pub const fn precedence(self) -> (u32, u32) { - match self { - BinaryOp::Add - | BinaryOp::Sub => (10, 11), - BinaryOp::Mul - | BinaryOp::Div => (20, 21), - BinaryOp::Pow => (31, 30), - } - } - -} - -#[derive(Debug, Clone, Copy)] -pub enum UnaryOp { - Pos, Neg, Recip, -} - -impl UnaryOp { - pub const fn from_token(tok: Token) -> Option { - match tok { - Token::Plus => Some(Self::Pos), - Token::Minus => Some(Self::Neg), - Token::Slash => Some(Self::Recip), - _ => None, - } - } - - pub const fn precedence(self) -> u32 { - 40 - } -} - -#[derive(Clone, Debug)] -pub enum Expr<'s> { - Number(Complex), - Name(&'s str), - Unary(UnaryOp, Box>), - Binary(BinaryOp, Box>, Box>), - FnCall(&'s str, Vec>), -} - -#[derive(Debug)] -pub enum Stmt<'s> { - Expr(Expr<'s>), - Store(&'s str, Expr<'s>), - Iter(&'s str, u32, u32, Vec>), -} - -pub enum Defn<'s> { - Const { name: &'s str, body: Expr<'s> }, - Func { name: &'s str, args: Vec<&'s str>, body: (Vec>, Expr<'s>) }, -} - -pub struct Parser<'s> { - scanner: Peekable>, -} - -impl <'s> Parser<'s> { - pub fn new(src: &'s str) -> Self { - Self { - scanner: Scanner::new(src).peekable() - } - } - - fn next(&mut self) -> Result<(Position, Token<'s>), ParseError> { - match self.scanner.next() { - Some(r) => Ok(r), - None => Err(self.err_here("Unexpected EOF")), - } - } - - fn peek(&mut self) -> Result<(Position, Token<'s>), ParseError> { - match self.scanner.peek() { - Some(r) => Ok(*r), - None => Err(self.err_here("Unexpected EOF")), - } - } - - fn at_end(&mut self) -> bool { - self.scanner.peek().is_none() - } - - fn expect(&mut self, tok: Token<'s>) -> Result { - match self.peek()? { - (p, t) if t == tok => { - self.next()?; - Ok(p) - }, - (p, t) => Err(self.err_at(format!("Unexpected token {t:?}, expected {tok:?}"), p)), - } - } - - fn err_here(&mut self, msg: S) -> ParseError - where S: Into { - ParseError { msg: msg.into(), pos: self.peek().map(|(p,_)| p).ok() } - } - - fn err_at(&mut self, msg: S, pos: Position) -> ParseError - where S: Into { - ParseError { msg: msg.into(), pos: Some(pos) } - } - - fn expr(&mut self, min_prec: u32) -> Result, ParseError> { - let (pos, tok) = self.next()?; - let mut expr = match tok { - Token::Number(n) => Expr::Number(Complex::from(n)), - Token::Name(n) => Expr::Name(n), - Token::LParen => { - let e = self.expr(0)?; - self.expect(Token::RParen)?; - e - } - tok => if let Some(op) = UnaryOp::from_token(tok) { - Expr::Unary(op, Box::new(self.expr(op.precedence())?)) - } else { - return Err(self.err_at(format!("Unexpected token {:?}", tok), pos)) - } - }; - - while let Ok((_, tok)) = self.peek() { - if is_closing(&tok) { - break; - } - expr = match tok { - Token::LParen => { - self.next()?; - let mut args = Vec::new(); - while !matches!(self.peek(), Err(_) | Ok((_, Token::RParen))) { - args.push(self.expr(0)?); - match self.peek()?.1 { - Token::Comma => { self.next()?; }, - Token::RParen => break, - _ => return Err(self.err_here(format!("Unexpected token {:?}, expected ',' or ')'", tok))) - } - } - self.expect(Token::RParen)?; - match expr { - Expr::Name(name) => Expr::FnCall(name, args), - _ => return Err(self.err_here("Cannot call this expression")) - } - }, - tok => if let Some(op) = BinaryOp::from_token(tok) { - let (lp, rp) = op.precedence(); - if lp < min_prec { - break; - } - self.next()?; - let rhs = self.expr(rp)?; - Expr::Binary(op, Box::new(expr), Box::new(rhs)) - } else { - let (lp, rp) = BinaryOp::Mul.precedence(); - if lp < min_prec { - break; - } - let rhs = self.expr(rp)?; - Expr::Binary(BinaryOp::Mul, Box::new(expr), Box::new(rhs)) - } - } - } - - Ok(expr) - } - - fn stmt(&mut self) -> Result, ParseError> { - let expr = self.expr(0)?; - match self.peek()?.1 { - Token::Arrow => { - self.next()?; - let name = match self.next()? { - (_, Token::Name(name)) => name, - (p, t) => return Err(self.err_at(format!("Unexpected token {t:?}, expected a name"), p)) - }; - Ok(Stmt::Store(name, expr)) - } - _ => Ok(Stmt::Expr(expr)) - } - } - - fn stmts(&mut self) -> Result>, ParseError> { - let mut stmts = Vec::new(); - loop { - stmts.push(self.stmt()?); - if !matches!(self.peek(), Ok((_, Token::Comma))) { - break - } - self.next()?; - } - Ok(stmts) - } - - pub fn parse(&mut self) -> Result>, ParseError> { - println!("parse"); - let mut defns = Vec::new(); - while self.peek().is_ok() { - println!("parse loop"); - while matches!(self.peek(), Ok((_, Token::Newline))) { - self.next()?; - } - - if self.peek().is_err() { - break; - } - - let lhspos = self.peek()?.0; - let lhs = self.expr(0)?; - - self.expect(Token::Equal)?; - - let defn = match lhs { - Expr::Name(name) => { - let rhs = self.expr(0)?; - Defn::Const { name, body: rhs } - }, - Expr::FnCall(name, args) => { - let mut rhs = self.stmts()?; - let last = rhs.pop().ok_or(self.err_here("Empty function body"))?; - let Stmt::Expr(last) = last else { - return Err(self.err_here("Last statement in function body must be a plain expression")) - }; - let args = args.iter() - .map(|a| match a { - Expr::Name(n) => Ok(*n), - _ => Err(self.err_at("Invalid function declaration", lhspos)) - }).collect::, ParseError>>()?; - Defn::Func { name, args, body: (rhs, last) } - }, - _ => return Err(self.err_at("Invalid lvalue, expected a name or function call", lhspos)), - }; - - defns.push(defn); - - if self.at_end() { - break - } - self.expect(Token::Newline)?; - } - Ok(defns) - } -} - -fn is_closing(tok: &Token) -> bool { - matches!(tok, Token::Equal | Token::RParen | Token::Newline | Token::Comma | Token::Arrow) -} diff --git a/src/language/scanner.rs b/src/language/scanner.rs deleted file mode 100644 index 819907e..0000000 --- a/src/language/scanner.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::{iter::Peekable, str::Chars}; - -use super::Position; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Token<'s> { - Error, - Name(&'s str), - Number(f64), - Plus, - Minus, - Star, - Slash, - Caret, - Equal, - Comma, - Arrow, - LParen, - RParen, - Newline, -} - -pub struct Scanner<'s> { - pub src: &'s str, - pub chars: Peekable>, - pub pos: Position, -} - -impl <'s> Scanner<'s> { - pub fn new(src: &'s str) -> Self { - Self { - src, - chars: src.chars().peekable(), - pos: Position { pos: 0, line: 1, col: 1 } - } - } - - fn next(&mut self) -> Option { - match self.chars.next() { - Some('\n') => { - self.pos.pos += 1; - self.pos.line += 1; - self.pos.col = 1; - Some('\n') - }, - Some(c) => { - self.pos.pos += 1; - self.pos.col += 1; - Some(c) - }, - None => None, - } - } - - fn peek(&mut self) -> Option { - self.chars.peek().copied() - } - - fn next_number(&mut self, pos: u32) -> Token<'s> { - while matches!(self.peek(), Some('0'..='9')) { - self.next(); - } - if matches!(self.peek(), Some('.')) { - self.next(); - while matches!(self.peek(), Some('0'..='9')) { - self.next(); - } - } - let s = &self.src[pos as usize..self.pos.pos as usize]; - match s.parse() { - Ok(x) => Token::Number(x), - Err(_) => Token::Error, - } - } - - fn next_name(&mut self, pos: u32) -> Token<'s> { - while matches!(self.peek(), Some('a'..='z' | 'A'..='Z' | '0'..='9' | '_')) { - self.next(); - } - let s = &self.src[pos as usize..self.pos.pos as usize]; - Token::Name(s) - } - - pub fn next_token(&mut self) -> Option<(Position, Token<'s>)> { - while matches!(self.peek(), Some(' ' | '\t')) { - self.next(); - } - self.peek()?; - let pos = self.pos; - let tok = match self.next().unwrap() { - '\n' => Token::Newline, - '+' => Token::Plus, - '-' => match self.peek().unwrap() { - '>' => { self.next(); Token::Arrow }, - _ => Token::Minus, - } - '*' => Token::Star, - '/' => Token::Slash, - '^' => Token::Caret, - '=' => Token::Equal, - ',' => Token::Comma, - '(' => Token::LParen, - ')' => Token::RParen, - '0'..='9' => self.next_number(pos.pos), - 'a'..='z' | 'A'..='Z' => self.next_name(pos.pos), - _ => Token::Error, - }; - Some((pos, tok)) - } -} - -impl <'s> Iterator for Scanner<'s> { - type Item = (Position, Token<'s>); - - fn next(&mut self) -> Option { - self.next_token() - } -} diff --git a/style.css b/style.css deleted file mode 100644 index 42953c9..0000000 --- a/style.css +++ /dev/null @@ -1,49 +0,0 @@ -* { - margin: 0; - padding: 0; -} - -body { - min-height: 100vh; - max-height: 100vh; - overflow: hidden; - --header-height: 50px; - --sidebar-width: 500px; -} - -#main { - min-height: 100vh; - max-height: 100vh; -} - -#header { - height: var(--header-height); -} - -#content { - width: 100vw; - height: calc(100vh - var(--header-height)); - display: flex; -} - -#sidebar { - width: var(--sidebar-width); - height: inherit; -} - -#canvas_container { - width: calc(100vw - var(--sidebar-width)); - height: inherit; -} - -#sidebar textarea { - width: calc(100% - 24px); - margin-left: 10px; - height: 99%; - resize: none; - font-size: 16px; -} - -canvas { - background-color: black; -}