split into crates, made changes

This commit is contained in:
TriMill 2023-06-07 18:58:20 -04:00
parent 3e9f05bbfd
commit b732e1ff3d
30 changed files with 1490 additions and 1285 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target
/pkg
cxgraph-web/pkg
/.vscode

431
Cargo.lock generated
View file

@ -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"

View file

@ -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"]}

View file

@ -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"

View file

@ -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();

17
cxgraph-web/Cargo.toml Normal file
View file

@ -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"]}

25
cxgraph-web/index.html Normal file
View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>cxgraph</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="canvas-container">
<canvas id="canvas"></canvas>
<canvas id="grid_canvas"></canvas>
</div>
<div class="menu">
<input id="menu_checkbox" type="checkbox" checked>
<div class="menu-inner" id="menu_inner">
<div><input type="button" id="button_graph" value="Graph"></div>
<textarea id="source_text">f(z) = z^2 + 3i&#10;plot(z) = 5z^2 + f(1/z) - 1</textarea>
</div>
</div>
<script>
import('./index.js').then(m => window.module = m)
</script>
</body>
</html>

89
cxgraph-web/index.js Normal file
View file

@ -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

View file

@ -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]

46
cxgraph-web/style.css Normal file
View file

@ -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;
}

View file

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cxgraph</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="main">
<div id="header">
<h1>CXGraph</h1>
</div>
<div id="content">
<div id="sidebar" class="split left">
<textarea id="srcinput"></textarea>
</div>
<div id="canvas_container" class="split right">
<!-- canvas -->
</div>
</div>
</div>
<script type="module">
import init, * as cxgraph from "./pkg/cxgraph.js";
await init();
canvas.style.width = "100%";
canvas.style.height = "100%";
await cxgraph.load_shader("f(z) = z^8 + 15z^4 - 16\nfp(z) = 8z^7 + 60z^3\nn(z) = z - f(z)/fp(z)\nni: iter n, 60\nplot(z) = f(z)");
await cxgraph.redraw();
let canvas = document.getElementsByTagName("canvas")[0];
new ResizeObserver(async () => {
let width = canvas.clientWidth;
let height = canvas.clientHeight;
await cxgraph.resize(width*2, height*2);
await cxgraph.set_bounds(-5*width/height, -5, 5*width/height, 5);
await cxgraph.redraw();
}).observe(canvas);
</script>
</body>
</html>

20
libcxgraph/Cargo.toml Normal file
View file

@ -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"

3
libcxgraph/build.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
lalrpop::process_root().unwrap();
}

View file

@ -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<Expression<'a>>,
}
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 {
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 {
Self {
ty: ExpressionType::Sum { countvar, min, max },
children: body,
}
}
pub fn new_prod(accvar: &'a str, min: i32, max: i32, body: Vec<Self>) -> 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>) -> Self {
body.push(init);
Self {
ty: ExpressionType::Iter { itervar, count },
children: body,
}
}
}
pub enum Definition<'a> {
Constant { name: &'a str, value: Vec<Expression<'a>> },
Function { name: &'a str, args: Vec<&'a str>, value: Vec<Expression<'a>> },
}
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(())
}

View file

@ -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(<Complex as ops::Add>::add)));
m.insert("sub", ("c_sub", Func::Two(<Complex as ops::Sub>::sub)));
m.insert("mul", ("c_mul", Func::Two(<Complex as ops::Mul>::mul)));
m.insert("div", ("c_div", Func::Two(<Complex as ops::Div>::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
}
}

View file

@ -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<String> for CompileError {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<fmt::Error> 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<String, CompileError> {
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<String, CompileError> {
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())
}
}
}

View file

@ -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<String, Box<dyn std::error::Error>> {
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)
}

View file

@ -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(<f64>),
Int => Token::Int(<i32>),
Name => Token::Name(<&'input str>),
}
}
// Definitions
pub Program: Vec<Definition<'input>> = Definitions;
Definitions: Vec<Definition<'input>> = {
"\n"* <defs:(<Definition> "\n"+)*> <last:Definition?> => defs.into_iter().chain(last).collect(),
}
Definition: Definition<'input> = {
<n:Name> "(" <args:(<Name> ",")*> <last:Name?> ")" "=" <exs:Exprs> => Definition::Function {
name: n,
args: args.into_iter().chain(last).collect(),
value: exs,
},
<n:Name> "=" <exs:Exprs> => Definition::Constant {
name: n,
value: exs,
},
}
// Expressions
Exprs: Vec<Expression<'input>> = {
<args:(<Expr> ",")*> <last:Expr?> => args.into_iter().chain(last).collect(),
}
Expr: Expression<'input> = Store;
Store: Expression<'input> = {
<a:Store> "->" <n:Name> => Expression::new_store(a, n),
Sum,
}
Sum: Expression<'input> = {
<a:Sum> "+" <b:Product> => Expression::new_binary(BinaryOp::Add, a, b),
<a:Sum> "-" <b:Product> => Expression::new_binary(BinaryOp::Sub, a, b),
Product,
}
Product: Expression<'input> = {
<a:Product> "*" <b:Unary> => Expression::new_binary(BinaryOp::Mul, a, b),
<a:Product> "/" <b:Unary> => Expression::new_binary(BinaryOp::Div, a, b),
Unary,
}
Unary: Expression<'input> = {
"+" <a:Unary> => Expression::new_unary(UnaryOp::Pos, a),
"-" <a:Unary> => Expression::new_unary(UnaryOp::Neg, a),
"*" <a:Unary> => Expression::new_unary(UnaryOp::Conj, a),
<a:Juxtapose> <b:Power> => Expression::new_binary(BinaryOp::Mul, a, b),
Power,
}
Juxtapose: Expression<'input> = {
<a:Juxtapose> <b:PreJuxtapose> => Expression::new_binary(BinaryOp::Mul, a, b),
PreJuxtapose,
}
Power: Expression<'input> = {
<a:FnCall> "^" <b:Unary> => Expression::new_binary(BinaryOp::Pow, a, b),
FnCall,
}
FnCall: Expression<'input> = {
<n:Name> "(" <args:Exprs> ")"
=> Expression::new_fncall(n, args),
<Item>
}
PreJuxtapose: Expression<'input> = {
Number,
"(" <Expr> ")",
}
Item: Expression<'input> = {
Number,
<n:Name> => Expression::new_name(n),
"(" <Expr> ")",
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
=> Expression::new_sum(name, min, max, exs),
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
=> Expression::new_prod(name, min, max, exs),
"iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" "{" <exs:Exprs> "}"
=> Expression::new_iter(name, count, init, exs),
}
Number: Expression<'input> = {
<n:Float> => Expression::new_number(n),
<n:Int> => Expression::new_number(n as f64),
}

View file

@ -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<T, L, E> = Result<(L, T, L), E>;
pub struct Lexer<'i> {
src: &'i str,
chars: Peekable<CharIndices<'i>>,
}
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<Token<'i>, 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::<i32>() {
return Ok((i, Token::Int(n), j+1))
}
}
match s.parse::<f64>() {
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<Token<'i>, 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<Spanned<Token<'i>, 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<Token<'i>, usize, LexerError>;
fn next(&mut self) -> Option<Self::Item> {
self.next_token()
}
}

View file

@ -1,4 +1,2 @@
pub mod language;
pub mod renderer;
pub mod complex;
pub mod wasm;

View file

@ -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<vec2f, 4>(
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);

View file

@ -1,6 +1,7 @@
use std::num::NonZeroU64;
use encase::{ShaderType, ShaderSize, UniformBuffer};
use log::warn;
use wgpu::util::DeviceExt;
type Vec2u = cgmath::Vector2<u32>;
@ -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 {

View file

@ -0,0 +1,11 @@
@vertex
fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f {
var pos = array<vec2f, 4>(
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);
}

View file

@ -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<f64> 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)
}
}

View file

@ -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<std::fmt::Error> for CompileError<'s> {
fn from(_: std::fmt::Error) -> Self {
Self::FmtError
}
}
thread_local! {
pub static BUILTINS: HashMap<&'static str, (&'static str, Type, Option<cxfn::Function>)> = {
let mut m: HashMap<&'static str, (&'static str, Type, Option<cxfn::Function>)> = 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<T, CompileError<'s>>;
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<Stmt<'s>>, 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<Expr<'s>>) -> 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<NameInfo> {
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<i32(n.x);i++){{result=invoke{argc}(func,result,")?;
// for i in 1..argc {
// write!(self.buf, "arg_{i},")?;
// }
// write!(self.buf, ");}}return result;}}")?;
// Ok(())
// }
}
const fn unop_strings(op: UnaryOp) -> [&'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(", ",", ")"],
}
}

View file

@ -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<CompileError<'s>> for Error<'s> {
fn from(value: CompileError<'s>) -> Self {
Self::Compile(value)
}
}
impl <'s> From<ParseError> for Error<'s> {
fn from(value: ParseError) -> Self {
Self::Parse(value)
}
}
pub fn compile(src: &str) -> Result<String, Error> {
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)
}

View file

@ -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<Defn>) -> Vec<Defn> {
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<Expr<'s>> = 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<Complex> = 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,
}
}

View file

@ -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<Position>,
}
#[derive(Debug, Clone, Copy)]
pub enum BinaryOp {
Add, Sub, Mul, Div, Pow,
}
impl BinaryOp {
pub const fn from_token(tok: Token) -> Option<Self> {
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<Self> {
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<Expr<'s>>),
Binary(BinaryOp, Box<Expr<'s>>, Box<Expr<'s>>),
FnCall(&'s str, Vec<Expr<'s>>),
}
#[derive(Debug)]
pub enum Stmt<'s> {
Expr(Expr<'s>),
Store(&'s str, Expr<'s>),
Iter(&'s str, u32, u32, Vec<Stmt<'s>>),
}
pub enum Defn<'s> {
Const { name: &'s str, body: Expr<'s> },
Func { name: &'s str, args: Vec<&'s str>, body: (Vec<Stmt<'s>>, Expr<'s>) },
}
pub struct Parser<'s> {
scanner: Peekable<Scanner<'s>>,
}
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<Position, ParseError> {
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<S>(&mut self, msg: S) -> ParseError
where S: Into<String> {
ParseError { msg: msg.into(), pos: self.peek().map(|(p,_)| p).ok() }
}
fn err_at<S>(&mut self, msg: S, pos: Position) -> ParseError
where S: Into<String> {
ParseError { msg: msg.into(), pos: Some(pos) }
}
fn expr(&mut self, min_prec: u32) -> Result<Expr<'s>, 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<Stmt<'s>, 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<Vec<Stmt<'s>>, 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<Vec<Defn<'s>>, 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::<Result<Vec<&str>, 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)
}

View file

@ -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<Chars<'s>>,
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<char> {
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<char> {
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::Item> {
self.next_token()
}
}

View file

@ -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;
}