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