From 7c9faffc992feba40769b2b4b238be8455b2b7d7 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Mon, 27 Jan 2025 22:01:30 +1300 Subject: [PATCH 1/4] cargo update, etc Signed-off-by: Nick Cameron --- src/wasm-lib/.cargo/config.toml | 2 + src/wasm-lib/Cargo.lock | 748 ++++++++---------- src/wasm-lib/Cargo.toml | 3 +- .../kcl/src/parsing/token/tokeniser.rs | 6 +- 4 files changed, 355 insertions(+), 404 deletions(-) create mode 100644 src/wasm-lib/.cargo/config.toml diff --git a/src/wasm-lib/.cargo/config.toml b/src/wasm-lib/.cargo/config.toml new file mode 100644 index 0000000000..f4e8c002fc --- /dev/null +++ b/src/wasm-lib/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index 0baa2689b6..828ff42bb9 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -72,9 +72,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -87,36 +87,37 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -145,9 +146,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] @@ -165,7 +166,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -198,9 +199,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auto_impl" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", @@ -225,7 +226,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -251,9 +252,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bigdecimal" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" dependencies = [ "autocfg", "libm", @@ -280,9 +281,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bitvec" @@ -316,7 +317,7 @@ dependencies = [ "bitvec", "chrono", "hex", - "indexmap 2.7.0", + "indexmap 2.7.1", "js-sys", "once_cell", "rand 0.8.5", @@ -341,9 +342,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -359,9 +360,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -374,9 +375,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.24" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] @@ -387,6 +388,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cgmath" version = "0.16.1" @@ -411,7 +418,7 @@ dependencies = [ "num-traits 0.2.19", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -485,31 +492,31 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.14", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] @@ -533,9 +540,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", @@ -549,9 +556,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -605,9 +612,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -624,15 +631,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -742,9 +749,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", @@ -839,9 +846,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "enum-iterator" @@ -871,12 +878,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -902,24 +909,24 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1079,9 +1086,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git_rev" @@ -1091,9 +1098,9 @@ checksum = "60884563ea313b5037683cd5d44f1e14e9cb07b08543756242a65887f9cff48e" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gloo-utils" @@ -1134,9 +1141,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1144,7 +1151,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.2.0", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -1174,7 +1181,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.0", + "thiserror 2.0.11", ] [[package]] @@ -1207,12 +1214,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.4.0" @@ -1227,11 +1228,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1304,9 +1305,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -1327,9 +1328,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", @@ -1347,13 +1348,13 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http 1.2.0", - "hyper 1.4.1", + "hyper 1.5.2", "hyper-util", "rustls", "rustls-pki-types", @@ -1365,16 +1366,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.2", "pin-project-lite", "socket2", "tokio", @@ -1571,7 +1572,7 @@ dependencies = [ "image", "itertools 0.12.1", "rayon", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -1587,9 +1588,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1610,15 +1611,16 @@ checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" [[package]] name = "insta" -version = "1.41.1" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" dependencies = [ "console", - "lazy_static", "linked-hash-map", + "once_cell", "pest", "pest_derive", + "pin-project", "regex", "serde", "similar", @@ -1638,19 +1640,19 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1694,15 +1696,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -1734,7 +1736,7 @@ dependencies = [ "handlebars", "http 1.2.0", "image", - "indexmap 2.7.0", + "indexmap 2.7.1", "insta", "itertools 0.13.0", "js-sys", @@ -1756,7 +1758,7 @@ dependencies = [ "serde_json", "sha2", "tabled", - "thiserror 2.0.0", + "thiserror 2.0.11", "tokio", "tokio-tungstenite", "toml", @@ -1780,7 +1782,7 @@ name = "kcl-test-server" version = "0.1.20" dependencies = [ "anyhow", - "hyper 0.14.30", + "hyper 0.14.32", "kcl-lib", "pico-args", "serde", @@ -1794,7 +1796,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "indexmap 2.7.0", + "indexmap 2.7.1", "kcl-lib", "kittycad", "kittycad-modeling-cmds", @@ -1835,7 +1837,7 @@ dependencies = [ "serde_bytes", "serde_json", "serde_urlencoded", - "thiserror 2.0.0", + "thiserror 2.0.11", "tokio", "tracing", "url", @@ -1911,15 +1913,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linked-hash-map" @@ -1929,15 +1931,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -1951,9 +1953,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" dependencies = [ "serde", ] @@ -1982,9 +1984,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9" [[package]] name = "measurements" @@ -2012,9 +2014,9 @@ dependencies = [ [[package]] name = "miette" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" dependencies = [ "backtrace", "backtrace-ext", @@ -2026,15 +2028,15 @@ dependencies = [ "supports-unicode", "terminal_size", "textwrap", - "thiserror 1.0.68", + "thiserror 1.0.69", "unicode-width 0.1.14", ] [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ "proc-macro2", "quote", @@ -2065,9 +2067,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", "simd-adler32", @@ -2087,11 +2089,10 @@ checksum = "9bec4598fddb13cc7b528819e697852653252b760f1228b7642679bf2ff2cd07" [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -2176,9 +2177,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -2203,9 +2204,9 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "opentelemetry" @@ -2218,7 +2219,7 @@ dependencies = [ "js-sys", "once_cell", "pin-project-lite", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -2236,7 +2237,7 @@ dependencies = [ "opentelemetry", "percent-encoding", "rand 0.8.5", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -2299,9 +2300,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2362,20 +2363,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.68", + "thiserror 2.0.11", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -2383,9 +2384,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", @@ -2396,9 +2397,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.13" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -2407,23 +2408,22 @@ dependencies = [ [[package]] name = "phonenumber" -version = "0.3.6+8.13.36" +version = "0.3.7+8.13.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11756237b57b8cc5e97dc8b1e70ea436324d30e7075de63b14fd15073a8f692a" +checksum = "2247167dc3741816fdd4d3690e97f56a892a264b44f4c702078b72d1f8b6bd40" dependencies = [ "bincode", "either", "fnv", - "itertools 0.12.1", - "lazy_static", "nom", + "once_cell", "quick-xml", "regex", "regex-cache", "serde", "serde_derive", "strum", - "thiserror 1.0.68", + "thiserror 1.0.69", ] [[package]] @@ -2434,18 +2434,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", @@ -2454,9 +2454,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2494,9 +2494,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -2507,9 +2507,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" @@ -2584,9 +2584,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -2656,54 +2656,58 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "rustls", "socket2", - "thiserror 1.0.68", + "thiserror 2.0.11", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom", "rand 0.8.5", "ring", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "rustls", + "rustls-pki-types", "slab", - "thiserror 1.0.68", + "thiserror 2.0.11", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ + "cfg_aliases", "libc", "once_cell", "socket2", @@ -2824,11 +2828,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -2845,9 +2849,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2880,9 +2884,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64 0.22.1", "bytes", @@ -2892,7 +2896,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.2", "hyper-rustls", "hyper-util", "ipnet", @@ -2914,6 +2918,7 @@ dependencies = [ "tokio", "tokio-rustls", "tokio-util", + "tower 0.5.2", "tower-service", "url", "wasm-bindgen", @@ -2947,7 +2952,7 @@ dependencies = [ "http 1.2.0", "reqwest", "serde", - "thiserror 1.0.68", + "thiserror 1.0.69", "tower-service", ] @@ -2962,12 +2967,12 @@ dependencies = [ "futures", "getrandom", "http 1.2.0", - "hyper 1.4.1", + "hyper 1.5.2", "parking_lot 0.11.2", "reqwest", "reqwest-middleware", "retry-policies", - "thiserror 1.0.68", + "thiserror 1.0.69", "tokio", "tracing", "wasm-timer", @@ -2975,9 +2980,9 @@ dependencies = [ [[package]] name = "reqwest-tracing" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff82cf5730a1311fb9413b0bc2b8e743e0157cd73f010ab4ec374a923873b6a2" +checksum = "73e6153390585f6961341b50e5a1931d6be6dee4292283635903c26ef9d980d2" dependencies = [ "anyhow", "async-trait", @@ -3045,9 +3050,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustfmt-wrapper" @@ -3057,29 +3062,29 @@ checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440" dependencies = [ "serde", "tempfile", - "thiserror 1.0.68", + "thiserror 1.0.69", "toml", "toolchain_find", ] [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ "once_cell", "ring", @@ -3091,12 +3096,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", @@ -3113,9 +3117,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -3130,9 +3137,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -3151,9 +3158,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -3169,7 +3176,7 @@ dependencies = [ "chrono", "dyn-clone", "indexmap 1.9.3", - "indexmap 2.7.0", + "indexmap 2.7.1", "schemars_derive", "serde", "serde_json", @@ -3197,11 +3204,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation", "core-foundation-sys", "libc", @@ -3210,9 +3217,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -3220,9 +3227,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" @@ -3270,7 +3277,7 @@ version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "itoa", "memchr", "ryu", @@ -3387,9 +3394,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" @@ -3406,17 +3413,11 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "smawk" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" - [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3436,9 +3437,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "str_indices" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" [[package]] name = "strsim" @@ -3499,18 +3500,18 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-color" -version = "3.0.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" dependencies = [ "is_ci", ] [[package]] name = "supports-hyperlinks" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" [[package]] name = "supports-unicode" @@ -3542,9 +3543,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -3598,12 +3599,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -3620,12 +3622,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3634,34 +3636,33 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ - "smawk", "unicode-linebreak", "unicode-width 0.1.14", ] [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.68", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.0" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.0", + "thiserror-impl 2.0.11", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -3670,9 +3671,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.0" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -3697,9 +3698,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -3718,9 +3719,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -3748,9 +3749,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -3763,9 +3764,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -3782,9 +3783,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -3793,12 +3794,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -3820,9 +3820,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -3858,7 +3858,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -3892,6 +3892,21 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -3917,7 +3932,7 @@ dependencies = [ "serde_json", "tokio", "tokio-util", - "tower", + "tower 0.4.13", "tower-lsp-macros", "tracing", ] @@ -3941,9 +3956,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -3952,9 +3967,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -3963,9 +3978,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -4002,9 +4017,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "sharded-slab", "thread_local", @@ -4024,10 +4039,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6" dependencies = [ "chrono", - "indexmap 2.7.0", + "indexmap 2.7.1", "lazy_static", "serde_json", - "thiserror 2.0.0", + "thiserror 2.0.11", "ts-rs-macros", "url", "uuid", @@ -4061,7 +4076,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 1.0.68", + "thiserror 1.0.69", "utf-8", ] @@ -4091,18 +4106,15 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" [[package]] name = "unicode-linebreak" @@ -4225,9 +4237,9 @@ dependencies = [ [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" @@ -4262,20 +4274,21 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -4287,9 +4300,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "futures-core", @@ -4301,9 +4314,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4311,9 +4324,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -4324,9 +4337,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-lib" @@ -4388,9 +4404,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -4408,9 +4424,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -4452,7 +4468,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4463,7 +4479,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4472,7 +4488,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4482,16 +4498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -4500,7 +4507,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4509,22 +4516,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -4533,46 +4525,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4585,48 +4559,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4635,9 +4585,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.22" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] @@ -4680,9 +4630,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -4692,9 +4642,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", @@ -4725,18 +4675,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", @@ -4782,7 +4732,7 @@ dependencies = [ "crc32fast", "crossbeam-utils", "displaydoc", - "indexmap 2.7.0", + "indexmap 2.7.1", "memchr", - "thiserror 2.0.0", + "thiserror 2.0.11", ] diff --git a/src/wasm-lib/Cargo.toml b/src/wasm-lib/Cargo.toml index 46822ecb40..199fc76d83 100644 --- a/src/wasm-lib/Cargo.toml +++ b/src/wasm-lib/Cargo.toml @@ -3,9 +3,8 @@ name = "wasm-lib" version = "0.1.0" edition = "2021" repository = "https://github.com/KittyCAD/modeling-app" -rust-version = "1.73" +rust-version = "1.83" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib"] diff --git a/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs b/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs index bd7625f15d..660e22330f 100644 --- a/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs +++ b/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs @@ -7,7 +7,7 @@ use winnow::{ prelude::*, stream::{Location, Stream}, token::{any, none_of, one_of, take_till, take_until}, - Located, Stateful, + LocatingSlice, Stateful, }; use super::TokenStream; @@ -65,13 +65,13 @@ lazy_static! { pub(super) fn lex(i: &str, module_id: ModuleId) -> Result, ContextError>> { let state = State::new(module_id); let input = Input { - input: Located::new(i), + input: LocatingSlice::new(i), state, }; Ok(TokenStream::new(repeat(0.., token).parse(input)?)) } -pub(super) type Input<'a> = Stateful, State>; +pub(super) type Input<'a> = Stateful, State>; #[derive(Debug, Clone)] pub(super) struct State { From 6f19f8d7f918449cc73e165d38a869636d69b390 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Mon, 27 Jan 2025 23:04:21 +1300 Subject: [PATCH 2/4] Refactor execution/mod.rs (code motion) Signed-off-by: Nick Cameron --- src/wasm-lib/kcl/src/engine/conn_wasm.rs | 1 + src/wasm-lib/kcl/src/engine/mod.rs | 65 + src/wasm-lib/kcl/src/execution/exec_ast.rs | 655 ++++- .../kcl/src/execution/function_param.rs | 49 - src/wasm-lib/kcl/src/execution/geometry.rs | 1036 ++++++++ src/wasm-lib/kcl/src/execution/kcl_value.rs | 4 +- src/wasm-lib/kcl/src/execution/memory.rs | 191 ++ src/wasm-lib/kcl/src/execution/mod.rs | 2230 +---------------- src/wasm-lib/kcl/src/execution/state.rs | 300 +++ src/wasm-lib/kcl/src/fs/wasm.rs | 2 +- src/wasm-lib/kcl/src/test_server.rs | 3 +- src/wasm-lib/kcl/src/wasm/mod.rs | 2 +- src/wasm-lib/src/wasm.rs | 10 +- 13 files changed, 2290 insertions(+), 2258 deletions(-) delete mode 100644 src/wasm-lib/kcl/src/execution/function_param.rs create mode 100644 src/wasm-lib/kcl/src/execution/geometry.rs create mode 100644 src/wasm-lib/kcl/src/execution/memory.rs create mode 100644 src/wasm-lib/kcl/src/execution/state.rs diff --git a/src/wasm-lib/kcl/src/engine/conn_wasm.rs b/src/wasm-lib/kcl/src/engine/conn_wasm.rs index f9b69a3261..7c3722c000 100644 --- a/src/wasm-lib/kcl/src/engine/conn_wasm.rs +++ b/src/wasm-lib/kcl/src/engine/conn_wasm.rs @@ -63,6 +63,7 @@ unsafe impl Sync for EngineConnection {} impl EngineConnection { pub async fn new(manager: EngineCommandManager) -> Result { + #[allow(clippy::arc_with_non_send_sync)] Ok(EngineConnection { manager: Arc::new(manager), batch: Arc::new(Mutex::new(Vec::new())), diff --git a/src/wasm-lib/kcl/src/engine/mod.rs b/src/wasm-lib/kcl/src/engine/mod.rs index 71e340313f..aec6b22774 100644 --- a/src/wasm-lib/kcl/src/engine/mod.rs +++ b/src/wasm-lib/kcl/src/engine/mod.rs @@ -621,3 +621,68 @@ pub enum PlaneName { /// The opposite side of the YZ plane. NegYz, } + +/// Create a new zoo api client. +#[cfg(not(target_arch = "wasm32"))] +pub fn new_zoo_client(token: Option, engine_addr: Option) -> anyhow::Result { + let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),); + let http_client = reqwest::Client::builder() + .user_agent(user_agent) + // For file conversions we need this to be long. + .timeout(std::time::Duration::from_secs(600)) + .connect_timeout(std::time::Duration::from_secs(60)); + let ws_client = reqwest::Client::builder() + .user_agent(user_agent) + // For file conversions we need this to be long. + .timeout(std::time::Duration::from_secs(600)) + .connect_timeout(std::time::Duration::from_secs(60)) + .connection_verbose(true) + .tcp_keepalive(std::time::Duration::from_secs(600)) + .http1_only(); + + let zoo_token_env = std::env::var("ZOO_API_TOKEN"); + + let token = if let Some(token) = token { + token + } else if let Ok(token) = std::env::var("KITTYCAD_API_TOKEN") { + if let Ok(zoo_token) = zoo_token_env { + if zoo_token != token { + return Err(anyhow::anyhow!( + "Both environment variables KITTYCAD_API_TOKEN=`{}` and ZOO_API_TOKEN=`{}` are set. Use only one.", + token, + zoo_token + )); + } + } + token + } else if let Ok(token) = zoo_token_env { + token + } else { + return Err(anyhow::anyhow!( + "No API token found in environment variables. Use KITTYCAD_API_TOKEN or ZOO_API_TOKEN" + )); + }; + + // Create the client. + let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client); + // Set an engine address if it's set. + let kittycad_host_env = std::env::var("KITTYCAD_HOST"); + if let Some(addr) = engine_addr { + client.set_base_url(addr); + } else if let Ok(addr) = std::env::var("ZOO_HOST") { + if let Ok(kittycad_host) = kittycad_host_env { + if kittycad_host != addr { + return Err(anyhow::anyhow!( + "Both environment variables KITTYCAD_HOST=`{}` and ZOO_HOST=`{}` are set. Use only one.", + kittycad_host, + addr + )); + } + } + client.set_base_url(addr); + } else if let Ok(addr) = kittycad_host_env { + client.set_base_url(addr); + } + + Ok(client) +} diff --git a/src/wasm-lib/kcl/src/execution/exec_ast.rs b/src/wasm-lib/kcl/src/execution/exec_ast.rs index 6b39d2be89..9b61a29722 100644 --- a/src/wasm-lib/kcl/src/execution/exec_ast.rs +++ b/src/wasm-lib/kcl/src/execution/exec_ast.rs @@ -1,25 +1,456 @@ use std::collections::HashMap; use async_recursion::async_recursion; +use schemars::JsonSchema; use crate::{ + engine::ExecutionKind, errors::{KclError, KclErrorDetails}, execution::{ - BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier, + annotations, + cad_op::{OpArg, Operation}, + BodyType, ExecState, ExecutorContext, KclValue, Metadata, TagEngineInfo, TagIdentifier, }, + fs::FileSystem, parsing::ast::types::{ - ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression, - CallExpressionKw, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, - ObjectExpression, PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator, + ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression, PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator }, - source_range::SourceRange, + source_range::{ModuleId, SourceRange}, std::{ args::{Arg, KwArgs}, FunctionKind, }, }; -use super::cad_op::{OpArg, Operation}; +use super::{state::ModuleState, MemoryFunction, ModuleRepr, ProgramMemory}; + +enum StatementKind<'a> { + Declaration { name: &'a str }, + Expression, +} + +impl ExecutorContext { + async fn handle_annotations( + &self, + annotations: impl Iterator, + scope: annotations::AnnotationScope, + exec_state: &mut ExecState, + ) -> Result<(), KclError> { + for (annotation, source_range) in annotations { + if annotation.annotation_name() == Some(annotations::SETTINGS) { + if scope == annotations::AnnotationScope::Module { + let old_units = exec_state.length_unit(); + exec_state + .mod_local + .settings + .update_from_annotation(annotation, source_range)?; + let new_units = exec_state.length_unit(); + if old_units != new_units { + self.engine.set_units(new_units.into(), source_range).await?; + } + } else { + return Err(KclError::Semantic(KclErrorDetails { + message: "Settings can only be modified at the top level scope of a file".to_owned(), + source_ranges: vec![source_range], + })); + } + } + // TODO warn on unknown annotations + } + Ok(()) + } + + /// Execute an AST's program. + #[async_recursion] + pub(super) async fn exec_program<'a>( + &'a self, + program: NodeRef<'a, crate::parsing::ast::types::Program>, + exec_state: &mut ExecState, + body_type: BodyType, + ) -> Result, KclError> { + self.handle_annotations( + program + .non_code_meta + .start_nodes + .iter() + .filter_map(|n| n.annotation().map(|result| (result, n.as_source_range()))), + annotations::AnnotationScope::Module, + exec_state, + ) + .await?; + + let mut last_expr = None; + // Iterate over the body of the program. + for statement in &program.body { + match statement { + BodyItem::ImportStatement(import_stmt) => { + let source_range = SourceRange::from(import_stmt); + let module_id = self.open_module(&import_stmt.path, exec_state, source_range).await?; + + match &import_stmt.selector { + ImportSelector::List { items } => { + let (_, module_memory, module_exports) = self + .exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range) + .await?; + for import_item in items { + // Extract the item from the module. + let item = + module_memory + .get(&import_item.name.name, import_item.into()) + .map_err(|_err| { + KclError::UndefinedValue(KclErrorDetails { + message: format!("{} is not defined in module", import_item.name.name), + source_ranges: vec![SourceRange::from(&import_item.name)], + }) + })?; + // Check that the item is allowed to be imported. + if !module_exports.contains(&import_item.name.name) { + return Err(KclError::Semantic(KclErrorDetails { + message: format!( + "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.", + import_item.name.name + ), + source_ranges: vec![SourceRange::from(&import_item.name)], + })); + } + + // Add the item to the current module. + exec_state.mut_memory().add( + import_item.identifier(), + item.clone(), + SourceRange::from(&import_item.name), + )?; + + if let ItemVisibility::Export = import_stmt.visibility { + exec_state + .mod_local + .module_exports + .push(import_item.identifier().to_owned()); + } + } + } + ImportSelector::Glob(_) => { + let (_, module_memory, module_exports) = self + .exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range) + .await?; + for name in module_exports.iter() { + let item = module_memory.get(name, source_range).map_err(|_err| { + KclError::Internal(KclErrorDetails { + message: format!("{} is not defined in module (but was exported?)", name), + source_ranges: vec![source_range], + }) + })?; + exec_state.mut_memory().add(name, item.clone(), source_range)?; + + if let ItemVisibility::Export = import_stmt.visibility { + exec_state.mod_local.module_exports.push(name.clone()); + } + } + } + ImportSelector::None { .. } => { + let name = import_stmt.module_name().unwrap(); + let item = KclValue::Module { + value: module_id, + meta: vec![source_range.into()], + }; + exec_state.mut_memory().add(&name, item, source_range)?; + } + } + last_expr = None; + } + BodyItem::ExpressionStatement(expression_statement) => { + let metadata = Metadata::from(expression_statement); + last_expr = Some( + self.execute_expr( + &expression_statement.expression, + exec_state, + &metadata, + StatementKind::Expression, + ) + .await?, + ); + } + BodyItem::VariableDeclaration(variable_declaration) => { + let var_name = variable_declaration.declaration.id.name.to_string(); + let source_range = SourceRange::from(&variable_declaration.declaration.init); + let metadata = Metadata { source_range }; + + let memory_item = self + .execute_expr( + &variable_declaration.declaration.init, + exec_state, + &metadata, + StatementKind::Declaration { name: &var_name }, + ) + .await?; + exec_state.mut_memory().add(&var_name, memory_item, source_range)?; + + // Track exports. + if let ItemVisibility::Export = variable_declaration.visibility { + exec_state.mod_local.module_exports.push(var_name); + } + last_expr = None; + } + BodyItem::ReturnStatement(return_statement) => { + let metadata = Metadata::from(return_statement); + let value = self + .execute_expr( + &return_statement.argument, + exec_state, + &metadata, + StatementKind::Expression, + ) + .await?; + exec_state.mut_memory().return_ = Some(value); + last_expr = None; + } + } + } + + if BodyType::Root == body_type { + // Flush the batch queue. + self.engine + .flush_batch( + // True here tells the engine to flush all the end commands as well like fillets + // and chamfers where the engine would otherwise eat the ID of the segments. + true, + SourceRange::new(program.end, program.end, program.module_id), + ) + .await?; + } + + Ok(last_expr) + } + + async fn open_module( + &self, + path: &ImportPath, + exec_state: &mut ExecState, + source_range: SourceRange, + ) -> Result { + match path { + ImportPath::Kcl { filename } => { + let resolved_path = if let Some(project_dir) = &self.settings.project_directory { + project_dir.join(filename) + } else { + std::path::PathBuf::from(filename) + }; + + if exec_state.mod_local.import_stack.contains(&resolved_path) { + return Err(KclError::ImportCycle(KclErrorDetails { + message: format!( + "circular import of modules is not allowed: {} -> {}", + exec_state + .mod_local + .import_stack + .iter() + .map(|p| p.as_path().to_string_lossy()) + .collect::>() + .join(" -> "), + resolved_path.to_string_lossy() + ), + source_ranges: vec![source_range], + })); + } + + if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) { + return Ok(*id); + } + + let source = self.fs.read_to_string(&resolved_path, source_range).await?; + let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len()); + // TODO handle parsing errors properly + let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?; + let repr = ModuleRepr::Kcl(parsed); + + Ok(exec_state.add_module(id, resolved_path, repr)) + } + ImportPath::Foreign { path } => { + let resolved_path = if let Some(project_dir) = &self.settings.project_directory { + project_dir.join(path) + } else { + std::path::PathBuf::from(path) + }; + + if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) { + return Ok(*id); + } + + let geom = super::import::import_foreign(&resolved_path, None, exec_state, self, source_range).await?; + let repr = ModuleRepr::Foreign(geom); + let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len()); + Ok(exec_state.add_module(id, resolved_path, repr)) + } + i => Err(KclError::Semantic(KclErrorDetails { + message: format!("Unsupported import: `{i}`"), + source_ranges: vec![source_range], + })), + } + } + + async fn exec_module( + &self, + module_id: ModuleId, + exec_state: &mut ExecState, + exec_kind: ExecutionKind, + source_range: SourceRange, + ) -> Result<(Option, ProgramMemory, Vec), KclError> { + let old_units = exec_state.length_unit(); + // TODO It sucks that we have to clone the whole module AST here + let info = exec_state.global.module_infos[&module_id].clone(); + + match &info.repr { + ModuleRepr::Root => Err(KclError::ImportCycle(KclErrorDetails { + message: format!( + "circular import of modules is not allowed: {} -> {}", + exec_state + .mod_local + .import_stack + .iter() + .map(|p| p.as_path().to_string_lossy()) + .collect::>() + .join(" -> "), + info.path.display() + ), + source_ranges: vec![source_range], + })), + ModuleRepr::Kcl(program) => { + let mut local_state = ModuleState { + import_stack: exec_state.mod_local.import_stack.clone(), + ..ModuleState::new(&self.settings) + }; + local_state.import_stack.push(info.path.clone()); + std::mem::swap(&mut exec_state.mod_local, &mut local_state); + let original_execution = self.engine.replace_execution_kind(exec_kind); + + let result = self + .exec_program(program, exec_state, crate::execution::BodyType::Root) + .await; + + let new_units = exec_state.length_unit(); + std::mem::swap(&mut exec_state.mod_local, &mut local_state); + if new_units != old_units { + self.engine.set_units(old_units.into(), Default::default()).await?; + } + self.engine.replace_execution_kind(original_execution); + + let result = result.map_err(|err| { + if let KclError::ImportCycle(_) = err { + // It was an import cycle. Keep the original message. + err.override_source_ranges(vec![source_range]) + } else { + KclError::Semantic(KclErrorDetails { + message: format!( + "Error loading imported file. Open it to view more details. {}: {}", + info.path.display(), + err.message() + ), + source_ranges: vec![source_range], + }) + } + })?; + + Ok((result, local_state.memory, local_state.module_exports)) + } + ModuleRepr::Foreign(geom) => { + let geom = super::import::send_to_engine(geom.clone(), self).await?; + Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new())) + } + } + } + + #[async_recursion] + async fn execute_expr<'a: 'async_recursion>( + &self, + init: &Expr, + exec_state: &mut ExecState, + metadata: &Metadata, + statement_kind: StatementKind<'a>, + ) -> Result { + let item = match init { + Expr::None(none) => KclValue::from(none), + Expr::Literal(literal) => KclValue::from(literal), + Expr::TagDeclarator(tag) => tag.execute(exec_state).await?, + Expr::Identifier(identifier) => { + let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone(); + if let KclValue::Module { value: module_id, meta } = value { + let (result, _, _) = self + .exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range) + .await?; + result.unwrap_or_else(|| { + // The module didn't have a return value. Currently, + // the only way to have a return value is with the final + // statement being an expression statement. + // + // TODO: Make a warning when we support them in the + // execution phase. + let mut new_meta = vec![metadata.to_owned()]; + new_meta.extend(meta); + KclValue::KclNone { + value: Default::default(), + meta: new_meta, + } + }) + } else { + value + } + } + Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?, + Expr::FunctionExpression(function_expression) => { + // Cloning memory here is crucial for semantics so that we close + // over variables. Variables defined lexically later shouldn't + // be available to the function body. + KclValue::Function { + expression: function_expression.clone(), + meta: vec![metadata.to_owned()], + func: None, + memory: Box::new(exec_state.memory().clone()), + } + } + Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?, + Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?, + Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?, + Expr::PipeSubstitution(pipe_substitution) => match statement_kind { + StatementKind::Declaration { name } => { + let message = format!( + "you cannot declare variable {name} as %, because % can only be used in function calls" + ); + + return Err(KclError::Semantic(KclErrorDetails { + message, + source_ranges: vec![pipe_substitution.into()], + })); + } + StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() { + Some(x) => x, + None => { + return Err(KclError::Semantic(KclErrorDetails { + message: "cannot use % outside a pipe expression".to_owned(), + source_ranges: vec![pipe_substitution.into()], + })); + } + }, + }, + Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?, + Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?, + Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?, + Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?, + Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?, + Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?, + Expr::LabelledExpression(expr) => { + let result = self + .execute_expr(&expr.expr, exec_state, metadata, statement_kind) + .await?; + exec_state + .mut_memory() + .add(&expr.label.name, result.clone(), init.into())?; + // TODO this lets us use the label as a variable name, but not as a tag in most cases + result + } + }; + Ok(item) + } +} impl BinaryPart { #[async_recursion] @@ -863,7 +1294,7 @@ impl Node { .await? .get_bool()?; if cond { - let block_result = ctx.inner_execute(&self.then_val, exec_state, BodyType::Block).await?; + let block_result = ctx.exec_program(&self.then_val, exec_state, BodyType::Block).await?; // Block must end in an expression, so this has to be Some. // Enforced by the parser. // See https://github.com/KittyCAD/modeling-app/issues/4015 @@ -882,9 +1313,7 @@ impl Node { .await? .get_bool()?; if cond { - let block_result = ctx - .inner_execute(&else_if.then_val, exec_state, BodyType::Block) - .await?; + let block_result = ctx.exec_program(&else_if.then_val, exec_state, BodyType::Block).await?; // Block must end in an expression, so this has to be Some. // Enforced by the parser. // See https://github.com/KittyCAD/modeling-app/issues/4015 @@ -893,7 +1322,7 @@ impl Node { } // Run the final `else` branch. - ctx.inner_execute(&self.final_else, exec_state, BodyType::Block) + ctx.exec_program(&self.final_else, exec_state, BodyType::Block) .await .map(|expr| expr.unwrap()) } @@ -1001,3 +1430,207 @@ impl Node { execute_pipe_body(exec_state, &self.body, self.into(), ctx).await } } + +/// For each argument given, +/// assign it to a parameter of the function, in the given block of function memory. +/// Returns Err if too few/too many arguments were given for the function. +fn assign_args_to_params( + function_expression: NodeRef<'_, FunctionExpression>, + args: Vec, + mut fn_memory: ProgramMemory, +) -> Result { + let num_args = function_expression.number_of_args(); + let (min_params, max_params) = num_args.into_inner(); + let n = args.len(); + + // Check if the user supplied too many arguments + // (we'll check for too few arguments below). + let err_wrong_number_args = KclError::Semantic(KclErrorDetails { + message: if min_params == max_params { + format!("Expected {min_params} arguments, got {n}") + } else { + format!("Expected {min_params}-{max_params} arguments, got {n}") + }, + source_ranges: vec![function_expression.into()], + }); + if n > max_params { + return Err(err_wrong_number_args); + } + + // Add the arguments to the memory. A new call frame should have already + // been created. + for (index, param) in function_expression.params.iter().enumerate() { + if let Some(arg) = args.get(index) { + // Argument was provided. + fn_memory.add(¶m.identifier.name, arg.value.clone(), (¶m.identifier).into())?; + } else { + // Argument was not provided. + if let Some(ref default_val) = param.default_value { + // If the corresponding parameter is optional, + // then it's fine, the user doesn't need to supply it. + fn_memory.add( + ¶m.identifier.name, + default_val.clone().into(), + (¶m.identifier).into(), + )?; + } else { + // But if the corresponding parameter was required, + // then the user has called with too few arguments. + return Err(err_wrong_number_args); + } + } + } + Ok(fn_memory) +} + +fn assign_args_to_params_kw( + function_expression: NodeRef<'_, FunctionExpression>, + mut args: crate::std::args::KwArgs, + mut fn_memory: ProgramMemory, +) -> Result { + // Add the arguments to the memory. A new call frame should have already + // been created. + let source_ranges = vec![function_expression.into()]; + for param in function_expression.params.iter() { + if param.labeled { + let arg = args.labeled.get(¶m.identifier.name); + let arg_val = match arg { + Some(arg) => arg.value.clone(), + None => match param.default_value { + Some(ref default_val) => KclValue::from(default_val.clone()), + None => { + return Err(KclError::Semantic(KclErrorDetails { + source_ranges, + message: format!( + "This function requires a parameter {}, but you haven't passed it one.", + param.identifier.name + ), + })); + } + }, + }; + fn_memory.add(¶m.identifier.name, arg_val, (¶m.identifier).into())?; + } else { + let Some(unlabeled) = args.unlabeled.take() else { + let param_name = ¶m.identifier.name; + return Err(if args.labeled.contains_key(param_name) { + KclError::Semantic(KclErrorDetails { + source_ranges, + message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"), + }) + } else { + KclError::Semantic(KclErrorDetails { + source_ranges, + message: "This function expects an unlabeled first parameter, but you haven't passed it one." + .to_owned(), + }) + }); + }; + fn_memory.add( + ¶m.identifier.name, + unlabeled.value.clone(), + (¶m.identifier).into(), + )?; + } + } + Ok(fn_memory) +} + +pub(crate) async fn call_user_defined_function( + args: Vec, + memory: &ProgramMemory, + function_expression: NodeRef<'_, FunctionExpression>, + exec_state: &mut ExecState, + ctx: &ExecutorContext, +) -> Result, KclError> { + // Create a new environment to execute the function body in so that local + // variables shadow variables in the parent scope. The new environment's + // parent should be the environment of the closure. + let mut body_memory = memory.clone(); + let body_env = body_memory.new_env_for_call(memory.current_env); + body_memory.current_env = body_env; + let fn_memory = assign_args_to_params(function_expression, args, body_memory)?; + + // Execute the function body using the memory we just created. + let (result, fn_memory) = { + let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory); + let result = ctx + .exec_program(&function_expression.body, exec_state, BodyType::Block) + .await; + // Restore the previous memory. + let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory); + + (result, fn_memory) + }; + + result.map(|_| fn_memory.return_) +} + +pub(crate) async fn call_user_defined_function_kw( + args: crate::std::args::KwArgs, + memory: &ProgramMemory, + function_expression: NodeRef<'_, FunctionExpression>, + exec_state: &mut ExecState, + ctx: &ExecutorContext, +) -> Result, KclError> { + // Create a new environment to execute the function body in so that local + // variables shadow variables in the parent scope. The new environment's + // parent should be the environment of the closure. + let mut body_memory = memory.clone(); + let body_env = body_memory.new_env_for_call(memory.current_env); + body_memory.current_env = body_env; + let fn_memory = assign_args_to_params_kw(function_expression, args, body_memory)?; + + // Execute the function body using the memory we just created. + let (result, fn_memory) = { + let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory); + let result = ctx + .exec_program(&function_expression.body, exec_state, BodyType::Block) + .await; + // Restore the previous memory. + let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory); + + (result, fn_memory) + }; + + result.map(|_| fn_memory.return_) +} + +/// A function being used as a parameter into a stdlib function. This is a +/// closure, plus everything needed to execute it. +pub struct FunctionParam<'a> { + pub inner: Option<&'a MemoryFunction>, + pub memory: ProgramMemory, + pub fn_expr: crate::parsing::ast::types::BoxNode, + pub meta: Vec, + pub ctx: ExecutorContext, +} + +impl<'a> FunctionParam<'a> { + pub async fn call(&self, exec_state: &mut ExecState, args: Vec) -> Result, KclError> { + if let Some(inner) = self.inner { + inner( + args, + self.memory.clone(), + self.fn_expr.clone(), + self.meta.clone(), + exec_state, + self.ctx.clone(), + ) + .await + } else { + call_user_defined_function(args, &self.memory, self.fn_expr.as_ref(), exec_state, &self.ctx).await + } + } +} + +impl JsonSchema for FunctionParam<'_> { + fn schema_name() -> String { + "FunctionParam".to_owned() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + // TODO: Actually generate a reasonable schema. + gen.subschema_for::<()>() + } +} diff --git a/src/wasm-lib/kcl/src/execution/function_param.rs b/src/wasm-lib/kcl/src/execution/function_param.rs deleted file mode 100644 index 8f907cfa1f..0000000000 --- a/src/wasm-lib/kcl/src/execution/function_param.rs +++ /dev/null @@ -1,49 +0,0 @@ -use schemars::JsonSchema; - -use crate::{ - errors::KclError, - execution::{ - call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory, - }, - parsing::ast::types::FunctionExpression, - std::args::Arg, -}; - -/// A function being used as a parameter into a stdlib function. This is a -/// closure, plus everything needed to execute it. -pub struct FunctionParam<'a> { - pub inner: Option<&'a MemoryFunction>, - pub memory: ProgramMemory, - pub fn_expr: crate::parsing::ast::types::BoxNode, - pub meta: Vec, - pub ctx: ExecutorContext, -} - -impl<'a> FunctionParam<'a> { - pub async fn call(&self, exec_state: &mut ExecState, args: Vec) -> Result, KclError> { - if let Some(inner) = self.inner { - inner( - args, - self.memory.clone(), - self.fn_expr.clone(), - self.meta.clone(), - exec_state, - self.ctx.clone(), - ) - .await - } else { - call_user_defined_function(args, &self.memory, self.fn_expr.as_ref(), exec_state, &self.ctx).await - } - } -} - -impl JsonSchema for FunctionParam<'_> { - fn schema_name() -> String { - "FunctionParam".to_owned() - } - - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - // TODO: Actually generate a reasonable schema. - gen.subschema_for::<()>() - } -} diff --git a/src/wasm-lib/kcl/src/execution/geometry.rs b/src/wasm-lib/kcl/src/execution/geometry.rs new file mode 100644 index 0000000000..b456bceacf --- /dev/null +++ b/src/wasm-lib/kcl/src/execution/geometry.rs @@ -0,0 +1,1036 @@ +use anyhow::Result; +use indexmap::IndexMap; +use kittycad_modeling_cmds as kcmc; +use kittycad_modeling_cmds::length_unit::LengthUnit; +use parse_display::{Display, FromStr}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + errors::KclError, + execution::{ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen}, + parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode}, +}; + +type Point2D = kcmc::shared::Point2d; +type Point3D = kcmc::shared::Point3d; + +/// A geometry. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type")] +pub enum Geometry { + Sketch(Box), + Solid(Box), +} + +impl Geometry { + pub fn id(&self) -> uuid::Uuid { + match self { + Geometry::Sketch(s) => s.id, + Geometry::Solid(e) => e.id, + } + } +} + +/// A set of geometry. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type")] +#[allow(clippy::vec_box)] +pub enum Geometries { + Sketches(Vec>), + Solids(Vec>), +} + +impl From for Geometries { + fn from(value: Geometry) -> Self { + match value { + Geometry::Sketch(x) => Self::Sketches(vec![x]), + Geometry::Solid(x) => Self::Solids(vec![x]), + } + } +} + +/// A sketch or a group of sketches. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +#[allow(clippy::vec_box)] +pub enum SketchSet { + Sketch(Box), + Sketches(Vec>), +} + +impl SketchSet { + pub fn meta(&self) -> Vec { + match self { + SketchSet::Sketch(sg) => sg.meta.clone(), + SketchSet::Sketches(sg) => sg.iter().flat_map(|sg| sg.meta.clone()).collect(), + } + } +} + +impl From for Vec { + fn from(value: SketchSet) -> Self { + match value { + SketchSet::Sketch(sg) => vec![*sg], + SketchSet::Sketches(sgs) => sgs.into_iter().map(|sg| *sg).collect(), + } + } +} + +impl From for SketchSet { + fn from(sg: Sketch) -> Self { + SketchSet::Sketch(Box::new(sg)) + } +} + +impl From> for SketchSet { + fn from(sg: Box) -> Self { + SketchSet::Sketch(sg) + } +} + +impl From> for SketchSet { + fn from(sg: Vec) -> Self { + if sg.len() == 1 { + SketchSet::Sketch(Box::new(sg[0].clone())) + } else { + SketchSet::Sketches(sg.into_iter().map(Box::new).collect()) + } + } +} + +impl From>> for SketchSet { + fn from(sg: Vec>) -> Self { + if sg.len() == 1 { + SketchSet::Sketch(sg[0].clone()) + } else { + SketchSet::Sketches(sg) + } + } +} + +impl From for Vec> { + fn from(sg: SketchSet) -> Self { + match sg { + SketchSet::Sketch(sg) => vec![sg], + SketchSet::Sketches(sgs) => sgs, + } + } +} + +impl From<&Sketch> for Vec> { + fn from(sg: &Sketch) -> Self { + vec![Box::new(sg.clone())] + } +} + +impl From> for Vec> { + fn from(sg: Box) -> Self { + vec![sg] + } +} + +/// A solid or a group of solids. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +#[allow(clippy::vec_box)] +pub enum SolidSet { + Solid(Box), + Solids(Vec>), +} + +impl From for SolidSet { + fn from(eg: Solid) -> Self { + SolidSet::Solid(Box::new(eg)) + } +} + +impl From> for SolidSet { + fn from(eg: Box) -> Self { + SolidSet::Solid(eg) + } +} + +impl From> for SolidSet { + fn from(eg: Vec) -> Self { + if eg.len() == 1 { + SolidSet::Solid(Box::new(eg[0].clone())) + } else { + SolidSet::Solids(eg.into_iter().map(Box::new).collect()) + } + } +} + +impl From>> for SolidSet { + fn from(eg: Vec>) -> Self { + if eg.len() == 1 { + SolidSet::Solid(eg[0].clone()) + } else { + SolidSet::Solids(eg) + } + } +} + +impl From for Vec> { + fn from(eg: SolidSet) -> Self { + match eg { + SolidSet::Solid(eg) => vec![eg], + SolidSet::Solids(egs) => egs, + } + } +} + +impl From<&Solid> for Vec> { + fn from(eg: &Solid) -> Self { + vec![Box::new(eg.clone())] + } +} + +impl From> for Vec> { + fn from(eg: Box) -> Self { + vec![eg] + } +} + +/// Data for an imported geometry. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ImportedGeometry { + /// The ID of the imported geometry. + pub id: uuid::Uuid, + /// The original file paths. + pub value: Vec, + #[serde(rename = "__meta")] + pub meta: Vec, +} + +/// A helix. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct Helix { + /// The id of the helix. + pub value: uuid::Uuid, + /// Number of revolutions. + pub revolutions: f64, + /// Start angle (in degrees). + pub angle_start: f64, + /// Is the helix rotation counter clockwise? + pub ccw: bool, + pub units: UnitLen, + #[serde(rename = "__meta")] + pub meta: Vec, +} + +/// A plane. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct Plane { + /// The id of the plane. + pub id: uuid::Uuid, + // The code for the plane either a string or custom. + pub value: PlaneType, + /// Origin of the plane. + pub origin: Point3d, + /// What should the plane’s X axis be? + pub x_axis: Point3d, + /// What should the plane’s Y axis be? + pub y_axis: Point3d, + /// The z-axis (normal). + pub z_axis: Point3d, + pub units: UnitLen, + #[serde(rename = "__meta")] + pub meta: Vec, +} + +impl Plane { + pub(crate) fn from_plane_data(value: crate::std::sketch::PlaneData, exec_state: &mut ExecState) -> Self { + let id = exec_state.global.id_generator.next_uuid(); + match value { + crate::std::sketch::PlaneData::XY => Plane { + id, + origin: Point3d::new(0.0, 0.0, 0.0), + x_axis: Point3d::new(1.0, 0.0, 0.0), + y_axis: Point3d::new(0.0, 1.0, 0.0), + z_axis: Point3d::new(0.0, 0.0, 1.0), + value: PlaneType::XY, + units: exec_state.length_unit(), + meta: vec![], + }, + crate::std::sketch::PlaneData::NegXY => Plane { + id, + origin: Point3d::new(0.0, 0.0, 0.0), + x_axis: Point3d::new(1.0, 0.0, 0.0), + y_axis: Point3d::new(0.0, 1.0, 0.0), + z_axis: Point3d::new(0.0, 0.0, -1.0), + value: PlaneType::XY, + units: exec_state.length_unit(), + meta: vec![], + }, + crate::std::sketch::PlaneData::XZ => Plane { + id, + origin: Point3d::new(0.0, 0.0, 0.0), + x_axis: Point3d::new(1.0, 0.0, 0.0), + y_axis: Point3d::new(0.0, 0.0, 1.0), + z_axis: Point3d::new(0.0, -1.0, 0.0), + value: PlaneType::XZ, + units: exec_state.length_unit(), + meta: vec![], + }, + crate::std::sketch::PlaneData::NegXZ => Plane { + id, + origin: Point3d::new(0.0, 0.0, 0.0), + x_axis: Point3d::new(-1.0, 0.0, 0.0), + y_axis: Point3d::new(0.0, 0.0, 1.0), + z_axis: Point3d::new(0.0, 1.0, 0.0), + value: PlaneType::XZ, + units: exec_state.length_unit(), + meta: vec![], + }, + crate::std::sketch::PlaneData::YZ => Plane { + id, + origin: Point3d::new(0.0, 0.0, 0.0), + x_axis: Point3d::new(0.0, 1.0, 0.0), + y_axis: Point3d::new(0.0, 0.0, 1.0), + z_axis: Point3d::new(1.0, 0.0, 0.0), + value: PlaneType::YZ, + units: exec_state.length_unit(), + meta: vec![], + }, + crate::std::sketch::PlaneData::NegYZ => Plane { + id, + origin: Point3d::new(0.0, 0.0, 0.0), + x_axis: Point3d::new(0.0, 1.0, 0.0), + y_axis: Point3d::new(0.0, 0.0, 1.0), + z_axis: Point3d::new(-1.0, 0.0, 0.0), + value: PlaneType::YZ, + units: exec_state.length_unit(), + meta: vec![], + }, + crate::std::sketch::PlaneData::Plane { + origin, + x_axis, + y_axis, + z_axis, + } => Plane { + id, + origin: *origin, + x_axis: *x_axis, + y_axis: *y_axis, + z_axis: *z_axis, + value: PlaneType::Custom, + units: exec_state.length_unit(), + meta: vec![], + }, + } + } + + /// The standard planes are XY, YZ and XZ (in both positive and negative) + pub fn is_standard(&self) -> bool { + !self.is_custom() + } + + /// The standard planes are XY, YZ and XZ (in both positive and negative) + /// Custom planes are any other plane that the user might specify. + pub fn is_custom(&self) -> bool { + matches!(self.value, PlaneType::Custom) + } +} + +/// A face. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct Face { + /// The id of the face. + pub id: uuid::Uuid, + /// The tag of the face. + pub value: String, + /// What should the face’s X axis be? + pub x_axis: Point3d, + /// What should the face’s Y axis be? + pub y_axis: Point3d, + /// The z-axis (normal). + pub z_axis: Point3d, + /// The solid the face is on. + pub solid: Box, + pub units: UnitLen, + #[serde(rename = "__meta")] + pub meta: Vec, +} + +/// Type for a plane. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +#[display(style = "camelCase")] +pub enum PlaneType { + #[serde(rename = "XY", alias = "xy")] + #[display("XY")] + XY, + #[serde(rename = "XZ", alias = "xz")] + #[display("XZ")] + XZ, + #[serde(rename = "YZ", alias = "yz")] + #[display("YZ")] + YZ, + /// A custom plane. + #[serde(rename = "Custom")] + #[display("Custom")] + Custom, +} + +/// A sketch is a collection of paths. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct Sketch { + /// The id of the sketch (this will change when the engine's reference to it changes). + pub id: uuid::Uuid, + /// The paths in the sketch. + pub paths: Vec, + /// What the sketch is on (can be a plane or a face). + pub on: SketchSurface, + /// The starting path. + pub start: BasePath, + /// Tag identifiers that have been declared in this sketch. + #[serde(default, skip_serializing_if = "IndexMap::is_empty")] + pub tags: IndexMap, + /// The original id of the sketch. This stays the same even if the sketch is + /// is sketched on face etc. + #[serde(skip)] + pub original_id: uuid::Uuid, + pub units: UnitLen, + /// Metadata. + #[serde(rename = "__meta")] + pub meta: Vec, +} + +/// A sketch type. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum SketchSurface { + Plane(Box), + Face(Box), +} + +impl SketchSurface { + pub(crate) fn id(&self) -> uuid::Uuid { + match self { + SketchSurface::Plane(plane) => plane.id, + SketchSurface::Face(face) => face.id, + } + } + pub(crate) fn x_axis(&self) -> Point3d { + match self { + SketchSurface::Plane(plane) => plane.x_axis, + SketchSurface::Face(face) => face.x_axis, + } + } + pub(crate) fn y_axis(&self) -> Point3d { + match self { + SketchSurface::Plane(plane) => plane.y_axis, + SketchSurface::Face(face) => face.y_axis, + } + } + pub(crate) fn z_axis(&self) -> Point3d { + match self { + SketchSurface::Plane(plane) => plane.z_axis, + SketchSurface::Face(face) => face.z_axis, + } + } +} + +#[derive(Debug, Clone)] +pub(crate) enum GetTangentialInfoFromPathsResult { + PreviousPoint([f64; 2]), + Arc { center: [f64; 2], ccw: bool }, + Circle { center: [f64; 2], ccw: bool, radius: f64 }, +} + +impl GetTangentialInfoFromPathsResult { + pub(crate) fn tan_previous_point(&self, last_arc_end: crate::std::utils::Coords2d) -> [f64; 2] { + match self { + GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p, + GetTangentialInfoFromPathsResult::Arc { center, ccw, .. } => { + crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end) + } + // The circle always starts at 0 degrees, so a suitable tangent + // point is either directly above or below. + GetTangentialInfoFromPathsResult::Circle { + center, radius, ccw, .. + } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }], + } + } +} + +impl Sketch { + pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) { + let mut tag_identifier: TagIdentifier = tag.into(); + let base = current_path.get_base(); + tag_identifier.info = Some(TagEngineInfo { + id: base.geo_meta.id, + sketch: self.id, + path: Some(current_path.clone()), + surface: None, + }); + + self.tags.insert(tag.name.to_string(), tag_identifier); + } + + /// Get the path most recently sketched. + pub(crate) fn latest_path(&self) -> Option<&Path> { + self.paths.last() + } + + /// The "pen" is an imaginary pen drawing the path. + /// This gets the current point the pen is hovering over, i.e. the point + /// where the last path segment ends, and the next path segment will begin. + pub(crate) fn current_pen_position(&self) -> Result { + let Some(path) = self.latest_path() else { + return Ok(self.start.to.into()); + }; + + let base = path.get_base(); + Ok(base.to.into()) + } + + pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult { + let Some(path) = self.latest_path() else { + return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to); + }; + path.get_tangential_info() + } +} + +/// An solid is a collection of extrude surfaces. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct Solid { + /// The id of the solid. + pub id: uuid::Uuid, + /// The extrude surfaces. + pub value: Vec, + /// The sketch. + pub sketch: Sketch, + /// The height of the solid. + pub height: f64, + /// The id of the extrusion start cap + pub start_cap_id: Option, + /// The id of the extrusion end cap + pub end_cap_id: Option, + /// Chamfers or fillets on this solid. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub edge_cuts: Vec, + pub units: UnitLen, + /// Metadata. + #[serde(rename = "__meta")] + pub meta: Vec, +} + +impl Solid { + pub(crate) fn get_all_edge_cut_ids(&self) -> Vec { + self.edge_cuts.iter().map(|foc| foc.id()).collect() + } +} + +/// An solid ID and its fillet and chamfer IDs. This is needed for lazy +/// fillet evaluation. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct SolidLazyIds { + pub solid_id: uuid::Uuid, + pub sketch_id: uuid::Uuid, + /// Chamfers or fillets on this solid. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub edge_cuts: Vec, +} + +impl From<&Solid> for SolidLazyIds { + fn from(eg: &Solid) -> Self { + Self { + solid_id: eg.id, + sketch_id: eg.sketch.id, + edge_cuts: eg.edge_cuts.iter().map(|foc| foc.id()).collect(), + } + } +} + +/// A fillet or a chamfer. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum EdgeCut { + /// A fillet. + Fillet { + /// The id of the engine command that called this fillet. + id: uuid::Uuid, + radius: f64, + /// The engine id of the edge to fillet. + #[serde(rename = "edgeId")] + edge_id: uuid::Uuid, + tag: Box>, + }, + /// A chamfer. + Chamfer { + /// The id of the engine command that called this chamfer. + id: uuid::Uuid, + length: f64, + /// The engine id of the edge to chamfer. + #[serde(rename = "edgeId")] + edge_id: uuid::Uuid, + tag: Box>, + }, +} + +impl EdgeCut { + pub fn id(&self) -> uuid::Uuid { + match self { + EdgeCut::Fillet { id, .. } => *id, + EdgeCut::Chamfer { id, .. } => *id, + } + } + + pub fn edge_id(&self) -> uuid::Uuid { + match self { + EdgeCut::Fillet { edge_id, .. } => *edge_id, + EdgeCut::Chamfer { edge_id, .. } => *edge_id, + } + } + + pub fn tag(&self) -> Option { + match self { + EdgeCut::Fillet { tag, .. } => *tag.clone(), + EdgeCut::Chamfer { tag, .. } => *tag.clone(), + } + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)] +#[ts(export)] +pub struct Point2d { + pub x: f64, + pub y: f64, +} + +impl From<[f64; 2]> for Point2d { + fn from(p: [f64; 2]) -> Self { + Self { x: p[0], y: p[1] } + } +} + +impl From<&[f64; 2]> for Point2d { + fn from(p: &[f64; 2]) -> Self { + Self { x: p[0], y: p[1] } + } +} + +impl From for [f64; 2] { + fn from(p: Point2d) -> Self { + [p.x, p.y] + } +} + +impl From for Point2D { + fn from(p: Point2d) -> Self { + Self { x: p.x, y: p.y } + } +} + +impl Point2d { + pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; + pub fn scale(self, scalar: f64) -> Self { + Self { + x: self.x * scalar, + y: self.y * scalar, + } + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)] +#[ts(export)] +pub struct Point3d { + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl Point3d { + pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 }; + pub fn new(x: f64, y: f64, z: f64) -> Self { + Self { x, y, z } + } +} + +impl From for Point3D { + fn from(p: Point3d) -> Self { + Self { x: p.x, y: p.y, z: p.z } + } +} +impl From for kittycad_modeling_cmds::shared::Point3d { + fn from(p: Point3d) -> Self { + Self { + x: LengthUnit(p.x), + y: LengthUnit(p.y), + z: LengthUnit(p.z), + } + } +} + +/// A base path. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct BasePath { + /// The from point. + #[ts(type = "[number, number]")] + pub from: [f64; 2], + /// The to point. + #[ts(type = "[number, number]")] + pub to: [f64; 2], + /// The tag of the path. + pub tag: Option, + /// Metadata. + #[serde(rename = "__geoMeta")] + pub geo_meta: GeoMeta, +} + +/// Geometry metadata. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct GeoMeta { + /// The id of the geometry. + pub id: uuid::Uuid, + /// Metadata. + #[serde(flatten)] + pub metadata: Metadata, +} + +/// A path. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type")] +pub enum Path { + /// A path that goes to a point. + ToPoint { + #[serde(flatten)] + base: BasePath, + }, + /// A arc that is tangential to the last path segment that goes to a point + TangentialArcTo { + #[serde(flatten)] + base: BasePath, + /// the arc's center + #[ts(type = "[number, number]")] + center: [f64; 2], + /// arc's direction + ccw: bool, + }, + /// A arc that is tangential to the last path segment + TangentialArc { + #[serde(flatten)] + base: BasePath, + /// the arc's center + #[ts(type = "[number, number]")] + center: [f64; 2], + /// arc's direction + ccw: bool, + }, + // TODO: consolidate segment enums, remove Circle. https://github.com/KittyCAD/modeling-app/issues/3940 + /// a complete arc + Circle { + #[serde(flatten)] + base: BasePath, + /// the arc's center + #[ts(type = "[number, number]")] + center: [f64; 2], + /// the arc's radius + radius: f64, + /// arc's direction + /// This is used to compute the tangential angle. + ccw: bool, + }, + /// A path that is horizontal. + Horizontal { + #[serde(flatten)] + base: BasePath, + /// The x coordinate. + x: f64, + }, + /// An angled line to. + AngledLineTo { + #[serde(flatten)] + base: BasePath, + /// The x coordinate. + x: Option, + /// The y coordinate. + y: Option, + }, + /// A base path. + Base { + #[serde(flatten)] + base: BasePath, + }, + /// A circular arc, not necessarily tangential to the current point. + Arc { + #[serde(flatten)] + base: BasePath, + /// Center of the circle that this arc is drawn on. + center: [f64; 2], + /// Radius of the circle that this arc is drawn on. + radius: f64, + /// True if the arc is counterclockwise. + ccw: bool, + }, +} + +/// What kind of path is this? +#[derive(Display)] +enum PathType { + ToPoint, + Base, + TangentialArc, + TangentialArcTo, + Circle, + Horizontal, + AngledLineTo, + Arc, +} + +impl From<&Path> for PathType { + fn from(value: &Path) -> Self { + match value { + Path::ToPoint { .. } => Self::ToPoint, + Path::TangentialArcTo { .. } => Self::TangentialArcTo, + Path::TangentialArc { .. } => Self::TangentialArc, + Path::Circle { .. } => Self::Circle, + Path::Horizontal { .. } => Self::Horizontal, + Path::AngledLineTo { .. } => Self::AngledLineTo, + Path::Base { .. } => Self::Base, + Path::Arc { .. } => Self::Arc, + } + } +} + +impl Path { + pub fn get_id(&self) -> uuid::Uuid { + match self { + Path::ToPoint { base } => base.geo_meta.id, + Path::Horizontal { base, .. } => base.geo_meta.id, + Path::AngledLineTo { base, .. } => base.geo_meta.id, + Path::Base { base } => base.geo_meta.id, + Path::TangentialArcTo { base, .. } => base.geo_meta.id, + Path::TangentialArc { base, .. } => base.geo_meta.id, + Path::Circle { base, .. } => base.geo_meta.id, + Path::Arc { base, .. } => base.geo_meta.id, + } + } + + pub fn get_tag(&self) -> Option { + match self { + Path::ToPoint { base } => base.tag.clone(), + Path::Horizontal { base, .. } => base.tag.clone(), + Path::AngledLineTo { base, .. } => base.tag.clone(), + Path::Base { base } => base.tag.clone(), + Path::TangentialArcTo { base, .. } => base.tag.clone(), + Path::TangentialArc { base, .. } => base.tag.clone(), + Path::Circle { base, .. } => base.tag.clone(), + Path::Arc { base, .. } => base.tag.clone(), + } + } + + pub fn get_base(&self) -> &BasePath { + match self { + Path::ToPoint { base } => base, + Path::Horizontal { base, .. } => base, + Path::AngledLineTo { base, .. } => base, + Path::Base { base } => base, + Path::TangentialArcTo { base, .. } => base, + Path::TangentialArc { base, .. } => base, + Path::Circle { base, .. } => base, + Path::Arc { base, .. } => base, + } + } + + /// Where does this path segment start? + pub fn get_from(&self) -> &[f64; 2] { + &self.get_base().from + } + /// Where does this path segment end? + pub fn get_to(&self) -> &[f64; 2] { + &self.get_base().to + } + + /// Length of this path segment, in cartesian plane. + pub fn length(&self) -> f64 { + match self { + Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => { + linear_distance(self.get_from(), self.get_to()) + } + Self::TangentialArc { + base: _, + center, + ccw: _, + } + | Self::TangentialArcTo { + base: _, + center, + ccw: _, + } => { + // The radius can be calculated as the linear distance between `to` and `center`, + // or between `from` and `center`. They should be the same. + let radius = linear_distance(self.get_from(), center); + debug_assert_eq!(radius, linear_distance(self.get_to(), center)); + // TODO: Call engine utils to figure this out. + linear_distance(self.get_from(), self.get_to()) + } + Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius, + Self::Arc { .. } => { + // TODO: Call engine utils to figure this out. + linear_distance(self.get_from(), self.get_to()) + } + } + } + + pub fn get_base_mut(&mut self) -> Option<&mut BasePath> { + match self { + Path::ToPoint { base } => Some(base), + Path::Horizontal { base, .. } => Some(base), + Path::AngledLineTo { base, .. } => Some(base), + Path::Base { base } => Some(base), + Path::TangentialArcTo { base, .. } => Some(base), + Path::TangentialArc { base, .. } => Some(base), + Path::Circle { base, .. } => Some(base), + Path::Arc { base, .. } => Some(base), + } + } + + pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult { + match self { + Path::TangentialArc { center, ccw, .. } + | Path::TangentialArcTo { center, ccw, .. } + | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc { + center: *center, + ccw: *ccw, + }, + Path::Circle { + center, ccw, radius, .. + } => GetTangentialInfoFromPathsResult::Circle { + center: *center, + ccw: *ccw, + radius: *radius, + }, + Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => { + let base = self.get_base(); + GetTangentialInfoFromPathsResult::PreviousPoint(base.from) + } + } + } +} + +/// Compute the straight-line distance between a pair of (2D) points. +#[rustfmt::skip] +fn linear_distance( + [x0, y0]: &[f64; 2], + [x1, y1]: &[f64; 2] +) -> f64 { + let y_sq = (y1 - y0).powi(2); + let x_sq = (x1 - x0).powi(2); + (y_sq + x_sq).sqrt() +} + +/// An extrude surface. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum ExtrudeSurface { + /// An extrude plane. + ExtrudePlane(ExtrudePlane), + ExtrudeArc(ExtrudeArc), + Chamfer(ChamferSurface), + Fillet(FilletSurface), +} + +// Chamfer surface. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ChamferSurface { + /// The id for the chamfer surface. + pub face_id: uuid::Uuid, + /// The tag. + pub tag: Option>, + /// Metadata. + #[serde(flatten)] + pub geo_meta: GeoMeta, +} + +// Fillet surface. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct FilletSurface { + /// The id for the fillet surface. + pub face_id: uuid::Uuid, + /// The tag. + pub tag: Option>, + /// Metadata. + #[serde(flatten)] + pub geo_meta: GeoMeta, +} + +/// An extruded plane. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ExtrudePlane { + /// The face id for the extrude plane. + pub face_id: uuid::Uuid, + /// The tag. + pub tag: Option>, + /// Metadata. + #[serde(flatten)] + pub geo_meta: GeoMeta, +} + +/// An extruded arc. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ExtrudeArc { + /// The face id for the extrude plane. + pub face_id: uuid::Uuid, + /// The tag. + pub tag: Option>, + /// Metadata. + #[serde(flatten)] + pub geo_meta: GeoMeta, +} + +impl ExtrudeSurface { + pub fn get_id(&self) -> uuid::Uuid { + match self { + ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id, + ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id, + ExtrudeSurface::Fillet(f) => f.geo_meta.id, + ExtrudeSurface::Chamfer(c) => c.geo_meta.id, + } + } + + pub fn get_tag(&self) -> Option> { + match self { + ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(), + ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(), + ExtrudeSurface::Fillet(f) => f.tag.clone(), + ExtrudeSurface::Chamfer(c) => c.tag.clone(), + } + } +} diff --git a/src/wasm-lib/kcl/src/execution/kcl_value.rs b/src/wasm-lib/kcl/src/execution/kcl_value.rs index 39caf9ebd3..eaaa664e35 100644 --- a/src/wasm-lib/kcl/src/execution/kcl_value.rs +++ b/src/wasm-lib/kcl/src/execution/kcl_value.rs @@ -533,7 +533,7 @@ impl KclValue { ) .await } else { - crate::execution::call_user_defined_function( + crate::execution::exec_ast::call_user_defined_function( args, closure_memory.as_ref(), expression.as_ref(), @@ -568,7 +568,7 @@ impl KclValue { if let Some(_func) = func { todo!("Implement calling KCL stdlib fns that are aliased. Part of https://github.com/KittyCAD/modeling-app/issues/4600"); } else { - crate::execution::call_user_defined_function_kw( + crate::execution::exec_ast::call_user_defined_function_kw( args.kw_args, closure_memory.as_ref(), expression.as_ref(), diff --git a/src/wasm-lib/kcl/src/execution/memory.rs b/src/wasm-lib/kcl/src/execution/memory.rs new file mode 100644 index 0000000000..4b0ef5e366 --- /dev/null +++ b/src/wasm-lib/kcl/src/execution/memory.rs @@ -0,0 +1,191 @@ +use anyhow::Result; +use indexmap::IndexMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + errors::{KclError, KclErrorDetails}, + execution::{KclValue, Metadata, Sketch, Solid, TagIdentifier}, + source_range::SourceRange, +}; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ProgramMemory { + pub environments: Vec, + pub current_env: EnvironmentRef, + #[serde(rename = "return")] + pub return_: Option, +} + +impl ProgramMemory { + pub fn new() -> Self { + Self { + environments: vec![Environment::root()], + current_env: EnvironmentRef::root(), + return_: None, + } + } + + pub fn new_env_for_call(&mut self, parent: EnvironmentRef) -> EnvironmentRef { + let new_env_ref = EnvironmentRef(self.environments.len()); + let new_env = Environment::new(parent); + self.environments.push(new_env); + new_env_ref + } + + /// Add to the program memory in the current scope. + pub fn add(&mut self, key: &str, value: KclValue, source_range: SourceRange) -> Result<(), KclError> { + if self.environments[self.current_env.index()].contains_key(key) { + return Err(KclError::ValueAlreadyDefined(KclErrorDetails { + message: format!("Cannot redefine `{}`", key), + source_ranges: vec![source_range], + })); + } + + self.environments[self.current_env.index()].insert(key.to_string(), value); + + Ok(()) + } + + pub fn update_tag(&mut self, tag: &str, value: TagIdentifier) -> Result<(), KclError> { + self.environments[self.current_env.index()].insert(tag.to_string(), KclValue::TagIdentifier(Box::new(value))); + + Ok(()) + } + + /// Get a value from the program memory. + /// Return Err if not found. + pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&KclValue, KclError> { + let mut env_ref = self.current_env; + loop { + let env = &self.environments[env_ref.index()]; + if let Some(item) = env.bindings.get(var) { + return Ok(item); + } + if let Some(parent) = env.parent { + env_ref = parent; + } else { + break; + } + } + + Err(KclError::UndefinedValue(KclErrorDetails { + message: format!("memory item key `{}` is not defined", var), + source_ranges: vec![source_range], + })) + } + + /// Returns all bindings in the current scope. + #[allow(dead_code)] + fn get_all_cur_scope(&self) -> IndexMap { + let env = &self.environments[self.current_env.index()]; + env.bindings.clone() + } + + /// Find all solids in the memory that are on a specific sketch id. + /// This does not look inside closures. But as long as we do not allow + /// mutation of variables in KCL, closure memory should be a subset of this. + #[allow(clippy::vec_box)] + pub fn find_solids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec> { + self.environments + .iter() + .flat_map(|env| { + env.bindings + .values() + .filter_map(|item| match item { + KclValue::Solid { value } if value.sketch.id == sketch_id => Some(value.clone()), + _ => None, + }) + .collect::>() + }) + .collect() + } +} + +impl Default for ProgramMemory { + fn default() -> Self { + Self::new() + } +} + +/// An index pointing to an environment. +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[schemars(transparent)] +pub struct EnvironmentRef(usize); + +impl EnvironmentRef { + pub fn root() -> Self { + Self(0) + } + + pub fn index(&self) -> usize { + self.0 + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +pub struct Environment { + pub(super) bindings: IndexMap, + parent: Option, +} + +const NO_META: Vec = Vec::new(); + +impl Environment { + pub fn root() -> Self { + Self { + // Prelude + bindings: IndexMap::from([ + ("ZERO".to_string(), KclValue::from_number(0.0, NO_META)), + ("QUARTER_TURN".to_string(), KclValue::from_number(90.0, NO_META)), + ("HALF_TURN".to_string(), KclValue::from_number(180.0, NO_META)), + ("THREE_QUARTER_TURN".to_string(), KclValue::from_number(270.0, NO_META)), + ]), + parent: None, + } + } + + pub fn new(parent: EnvironmentRef) -> Self { + Self { + bindings: IndexMap::new(), + parent: Some(parent), + } + } + + pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&KclValue, KclError> { + self.bindings.get(key).ok_or_else(|| { + KclError::UndefinedValue(KclErrorDetails { + message: format!("memory item key `{}` is not defined", key), + source_ranges: vec![source_range], + }) + }) + } + + pub fn insert(&mut self, key: String, value: KclValue) { + self.bindings.insert(key, value); + } + + pub fn contains_key(&self, key: &str) -> bool { + self.bindings.contains_key(key) + } + + pub fn update_sketch_tags(&mut self, sg: &Sketch) { + if sg.tags.is_empty() { + return; + } + + for (_, val) in self.bindings.iter_mut() { + let KclValue::Sketch { value } = val else { continue }; + let mut sketch = value.to_owned(); + + if sketch.original_id == sg.original_id { + for tag in sg.tags.iter() { + sketch.tags.insert(tag.0.clone(), tag.1.clone()); + } + } + *val = KclValue::Sketch { value: sketch }; + } + } +} diff --git a/src/wasm-lib/kcl/src/execution/mod.rs b/src/wasm-lib/kcl/src/execution/mod.rs index b6352fdda4..4047215d4e 100644 --- a/src/wasm-lib/kcl/src/execution/mod.rs +++ b/src/wasm-lib/kcl/src/execution/mod.rs @@ -2,10 +2,7 @@ use std::{path::PathBuf, sync::Arc}; -use annotations::AnnotationScope; use anyhow::Result; -use artifact::build_artifact_graph; -use async_recursion::async_recursion; use indexmap::IndexMap; use kcmc::{ each_cmd as mcmd, @@ -13,38 +10,19 @@ use kcmc::{ websocket::{ModelingSessionData, OkWebSocketResponseData}, ImageFormat, ModelingCmd, }; -use kittycad_modeling_cmds::length_unit::LengthUnit; -use kittycad_modeling_cmds::{self as kcmc, websocket::WebSocketResponse}; -use parse_display::{Display, FromStr}; +use kittycad_modeling_cmds as kcmc; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -type Point2D = kcmc::shared::Point2d; -type Point3D = kcmc::shared::Point3d; - -pub use function_param::FunctionParam; -pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, ZOO_COORD_SYSTEM}; -pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen}; -use uuid::Uuid; - -mod annotations; -mod artifact; -pub(crate) mod cache; -mod cad_op; -mod exec_ast; -mod function_param; -mod import; -mod kcl_value; - use crate::{ - engine::{EngineManager, ExecutionKind}, - errors::{KclError, KclErrorDetails}, - execution::cache::{CacheInformation, CacheResult}, - fs::{FileManager, FileSystem}, - parsing::ast::types::{ - BodyItem, Expr, FunctionExpression, ImportPath, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue, - Program as AstProgram, TagDeclarator, TagNode, + engine::EngineManager, + errors::KclError, + execution::{ + artifact::build_artifact_graph, + cache::{CacheInformation, CacheResult}, }, + fs::FileManager, + parsing::ast::types::{Expr, FunctionExpression, Node, NodeRef, Program as AstProgram}, settings::types::UnitLength, source_range::{ModuleId, SourceRange}, std::{args::Arg, StdLib}, @@ -52,63 +30,25 @@ use crate::{ ExecError, KclErrorWithOutputs, Program, }; -// Re-exports. pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId}; pub use cad_op::Operation; +pub use exec_ast::FunctionParam; +pub use geometry::*; +pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, ZOO_COORD_SYSTEM}; +pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen}; +pub use memory::ProgramMemory; +pub use state::{ExecState, IdGenerator}; -/// State for executing a program. -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ExecState { - pub global: GlobalState, - pub mod_local: ModuleState, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct GlobalState { - /// The stable artifact ID generator. - pub id_generator: IdGenerator, - /// Map from source file absolute path to module ID. - pub path_to_source_id: IndexMap, - /// Map from module ID to module info. - pub module_infos: IndexMap, - /// Output map of UUIDs to artifacts. - pub artifacts: IndexMap, - /// Output commands to allow building the artifact graph by the caller. - /// These are accumulated in the [`ExecutorContext`] but moved here for - /// convenience of the execution cache. - pub artifact_commands: Vec, - /// Responses from the engine for `artifact_commands`. We need to cache - /// this so that we can build the artifact graph. These are accumulated in - /// the [`ExecutorContext`] but moved here for convenience of the execution - /// cache. - pub artifact_responses: IndexMap, - /// Output artifact graph. - pub artifact_graph: ArtifactGraph, -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct ModuleState { - /// Program variable bindings. - pub memory: ProgramMemory, - /// Dynamic state that follows dynamic flow of the program. - pub dynamic_state: DynamicState, - /// The current value of the pipe operator returned from the previous - /// expression. If we're not currently in a pipeline, this will be None. - pub pipe_value: Option, - /// Identifiers that have been exported from the current module. - pub module_exports: Vec, - /// The stack of import statements for detecting circular module imports. - /// If this is empty, we're not currently executing an import statement. - pub import_stack: Vec, - /// Operations that have been performed in execution order, for display in - /// the Feature Tree. - pub operations: Vec, - /// Settings specified from annotations. - pub settings: MetaSettings, -} +mod annotations; +mod artifact; +pub(crate) mod cache; +mod cad_op; +mod exec_ast; +mod geometry; +mod import; +mod kcl_value; +mod memory; +mod state; /// Outcome of executing a program. This is used in TS. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] @@ -128,741 +68,6 @@ pub struct ExecOutcome { pub artifact_graph: ArtifactGraph, } -impl ExecState { - pub fn new(exec_settings: &ExecutorSettings) -> Self { - ExecState { - global: GlobalState::new(exec_settings), - mod_local: ModuleState::new(exec_settings), - } - } - - fn reset(&mut self, exec_settings: &ExecutorSettings) { - let mut id_generator = self.global.id_generator.clone(); - // We do not pop the ids, since we want to keep the same id generator. - // This is for the front end to keep track of the ids. - id_generator.next_id = 0; - - let mut global = GlobalState::new(exec_settings); - global.id_generator = id_generator; - - *self = ExecState { - global, - mod_local: ModuleState::new(exec_settings), - }; - } - - /// Convert to execution outcome when running in WebAssembly. We want to - /// reduce the amount of data that crosses the WASM boundary as much as - /// possible. - pub fn to_wasm_outcome(self) -> ExecOutcome { - // Fields are opt-in so that we don't accidentally leak private internal - // state when we add more to ExecState. - ExecOutcome { - memory: self.mod_local.memory, - operations: self.mod_local.operations, - artifacts: self.global.artifacts, - artifact_commands: self.global.artifact_commands, - artifact_graph: self.global.artifact_graph, - } - } - - pub fn memory(&self) -> &ProgramMemory { - &self.mod_local.memory - } - - pub fn mut_memory(&mut self) -> &mut ProgramMemory { - &mut self.mod_local.memory - } - - pub fn next_uuid(&mut self) -> Uuid { - self.global.id_generator.next_uuid() - } - - pub fn add_artifact(&mut self, artifact: Artifact) { - let id = artifact.id(); - self.global.artifacts.insert(id, artifact); - } - - fn add_module(&mut self, id: ModuleId, path: std::path::PathBuf, repr: ModuleRepr) -> ModuleId { - debug_assert!(!self.global.path_to_source_id.contains_key(&path)); - - self.global.path_to_source_id.insert(path.clone(), id); - - let module_info = ModuleInfo { id, repr, path }; - self.global.module_infos.insert(id, module_info); - - id - } - - pub fn length_unit(&self) -> UnitLen { - self.mod_local.settings.default_length_units - } - - pub fn angle_unit(&self) -> UnitAngle { - self.mod_local.settings.default_angle_units - } -} - -impl GlobalState { - fn new(settings: &ExecutorSettings) -> Self { - let mut global = GlobalState { - id_generator: Default::default(), - path_to_source_id: Default::default(), - module_infos: Default::default(), - artifacts: Default::default(), - artifact_commands: Default::default(), - artifact_responses: Default::default(), - artifact_graph: Default::default(), - }; - - let root_id = ModuleId::default(); - let root_path = settings.current_file.clone().unwrap_or_default(); - global.module_infos.insert( - root_id, - ModuleInfo { - id: root_id, - path: root_path.clone(), - repr: ModuleRepr::Root, - }, - ); - global.path_to_source_id.insert(root_path, root_id); - global - } -} - -impl ModuleState { - fn new(exec_settings: &ExecutorSettings) -> Self { - ModuleState { - memory: Default::default(), - dynamic_state: Default::default(), - pipe_value: Default::default(), - module_exports: Default::default(), - import_stack: Default::default(), - operations: Default::default(), - settings: MetaSettings { - default_length_units: exec_settings.units.into(), - default_angle_units: Default::default(), - }, - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct MetaSettings { - pub default_length_units: kcl_value::UnitLen, - pub default_angle_units: kcl_value::UnitAngle, -} - -impl MetaSettings { - fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> { - let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?; - - for p in properties { - match &*p.inner.key.name { - annotations::SETTINGS_UNIT_LENGTH => { - let value = annotations::expect_ident(&p.inner.value)?; - let value = kcl_value::UnitLen::from_str(value, source_range)?; - self.default_length_units = value; - } - annotations::SETTINGS_UNIT_ANGLE => { - let value = annotations::expect_ident(&p.inner.value)?; - let value = kcl_value::UnitAngle::from_str(value, source_range)?; - self.default_angle_units = value; - } - name => { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( - "Unexpected settings key: `{name}`; expected one of `{}`, `{}`", - annotations::SETTINGS_UNIT_LENGTH, - annotations::SETTINGS_UNIT_ANGLE - ), - source_ranges: vec![source_range], - })) - } - } - } - - Ok(()) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct ProgramMemory { - pub environments: Vec, - pub current_env: EnvironmentRef, - #[serde(rename = "return")] - pub return_: Option, -} - -impl ProgramMemory { - pub fn new() -> Self { - Self { - environments: vec![Environment::root()], - current_env: EnvironmentRef::root(), - return_: None, - } - } - - pub fn new_env_for_call(&mut self, parent: EnvironmentRef) -> EnvironmentRef { - let new_env_ref = EnvironmentRef(self.environments.len()); - let new_env = Environment::new(parent); - self.environments.push(new_env); - new_env_ref - } - - /// Add to the program memory in the current scope. - pub fn add(&mut self, key: &str, value: KclValue, source_range: SourceRange) -> Result<(), KclError> { - if self.environments[self.current_env.index()].contains_key(key) { - return Err(KclError::ValueAlreadyDefined(KclErrorDetails { - message: format!("Cannot redefine `{}`", key), - source_ranges: vec![source_range], - })); - } - - self.environments[self.current_env.index()].insert(key.to_string(), value); - - Ok(()) - } - - pub fn update_tag(&mut self, tag: &str, value: TagIdentifier) -> Result<(), KclError> { - self.environments[self.current_env.index()].insert(tag.to_string(), KclValue::TagIdentifier(Box::new(value))); - - Ok(()) - } - - /// Get a value from the program memory. - /// Return Err if not found. - pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&KclValue, KclError> { - let mut env_ref = self.current_env; - loop { - let env = &self.environments[env_ref.index()]; - if let Some(item) = env.bindings.get(var) { - return Ok(item); - } - if let Some(parent) = env.parent { - env_ref = parent; - } else { - break; - } - } - - Err(KclError::UndefinedValue(KclErrorDetails { - message: format!("memory item key `{}` is not defined", var), - source_ranges: vec![source_range], - })) - } - - /// Returns all bindings in the current scope. - #[allow(dead_code)] - fn get_all_cur_scope(&self) -> IndexMap { - let env = &self.environments[self.current_env.index()]; - env.bindings.clone() - } - - /// Find all solids in the memory that are on a specific sketch id. - /// This does not look inside closures. But as long as we do not allow - /// mutation of variables in KCL, closure memory should be a subset of this. - #[allow(clippy::vec_box)] - pub fn find_solids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec> { - self.environments - .iter() - .flat_map(|env| { - env.bindings - .values() - .filter_map(|item| match item { - KclValue::Solid { value } if value.sketch.id == sketch_id => Some(value.clone()), - _ => None, - }) - .collect::>() - }) - .collect() - } -} - -impl Default for ProgramMemory { - fn default() -> Self { - Self::new() - } -} - -/// An index pointing to an environment. -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[schemars(transparent)] -pub struct EnvironmentRef(usize); - -impl EnvironmentRef { - pub fn root() -> Self { - Self(0) - } - - pub fn index(&self) -> usize { - self.0 - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -pub struct Environment { - bindings: IndexMap, - parent: Option, -} - -const NO_META: Vec = Vec::new(); - -impl Environment { - pub fn root() -> Self { - Self { - // Prelude - bindings: IndexMap::from([ - ("ZERO".to_string(), KclValue::from_number(0.0, NO_META)), - ("QUARTER_TURN".to_string(), KclValue::from_number(90.0, NO_META)), - ("HALF_TURN".to_string(), KclValue::from_number(180.0, NO_META)), - ("THREE_QUARTER_TURN".to_string(), KclValue::from_number(270.0, NO_META)), - ]), - parent: None, - } - } - - pub fn new(parent: EnvironmentRef) -> Self { - Self { - bindings: IndexMap::new(), - parent: Some(parent), - } - } - - pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&KclValue, KclError> { - self.bindings.get(key).ok_or_else(|| { - KclError::UndefinedValue(KclErrorDetails { - message: format!("memory item key `{}` is not defined", key), - source_ranges: vec![source_range], - }) - }) - } - - pub fn insert(&mut self, key: String, value: KclValue) { - self.bindings.insert(key, value); - } - - pub fn contains_key(&self, key: &str) -> bool { - self.bindings.contains_key(key) - } - - pub fn update_sketch_tags(&mut self, sg: &Sketch) { - if sg.tags.is_empty() { - return; - } - - for (_, val) in self.bindings.iter_mut() { - let KclValue::Sketch { value } = val else { continue }; - let mut sketch = value.to_owned(); - - if sketch.original_id == sg.original_id { - for tag in sg.tags.iter() { - sketch.tags.insert(tag.0.clone(), tag.1.clone()); - } - } - *val = KclValue::Sketch { value: sketch }; - } - } -} - -/// Dynamic state that depends on the dynamic flow of the program, like the call -/// stack. If the language had exceptions, for example, you could store the -/// stack of exception handlers here. -#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub struct DynamicState { - pub solid_ids: Vec, -} - -impl DynamicState { - #[must_use] - fn merge(&self, memory: &ProgramMemory) -> Self { - let mut merged = self.clone(); - merged.append(memory); - merged - } - - fn append(&mut self, memory: &ProgramMemory) { - for env in &memory.environments { - for item in env.bindings.values() { - if let KclValue::Solid { value } = item { - self.solid_ids.push(SolidLazyIds::from(value.as_ref())); - } - } - } - } - - pub(crate) fn edge_cut_ids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec { - self.solid_ids - .iter() - .flat_map(|eg| { - if eg.sketch_id == sketch_id { - eg.edge_cuts.clone() - } else { - Vec::new() - } - }) - .collect::>() - } -} - -/// A generator for ArtifactIds that can be stable across executions. -#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct IdGenerator { - next_id: usize, - ids: Vec, -} - -impl IdGenerator { - pub fn new() -> Self { - Self::default() - } - - pub fn next_uuid(&mut self) -> uuid::Uuid { - if let Some(id) = self.ids.get(self.next_id) { - self.next_id += 1; - *id - } else { - let id = uuid::Uuid::new_v4(); - self.ids.push(id); - self.next_id += 1; - id - } - } -} - -/// A geometry. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type")] -pub enum Geometry { - Sketch(Box), - Solid(Box), -} - -impl Geometry { - pub fn id(&self) -> uuid::Uuid { - match self { - Geometry::Sketch(s) => s.id, - Geometry::Solid(e) => e.id, - } - } -} - -/// A set of geometry. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type")] -#[allow(clippy::vec_box)] -pub enum Geometries { - Sketches(Vec>), - Solids(Vec>), -} - -impl From for Geometries { - fn from(value: Geometry) -> Self { - match value { - Geometry::Sketch(x) => Self::Sketches(vec![x]), - Geometry::Solid(x) => Self::Solids(vec![x]), - } - } -} - -/// A sketch or a group of sketches. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -#[allow(clippy::vec_box)] -pub enum SketchSet { - Sketch(Box), - Sketches(Vec>), -} - -impl SketchSet { - pub fn meta(&self) -> Vec { - match self { - SketchSet::Sketch(sg) => sg.meta.clone(), - SketchSet::Sketches(sg) => sg.iter().flat_map(|sg| sg.meta.clone()).collect(), - } - } -} - -impl From for Vec { - fn from(value: SketchSet) -> Self { - match value { - SketchSet::Sketch(sg) => vec![*sg], - SketchSet::Sketches(sgs) => sgs.into_iter().map(|sg| *sg).collect(), - } - } -} - -impl From for SketchSet { - fn from(sg: Sketch) -> Self { - SketchSet::Sketch(Box::new(sg)) - } -} - -impl From> for SketchSet { - fn from(sg: Box) -> Self { - SketchSet::Sketch(sg) - } -} - -impl From> for SketchSet { - fn from(sg: Vec) -> Self { - if sg.len() == 1 { - SketchSet::Sketch(Box::new(sg[0].clone())) - } else { - SketchSet::Sketches(sg.into_iter().map(Box::new).collect()) - } - } -} - -impl From>> for SketchSet { - fn from(sg: Vec>) -> Self { - if sg.len() == 1 { - SketchSet::Sketch(sg[0].clone()) - } else { - SketchSet::Sketches(sg) - } - } -} - -impl From for Vec> { - fn from(sg: SketchSet) -> Self { - match sg { - SketchSet::Sketch(sg) => vec![sg], - SketchSet::Sketches(sgs) => sgs, - } - } -} - -impl From<&Sketch> for Vec> { - fn from(sg: &Sketch) -> Self { - vec![Box::new(sg.clone())] - } -} - -impl From> for Vec> { - fn from(sg: Box) -> Self { - vec![sg] - } -} - -/// A solid or a group of solids. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -#[allow(clippy::vec_box)] -pub enum SolidSet { - Solid(Box), - Solids(Vec>), -} - -impl From for SolidSet { - fn from(eg: Solid) -> Self { - SolidSet::Solid(Box::new(eg)) - } -} - -impl From> for SolidSet { - fn from(eg: Box) -> Self { - SolidSet::Solid(eg) - } -} - -impl From> for SolidSet { - fn from(eg: Vec) -> Self { - if eg.len() == 1 { - SolidSet::Solid(Box::new(eg[0].clone())) - } else { - SolidSet::Solids(eg.into_iter().map(Box::new).collect()) - } - } -} - -impl From>> for SolidSet { - fn from(eg: Vec>) -> Self { - if eg.len() == 1 { - SolidSet::Solid(eg[0].clone()) - } else { - SolidSet::Solids(eg) - } - } -} - -impl From for Vec> { - fn from(eg: SolidSet) -> Self { - match eg { - SolidSet::Solid(eg) => vec![eg], - SolidSet::Solids(egs) => egs, - } - } -} - -impl From<&Solid> for Vec> { - fn from(eg: &Solid) -> Self { - vec![Box::new(eg.clone())] - } -} - -impl From> for Vec> { - fn from(eg: Box) -> Self { - vec![eg] - } -} - -/// Data for an imported geometry. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct ImportedGeometry { - /// The ID of the imported geometry. - pub id: uuid::Uuid, - /// The original file paths. - pub value: Vec, - #[serde(rename = "__meta")] - pub meta: Vec, -} - -/// A helix. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct Helix { - /// The id of the helix. - pub value: uuid::Uuid, - /// Number of revolutions. - pub revolutions: f64, - /// Start angle (in degrees). - pub angle_start: f64, - /// Is the helix rotation counter clockwise? - pub ccw: bool, - pub units: UnitLen, - #[serde(rename = "__meta")] - pub meta: Vec, -} - -/// A plane. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct Plane { - /// The id of the plane. - pub id: uuid::Uuid, - // The code for the plane either a string or custom. - pub value: PlaneType, - /// Origin of the plane. - pub origin: Point3d, - /// What should the plane’s X axis be? - pub x_axis: Point3d, - /// What should the plane’s Y axis be? - pub y_axis: Point3d, - /// The z-axis (normal). - pub z_axis: Point3d, - pub units: UnitLen, - #[serde(rename = "__meta")] - pub meta: Vec, -} - -impl Plane { - pub(crate) fn from_plane_data(value: crate::std::sketch::PlaneData, exec_state: &mut ExecState) -> Self { - let id = exec_state.global.id_generator.next_uuid(); - match value { - crate::std::sketch::PlaneData::XY => Plane { - id, - origin: Point3d::new(0.0, 0.0, 0.0), - x_axis: Point3d::new(1.0, 0.0, 0.0), - y_axis: Point3d::new(0.0, 1.0, 0.0), - z_axis: Point3d::new(0.0, 0.0, 1.0), - value: PlaneType::XY, - units: exec_state.length_unit(), - meta: vec![], - }, - crate::std::sketch::PlaneData::NegXY => Plane { - id, - origin: Point3d::new(0.0, 0.0, 0.0), - x_axis: Point3d::new(1.0, 0.0, 0.0), - y_axis: Point3d::new(0.0, 1.0, 0.0), - z_axis: Point3d::new(0.0, 0.0, -1.0), - value: PlaneType::XY, - units: exec_state.length_unit(), - meta: vec![], - }, - crate::std::sketch::PlaneData::XZ => Plane { - id, - origin: Point3d::new(0.0, 0.0, 0.0), - x_axis: Point3d::new(1.0, 0.0, 0.0), - y_axis: Point3d::new(0.0, 0.0, 1.0), - z_axis: Point3d::new(0.0, -1.0, 0.0), - value: PlaneType::XZ, - units: exec_state.length_unit(), - meta: vec![], - }, - crate::std::sketch::PlaneData::NegXZ => Plane { - id, - origin: Point3d::new(0.0, 0.0, 0.0), - x_axis: Point3d::new(-1.0, 0.0, 0.0), - y_axis: Point3d::new(0.0, 0.0, 1.0), - z_axis: Point3d::new(0.0, 1.0, 0.0), - value: PlaneType::XZ, - units: exec_state.length_unit(), - meta: vec![], - }, - crate::std::sketch::PlaneData::YZ => Plane { - id, - origin: Point3d::new(0.0, 0.0, 0.0), - x_axis: Point3d::new(0.0, 1.0, 0.0), - y_axis: Point3d::new(0.0, 0.0, 1.0), - z_axis: Point3d::new(1.0, 0.0, 0.0), - value: PlaneType::YZ, - units: exec_state.length_unit(), - meta: vec![], - }, - crate::std::sketch::PlaneData::NegYZ => Plane { - id, - origin: Point3d::new(0.0, 0.0, 0.0), - x_axis: Point3d::new(0.0, 1.0, 0.0), - y_axis: Point3d::new(0.0, 0.0, 1.0), - z_axis: Point3d::new(-1.0, 0.0, 0.0), - value: PlaneType::YZ, - units: exec_state.length_unit(), - meta: vec![], - }, - crate::std::sketch::PlaneData::Plane { - origin, - x_axis, - y_axis, - z_axis, - } => Plane { - id, - origin: *origin, - x_axis: *x_axis, - y_axis: *y_axis, - z_axis: *z_axis, - value: PlaneType::Custom, - units: exec_state.length_unit(), - meta: vec![], - }, - } - } - - /// The standard planes are XY, YZ and XZ (in both positive and negative) - pub fn is_standard(&self) -> bool { - !self.is_custom() - } - - /// The standard planes are XY, YZ and XZ (in both positive and negative) - /// Custom planes are any other plane that the user might specify. - pub fn is_custom(&self) -> bool { - matches!(self.value, PlaneType::Custom) - } -} - #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] #[serde(rename_all = "camelCase")] @@ -875,49 +80,6 @@ pub struct DefaultPlanes { pub neg_yz: uuid::Uuid, } -/// A face. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct Face { - /// The id of the face. - pub id: uuid::Uuid, - /// The tag of the face. - pub value: String, - /// What should the face’s X axis be? - pub x_axis: Point3d, - /// What should the face’s Y axis be? - pub y_axis: Point3d, - /// The z-axis (normal). - pub z_axis: Point3d, - /// The solid the face is on. - pub solid: Box, - pub units: UnitLen, - #[serde(rename = "__meta")] - pub meta: Vec, -} - -/// Type for a plane. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -#[display(style = "camelCase")] -pub enum PlaneType { - #[serde(rename = "XY", alias = "xy")] - #[display("XY")] - XY, - #[serde(rename = "XZ", alias = "xz")] - #[display("XZ")] - XZ, - #[serde(rename = "YZ", alias = "yz")] - #[display("YZ")] - YZ, - /// A custom plane. - #[serde(rename = "Custom")] - #[display("Custom")] - Custom, -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] #[serde(tag = "type", rename_all = "camelCase")] @@ -973,249 +135,22 @@ pub type MemoryFunction = expression: crate::parsing::ast::types::BoxNode, metadata: Vec, exec_state: &ExecState, - ctx: ExecutorContext, - ) -> std::pin::Pin, KclError>> + Send>>; - -/// Engine information for a tag. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -pub struct TagEngineInfo { - /// The id of the tagged object. - pub id: uuid::Uuid, - /// The sketch the tag is on. - pub sketch: uuid::Uuid, - /// The path the tag is on. - pub path: Option, - /// The surface information for the tag. - pub surface: Option, -} - -/// A sketch is a collection of paths. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -pub struct Sketch { - /// The id of the sketch (this will change when the engine's reference to it changes). - pub id: uuid::Uuid, - /// The paths in the sketch. - pub paths: Vec, - /// What the sketch is on (can be a plane or a face). - pub on: SketchSurface, - /// The starting path. - pub start: BasePath, - /// Tag identifiers that have been declared in this sketch. - #[serde(default, skip_serializing_if = "IndexMap::is_empty")] - pub tags: IndexMap, - /// The original id of the sketch. This stays the same even if the sketch is - /// is sketched on face etc. - #[serde(skip)] - pub original_id: uuid::Uuid, - pub units: UnitLen, - /// Metadata. - #[serde(rename = "__meta")] - pub meta: Vec, -} - -/// A sketch type. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -pub enum SketchSurface { - Plane(Box), - Face(Box), -} - -impl SketchSurface { - pub(crate) fn id(&self) -> uuid::Uuid { - match self { - SketchSurface::Plane(plane) => plane.id, - SketchSurface::Face(face) => face.id, - } - } - pub(crate) fn x_axis(&self) -> Point3d { - match self { - SketchSurface::Plane(plane) => plane.x_axis, - SketchSurface::Face(face) => face.x_axis, - } - } - pub(crate) fn y_axis(&self) -> Point3d { - match self { - SketchSurface::Plane(plane) => plane.y_axis, - SketchSurface::Face(face) => face.y_axis, - } - } - pub(crate) fn z_axis(&self) -> Point3d { - match self { - SketchSurface::Plane(plane) => plane.z_axis, - SketchSurface::Face(face) => face.z_axis, - } - } -} - -#[derive(Debug, Clone)] -pub(crate) enum GetTangentialInfoFromPathsResult { - PreviousPoint([f64; 2]), - Arc { center: [f64; 2], ccw: bool }, - Circle { center: [f64; 2], ccw: bool, radius: f64 }, -} - -impl GetTangentialInfoFromPathsResult { - pub(crate) fn tan_previous_point(&self, last_arc_end: crate::std::utils::Coords2d) -> [f64; 2] { - match self { - GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p, - GetTangentialInfoFromPathsResult::Arc { center, ccw, .. } => { - crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end) - } - // The circle always starts at 0 degrees, so a suitable tangent - // point is either directly above or below. - GetTangentialInfoFromPathsResult::Circle { - center, radius, ccw, .. - } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }], - } - } -} - -impl Sketch { - pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) { - let mut tag_identifier: TagIdentifier = tag.into(); - let base = current_path.get_base(); - tag_identifier.info = Some(TagEngineInfo { - id: base.geo_meta.id, - sketch: self.id, - path: Some(current_path.clone()), - surface: None, - }); - - self.tags.insert(tag.name.to_string(), tag_identifier); - } - - /// Get the path most recently sketched. - pub(crate) fn latest_path(&self) -> Option<&Path> { - self.paths.last() - } - - /// The "pen" is an imaginary pen drawing the path. - /// This gets the current point the pen is hovering over, i.e. the point - /// where the last path segment ends, and the next path segment will begin. - pub(crate) fn current_pen_position(&self) -> Result { - let Some(path) = self.latest_path() else { - return Ok(self.start.to.into()); - }; - - let base = path.get_base(); - Ok(base.to.into()) - } - - pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult { - let Some(path) = self.latest_path() else { - return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to); - }; - path.get_tangential_info() - } -} - -/// An solid is a collection of extrude surfaces. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -pub struct Solid { - /// The id of the solid. - pub id: uuid::Uuid, - /// The extrude surfaces. - pub value: Vec, - /// The sketch. - pub sketch: Sketch, - /// The height of the solid. - pub height: f64, - /// The id of the extrusion start cap - pub start_cap_id: Option, - /// The id of the extrusion end cap - pub end_cap_id: Option, - /// Chamfers or fillets on this solid. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub edge_cuts: Vec, - pub units: UnitLen, - /// Metadata. - #[serde(rename = "__meta")] - pub meta: Vec, -} - -impl Solid { - pub(crate) fn get_all_edge_cut_ids(&self) -> Vec { - self.edge_cuts.iter().map(|foc| foc.id()).collect() - } -} - -/// An solid ID and its fillet and chamfer IDs. This is needed for lazy -/// fillet evaluation. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub struct SolidLazyIds { - pub solid_id: uuid::Uuid, - pub sketch_id: uuid::Uuid, - /// Chamfers or fillets on this solid. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub edge_cuts: Vec, -} - -impl From<&Solid> for SolidLazyIds { - fn from(eg: &Solid) -> Self { - Self { - solid_id: eg.id, - sketch_id: eg.sketch.id, - edge_cuts: eg.edge_cuts.iter().map(|foc| foc.id()).collect(), - } - } -} - -/// A fillet or a chamfer. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -pub enum EdgeCut { - /// A fillet. - Fillet { - /// The id of the engine command that called this fillet. - id: uuid::Uuid, - radius: f64, - /// The engine id of the edge to fillet. - #[serde(rename = "edgeId")] - edge_id: uuid::Uuid, - tag: Box>, - }, - /// A chamfer. - Chamfer { - /// The id of the engine command that called this chamfer. - id: uuid::Uuid, - length: f64, - /// The engine id of the edge to chamfer. - #[serde(rename = "edgeId")] - edge_id: uuid::Uuid, - tag: Box>, - }, -} - -impl EdgeCut { - pub fn id(&self) -> uuid::Uuid { - match self { - EdgeCut::Fillet { id, .. } => *id, - EdgeCut::Chamfer { id, .. } => *id, - } - } - - pub fn edge_id(&self) -> uuid::Uuid { - match self { - EdgeCut::Fillet { edge_id, .. } => *edge_id, - EdgeCut::Chamfer { edge_id, .. } => *edge_id, - } - } + ctx: ExecutorContext, + ) -> std::pin::Pin, KclError>> + Send>>; - pub fn tag(&self) -> Option { - match self { - EdgeCut::Fillet { tag, .. } => *tag.clone(), - EdgeCut::Chamfer { tag, .. } => *tag.clone(), - } - } +/// Engine information for a tag. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TagEngineInfo { + /// The id of the tagged object. + pub id: uuid::Uuid, + /// The sketch the tag is on. + pub sketch: uuid::Uuid, + /// The path the tag is on. + pub path: Option, + /// The surface information for the tag. + pub surface: Option, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] @@ -1246,77 +181,6 @@ pub enum ModuleRepr { Foreign(import::PreImportedGeometry), } -#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)] -#[ts(export)] -pub struct Point2d { - pub x: f64, - pub y: f64, -} - -impl From<[f64; 2]> for Point2d { - fn from(p: [f64; 2]) -> Self { - Self { x: p[0], y: p[1] } - } -} - -impl From<&[f64; 2]> for Point2d { - fn from(p: &[f64; 2]) -> Self { - Self { x: p[0], y: p[1] } - } -} - -impl From for [f64; 2] { - fn from(p: Point2d) -> Self { - [p.x, p.y] - } -} - -impl From for Point2D { - fn from(p: Point2d) -> Self { - Self { x: p.x, y: p.y } - } -} - -impl Point2d { - pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; - pub fn scale(self, scalar: f64) -> Self { - Self { - x: self.x * scalar, - y: self.y * scalar, - } - } -} - -#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)] -#[ts(export)] -pub struct Point3d { - pub x: f64, - pub y: f64, - pub z: f64, -} - -impl Point3d { - pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 }; - pub fn new(x: f64, y: f64, z: f64) -> Self { - Self { x, y, z } - } -} - -impl From for Point3D { - fn from(p: Point3d) -> Self { - Self { x: p.x, y: p.y, z: p.z } - } -} -impl From for kittycad_modeling_cmds::shared::Point3d { - fn from(p: Point3d) -> Self { - Self { - x: LengthUnit(p.x), - y: LengthUnit(p.y), - z: LengthUnit(p.z), - } - } -} - /// Metadata. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq, Copy)] #[ts(export)] @@ -1354,357 +218,6 @@ impl From<&Expr> for Metadata { } } -/// A base path. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct BasePath { - /// The from point. - #[ts(type = "[number, number]")] - pub from: [f64; 2], - /// The to point. - #[ts(type = "[number, number]")] - pub to: [f64; 2], - /// The tag of the path. - pub tag: Option, - /// Metadata. - #[serde(rename = "__geoMeta")] - pub geo_meta: GeoMeta, -} - -/// Geometry metadata. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct GeoMeta { - /// The id of the geometry. - pub id: uuid::Uuid, - /// Metadata. - #[serde(flatten)] - pub metadata: Metadata, -} - -/// A path. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type")] -pub enum Path { - /// A path that goes to a point. - ToPoint { - #[serde(flatten)] - base: BasePath, - }, - /// A arc that is tangential to the last path segment that goes to a point - TangentialArcTo { - #[serde(flatten)] - base: BasePath, - /// the arc's center - #[ts(type = "[number, number]")] - center: [f64; 2], - /// arc's direction - ccw: bool, - }, - /// A arc that is tangential to the last path segment - TangentialArc { - #[serde(flatten)] - base: BasePath, - /// the arc's center - #[ts(type = "[number, number]")] - center: [f64; 2], - /// arc's direction - ccw: bool, - }, - // TODO: consolidate segment enums, remove Circle. https://github.com/KittyCAD/modeling-app/issues/3940 - /// a complete arc - Circle { - #[serde(flatten)] - base: BasePath, - /// the arc's center - #[ts(type = "[number, number]")] - center: [f64; 2], - /// the arc's radius - radius: f64, - /// arc's direction - /// This is used to compute the tangential angle. - ccw: bool, - }, - /// A path that is horizontal. - Horizontal { - #[serde(flatten)] - base: BasePath, - /// The x coordinate. - x: f64, - }, - /// An angled line to. - AngledLineTo { - #[serde(flatten)] - base: BasePath, - /// The x coordinate. - x: Option, - /// The y coordinate. - y: Option, - }, - /// A base path. - Base { - #[serde(flatten)] - base: BasePath, - }, - /// A circular arc, not necessarily tangential to the current point. - Arc { - #[serde(flatten)] - base: BasePath, - /// Center of the circle that this arc is drawn on. - center: [f64; 2], - /// Radius of the circle that this arc is drawn on. - radius: f64, - /// True if the arc is counterclockwise. - ccw: bool, - }, -} - -/// What kind of path is this? -#[derive(Display)] -enum PathType { - ToPoint, - Base, - TangentialArc, - TangentialArcTo, - Circle, - Horizontal, - AngledLineTo, - Arc, -} - -impl From<&Path> for PathType { - fn from(value: &Path) -> Self { - match value { - Path::ToPoint { .. } => Self::ToPoint, - Path::TangentialArcTo { .. } => Self::TangentialArcTo, - Path::TangentialArc { .. } => Self::TangentialArc, - Path::Circle { .. } => Self::Circle, - Path::Horizontal { .. } => Self::Horizontal, - Path::AngledLineTo { .. } => Self::AngledLineTo, - Path::Base { .. } => Self::Base, - Path::Arc { .. } => Self::Arc, - } - } -} - -impl Path { - pub fn get_id(&self) -> uuid::Uuid { - match self { - Path::ToPoint { base } => base.geo_meta.id, - Path::Horizontal { base, .. } => base.geo_meta.id, - Path::AngledLineTo { base, .. } => base.geo_meta.id, - Path::Base { base } => base.geo_meta.id, - Path::TangentialArcTo { base, .. } => base.geo_meta.id, - Path::TangentialArc { base, .. } => base.geo_meta.id, - Path::Circle { base, .. } => base.geo_meta.id, - Path::Arc { base, .. } => base.geo_meta.id, - } - } - - pub fn get_tag(&self) -> Option { - match self { - Path::ToPoint { base } => base.tag.clone(), - Path::Horizontal { base, .. } => base.tag.clone(), - Path::AngledLineTo { base, .. } => base.tag.clone(), - Path::Base { base } => base.tag.clone(), - Path::TangentialArcTo { base, .. } => base.tag.clone(), - Path::TangentialArc { base, .. } => base.tag.clone(), - Path::Circle { base, .. } => base.tag.clone(), - Path::Arc { base, .. } => base.tag.clone(), - } - } - - pub fn get_base(&self) -> &BasePath { - match self { - Path::ToPoint { base } => base, - Path::Horizontal { base, .. } => base, - Path::AngledLineTo { base, .. } => base, - Path::Base { base } => base, - Path::TangentialArcTo { base, .. } => base, - Path::TangentialArc { base, .. } => base, - Path::Circle { base, .. } => base, - Path::Arc { base, .. } => base, - } - } - - /// Where does this path segment start? - pub fn get_from(&self) -> &[f64; 2] { - &self.get_base().from - } - /// Where does this path segment end? - pub fn get_to(&self) -> &[f64; 2] { - &self.get_base().to - } - - /// Length of this path segment, in cartesian plane. - pub fn length(&self) -> f64 { - match self { - Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => { - linear_distance(self.get_from(), self.get_to()) - } - Self::TangentialArc { - base: _, - center, - ccw: _, - } - | Self::TangentialArcTo { - base: _, - center, - ccw: _, - } => { - // The radius can be calculated as the linear distance between `to` and `center`, - // or between `from` and `center`. They should be the same. - let radius = linear_distance(self.get_from(), center); - debug_assert_eq!(radius, linear_distance(self.get_to(), center)); - // TODO: Call engine utils to figure this out. - linear_distance(self.get_from(), self.get_to()) - } - Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius, - Self::Arc { .. } => { - // TODO: Call engine utils to figure this out. - linear_distance(self.get_from(), self.get_to()) - } - } - } - - pub fn get_base_mut(&mut self) -> Option<&mut BasePath> { - match self { - Path::ToPoint { base } => Some(base), - Path::Horizontal { base, .. } => Some(base), - Path::AngledLineTo { base, .. } => Some(base), - Path::Base { base } => Some(base), - Path::TangentialArcTo { base, .. } => Some(base), - Path::TangentialArc { base, .. } => Some(base), - Path::Circle { base, .. } => Some(base), - Path::Arc { base, .. } => Some(base), - } - } - - pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult { - match self { - Path::TangentialArc { center, ccw, .. } - | Path::TangentialArcTo { center, ccw, .. } - | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc { - center: *center, - ccw: *ccw, - }, - Path::Circle { - center, ccw, radius, .. - } => GetTangentialInfoFromPathsResult::Circle { - center: *center, - ccw: *ccw, - radius: *radius, - }, - Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => { - let base = self.get_base(); - GetTangentialInfoFromPathsResult::PreviousPoint(base.from) - } - } - } -} - -/// Compute the straight-line distance between a pair of (2D) points. -#[rustfmt::skip] -fn linear_distance( - [x0, y0]: &[f64; 2], - [x1, y1]: &[f64; 2] -) -> f64 { - let y_sq = (y1 - y0).powi(2); - let x_sq = (x1 - x0).powi(2); - (y_sq + x_sq).sqrt() -} - -/// An extrude surface. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -pub enum ExtrudeSurface { - /// An extrude plane. - ExtrudePlane(ExtrudePlane), - ExtrudeArc(ExtrudeArc), - Chamfer(ChamferSurface), - Fillet(FilletSurface), -} - -// Chamfer surface. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct ChamferSurface { - /// The id for the chamfer surface. - pub face_id: uuid::Uuid, - /// The tag. - pub tag: Option>, - /// Metadata. - #[serde(flatten)] - pub geo_meta: GeoMeta, -} - -// Fillet surface. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct FilletSurface { - /// The id for the fillet surface. - pub face_id: uuid::Uuid, - /// The tag. - pub tag: Option>, - /// Metadata. - #[serde(flatten)] - pub geo_meta: GeoMeta, -} - -/// An extruded plane. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct ExtrudePlane { - /// The face id for the extrude plane. - pub face_id: uuid::Uuid, - /// The tag. - pub tag: Option>, - /// Metadata. - #[serde(flatten)] - pub geo_meta: GeoMeta, -} - -/// An extruded arc. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(rename_all = "camelCase")] -pub struct ExtrudeArc { - /// The face id for the extrude plane. - pub face_id: uuid::Uuid, - /// The tag. - pub tag: Option>, - /// Metadata. - #[serde(flatten)] - pub geo_meta: GeoMeta, -} - -impl ExtrudeSurface { - pub fn get_id(&self) -> uuid::Uuid { - match self { - ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id, - ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id, - ExtrudeSurface::Fillet(f) => f.geo_meta.id, - ExtrudeSurface::Chamfer(c) => c.geo_meta.id, - } - } - - pub fn get_tag(&self) -> Option> { - match self { - ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(), - ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(), - ExtrudeSurface::Fillet(f) => f.tag.clone(), - ExtrudeSurface::Chamfer(c) => c.tag.clone(), - } - } -} - /// The type of ExecutorContext being used #[derive(PartialEq, Debug, Default, Clone)] pub enum ContextType { @@ -1830,71 +343,6 @@ impl ExecutorSettings { } } -/// Create a new zoo api client. -#[cfg(not(target_arch = "wasm32"))] -pub fn new_zoo_client(token: Option, engine_addr: Option) -> Result { - let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),); - let http_client = reqwest::Client::builder() - .user_agent(user_agent) - // For file conversions we need this to be long. - .timeout(std::time::Duration::from_secs(600)) - .connect_timeout(std::time::Duration::from_secs(60)); - let ws_client = reqwest::Client::builder() - .user_agent(user_agent) - // For file conversions we need this to be long. - .timeout(std::time::Duration::from_secs(600)) - .connect_timeout(std::time::Duration::from_secs(60)) - .connection_verbose(true) - .tcp_keepalive(std::time::Duration::from_secs(600)) - .http1_only(); - - let zoo_token_env = std::env::var("ZOO_API_TOKEN"); - - let token = if let Some(token) = token { - token - } else if let Ok(token) = std::env::var("KITTYCAD_API_TOKEN") { - if let Ok(zoo_token) = zoo_token_env { - if zoo_token != token { - return Err(anyhow::anyhow!( - "Both environment variables KITTYCAD_API_TOKEN=`{}` and ZOO_API_TOKEN=`{}` are set. Use only one.", - token, - zoo_token - )); - } - } - token - } else if let Ok(token) = zoo_token_env { - token - } else { - return Err(anyhow::anyhow!( - "No API token found in environment variables. Use KITTYCAD_API_TOKEN or ZOO_API_TOKEN" - )); - }; - - // Create the client. - let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client); - // Set an engine address if it's set. - let kittycad_host_env = std::env::var("KITTYCAD_HOST"); - if let Some(addr) = engine_addr { - client.set_base_url(addr); - } else if let Ok(addr) = std::env::var("ZOO_HOST") { - if let Ok(kittycad_host) = kittycad_host_env { - if kittycad_host != addr { - return Err(anyhow::anyhow!( - "Both environment variables KITTYCAD_HOST=`{}` and ZOO_HOST=`{}` are set. Use only one.", - kittycad_host, - addr - )); - } - } - client.set_base_url(addr); - } else if let Ok(addr) = kittycad_host_env { - client.set_base_url(addr); - } - - Ok(client) -} - impl ExecutorContext { /// Create a new default executor context. #[cfg(not(target_arch = "wasm32"))] @@ -2003,7 +451,7 @@ impl ExecutorContext { engine_addr: Option, ) -> Result { // Create the client. - let client = new_zoo_client(token, engine_addr)?; + let client = crate::engine::new_zoo_client(token, engine_addr)?; let ctx = Self::new(&client, settings).await?; Ok(ctx) @@ -2065,6 +513,7 @@ impl ExecutorContext { // AND if we aren't in wasm it doesn't really matter. Ok(()) } + /// Given an old ast, old program memory and new ast, find the parts of the code that need to be /// re-executed. /// This function should never error, because in the case of any internal error, we should just pop @@ -2129,14 +578,14 @@ impl ExecutorContext { } // Check if the changes were only to Non-code areas, like comments or whitespace. - Some(self.generate_changed_program(old_ast, new_ast)) + Some(Self::generate_changed_program(old_ast, new_ast)) } /// Force-generate a new CacheResult, even if one shouldn't be made. The /// way in which this gets invoked should always be through /// [Self::get_changed_program]. This is purely to contain the logic on /// how we construct a new [CacheResult]. - pub fn generate_changed_program(&self, old_ast: Node, new_ast: Node) -> CacheResult { + fn generate_changed_program(old_ast: Node, new_ast: Node) -> CacheResult { let mut generated_program = new_ast.clone(); generated_program.body = vec![]; @@ -2306,7 +755,7 @@ impl ExecutorContext { // Don't early return! We need to build other outputs regardless of // whether execution failed. let exec_result = self - .inner_execute(program, exec_state, crate::execution::BodyType::Root) + .exec_program(program, exec_state, crate::execution::BodyType::Root) .await; // Move the artifact commands and responses to simplify cache management // and error creation. @@ -2333,427 +782,6 @@ impl ExecutorContext { } } - async fn handle_annotations( - &self, - annotations: impl Iterator, - scope: AnnotationScope, - exec_state: &mut ExecState, - ) -> Result<(), KclError> { - for (annotation, source_range) in annotations { - if annotation.annotation_name() == Some(annotations::SETTINGS) { - if scope == AnnotationScope::Module { - let old_units = exec_state.length_unit(); - exec_state - .mod_local - .settings - .update_from_annotation(annotation, source_range)?; - let new_units = exec_state.length_unit(); - if old_units != new_units { - self.engine.set_units(new_units.into(), source_range).await?; - } - } else { - return Err(KclError::Semantic(KclErrorDetails { - message: "Settings can only be modified at the top level scope of a file".to_owned(), - source_ranges: vec![source_range], - })); - } - } - // TODO warn on unknown annotations - } - Ok(()) - } - - /// Execute an AST's program. - #[async_recursion] - pub(crate) async fn inner_execute<'a>( - &'a self, - program: NodeRef<'a, crate::parsing::ast::types::Program>, - exec_state: &mut ExecState, - body_type: BodyType, - ) -> Result, KclError> { - self.handle_annotations( - program - .non_code_meta - .start_nodes - .iter() - .filter_map(|n| n.annotation().map(|result| (result, n.as_source_range()))), - AnnotationScope::Module, - exec_state, - ) - .await?; - - let mut last_expr = None; - // Iterate over the body of the program. - for statement in &program.body { - match statement { - BodyItem::ImportStatement(import_stmt) => { - let source_range = SourceRange::from(import_stmt); - let module_id = self.open_module(&import_stmt.path, exec_state, source_range).await?; - - match &import_stmt.selector { - ImportSelector::List { items } => { - let (_, module_memory, module_exports) = self - .exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range) - .await?; - for import_item in items { - // Extract the item from the module. - let item = - module_memory - .get(&import_item.name.name, import_item.into()) - .map_err(|_err| { - KclError::UndefinedValue(KclErrorDetails { - message: format!("{} is not defined in module", import_item.name.name), - source_ranges: vec![SourceRange::from(&import_item.name)], - }) - })?; - // Check that the item is allowed to be imported. - if !module_exports.contains(&import_item.name.name) { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( - "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.", - import_item.name.name - ), - source_ranges: vec![SourceRange::from(&import_item.name)], - })); - } - - // Add the item to the current module. - exec_state.mut_memory().add( - import_item.identifier(), - item.clone(), - SourceRange::from(&import_item.name), - )?; - - if let ItemVisibility::Export = import_stmt.visibility { - exec_state - .mod_local - .module_exports - .push(import_item.identifier().to_owned()); - } - } - } - ImportSelector::Glob(_) => { - let (_, module_memory, module_exports) = self - .exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range) - .await?; - for name in module_exports.iter() { - let item = module_memory.get(name, source_range).map_err(|_err| { - KclError::Internal(KclErrorDetails { - message: format!("{} is not defined in module (but was exported?)", name), - source_ranges: vec![source_range], - }) - })?; - exec_state.mut_memory().add(name, item.clone(), source_range)?; - - if let ItemVisibility::Export = import_stmt.visibility { - exec_state.mod_local.module_exports.push(name.clone()); - } - } - } - ImportSelector::None { .. } => { - let name = import_stmt.module_name().unwrap(); - let item = KclValue::Module { - value: module_id, - meta: vec![source_range.into()], - }; - exec_state.mut_memory().add(&name, item, source_range)?; - } - } - last_expr = None; - } - BodyItem::ExpressionStatement(expression_statement) => { - let metadata = Metadata::from(expression_statement); - last_expr = Some( - self.execute_expr( - &expression_statement.expression, - exec_state, - &metadata, - StatementKind::Expression, - ) - .await?, - ); - } - BodyItem::VariableDeclaration(variable_declaration) => { - let var_name = variable_declaration.declaration.id.name.to_string(); - let source_range = SourceRange::from(&variable_declaration.declaration.init); - let metadata = Metadata { source_range }; - - let memory_item = self - .execute_expr( - &variable_declaration.declaration.init, - exec_state, - &metadata, - StatementKind::Declaration { name: &var_name }, - ) - .await?; - exec_state.mut_memory().add(&var_name, memory_item, source_range)?; - - // Track exports. - if let ItemVisibility::Export = variable_declaration.visibility { - exec_state.mod_local.module_exports.push(var_name); - } - last_expr = None; - } - BodyItem::ReturnStatement(return_statement) => { - let metadata = Metadata::from(return_statement); - let value = self - .execute_expr( - &return_statement.argument, - exec_state, - &metadata, - StatementKind::Expression, - ) - .await?; - exec_state.mut_memory().return_ = Some(value); - last_expr = None; - } - } - } - - if BodyType::Root == body_type { - // Flush the batch queue. - self.engine - .flush_batch( - // True here tells the engine to flush all the end commands as well like fillets - // and chamfers where the engine would otherwise eat the ID of the segments. - true, - SourceRange::new(program.end, program.end, program.module_id), - ) - .await?; - } - - Ok(last_expr) - } - - async fn open_module( - &self, - path: &ImportPath, - exec_state: &mut ExecState, - source_range: SourceRange, - ) -> Result { - match path { - ImportPath::Kcl { filename } => { - let resolved_path = if let Some(project_dir) = &self.settings.project_directory { - project_dir.join(filename) - } else { - std::path::PathBuf::from(filename) - }; - - if exec_state.mod_local.import_stack.contains(&resolved_path) { - return Err(KclError::ImportCycle(KclErrorDetails { - message: format!( - "circular import of modules is not allowed: {} -> {}", - exec_state - .mod_local - .import_stack - .iter() - .map(|p| p.as_path().to_string_lossy()) - .collect::>() - .join(" -> "), - resolved_path.to_string_lossy() - ), - source_ranges: vec![source_range], - })); - } - - if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) { - return Ok(*id); - } - - let source = self.fs.read_to_string(&resolved_path, source_range).await?; - let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len()); - // TODO handle parsing errors properly - let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?; - let repr = ModuleRepr::Kcl(parsed); - - Ok(exec_state.add_module(id, resolved_path, repr)) - } - ImportPath::Foreign { path } => { - let resolved_path = if let Some(project_dir) = &self.settings.project_directory { - project_dir.join(path) - } else { - std::path::PathBuf::from(path) - }; - - if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) { - return Ok(*id); - } - - let geom = import::import_foreign(&resolved_path, None, exec_state, self, source_range).await?; - let repr = ModuleRepr::Foreign(geom); - let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len()); - Ok(exec_state.add_module(id, resolved_path, repr)) - } - i => Err(KclError::Semantic(KclErrorDetails { - message: format!("Unsupported import: `{i}`"), - source_ranges: vec![source_range], - })), - } - } - - async fn exec_module( - &self, - module_id: ModuleId, - exec_state: &mut ExecState, - exec_kind: ExecutionKind, - source_range: SourceRange, - ) -> Result<(Option, ProgramMemory, Vec), KclError> { - let old_units = exec_state.length_unit(); - // TODO It sucks that we have to clone the whole module AST here - let info = exec_state.global.module_infos[&module_id].clone(); - - match &info.repr { - ModuleRepr::Root => Err(KclError::ImportCycle(KclErrorDetails { - message: format!( - "circular import of modules is not allowed: {} -> {}", - exec_state - .mod_local - .import_stack - .iter() - .map(|p| p.as_path().to_string_lossy()) - .collect::>() - .join(" -> "), - info.path.display() - ), - source_ranges: vec![source_range], - })), - ModuleRepr::Kcl(program) => { - let mut local_state = ModuleState { - import_stack: exec_state.mod_local.import_stack.clone(), - ..ModuleState::new(&self.settings) - }; - local_state.import_stack.push(info.path.clone()); - std::mem::swap(&mut exec_state.mod_local, &mut local_state); - let original_execution = self.engine.replace_execution_kind(exec_kind); - - let result = self - .inner_execute(program, exec_state, crate::execution::BodyType::Root) - .await; - - let new_units = exec_state.length_unit(); - std::mem::swap(&mut exec_state.mod_local, &mut local_state); - if new_units != old_units { - self.engine.set_units(old_units.into(), Default::default()).await?; - } - self.engine.replace_execution_kind(original_execution); - - let result = result.map_err(|err| { - if let KclError::ImportCycle(_) = err { - // It was an import cycle. Keep the original message. - err.override_source_ranges(vec![source_range]) - } else { - KclError::Semantic(KclErrorDetails { - message: format!( - "Error loading imported file. Open it to view more details. {}: {}", - info.path.display(), - err.message() - ), - source_ranges: vec![source_range], - }) - } - })?; - - Ok((result, local_state.memory, local_state.module_exports)) - } - ModuleRepr::Foreign(geom) => { - let geom = send_import_to_engine(geom.clone(), self).await?; - Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new())) - } - } - } - - #[async_recursion] - pub async fn execute_expr<'a: 'async_recursion>( - &self, - init: &Expr, - exec_state: &mut ExecState, - metadata: &Metadata, - statement_kind: StatementKind<'a>, - ) -> Result { - let item = match init { - Expr::None(none) => KclValue::from(none), - Expr::Literal(literal) => KclValue::from(literal), - Expr::TagDeclarator(tag) => tag.execute(exec_state).await?, - Expr::Identifier(identifier) => { - let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone(); - if let KclValue::Module { value: module_id, meta } = value { - let (result, _, _) = self - .exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range) - .await?; - result.unwrap_or_else(|| { - // The module didn't have a return value. Currently, - // the only way to have a return value is with the final - // statement being an expression statement. - // - // TODO: Make a warning when we support them in the - // execution phase. - let mut new_meta = vec![metadata.to_owned()]; - new_meta.extend(meta); - KclValue::KclNone { - value: Default::default(), - meta: new_meta, - } - }) - } else { - value - } - } - Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?, - Expr::FunctionExpression(function_expression) => { - // Cloning memory here is crucial for semantics so that we close - // over variables. Variables defined lexically later shouldn't - // be available to the function body. - KclValue::Function { - expression: function_expression.clone(), - meta: vec![metadata.to_owned()], - func: None, - memory: Box::new(exec_state.memory().clone()), - } - } - Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?, - Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?, - Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?, - Expr::PipeSubstitution(pipe_substitution) => match statement_kind { - StatementKind::Declaration { name } => { - let message = format!( - "you cannot declare variable {name} as %, because % can only be used in function calls" - ); - - return Err(KclError::Semantic(KclErrorDetails { - message, - source_ranges: vec![pipe_substitution.into()], - })); - } - StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() { - Some(x) => x, - None => { - return Err(KclError::Semantic(KclErrorDetails { - message: "cannot use % outside a pipe expression".to_owned(), - source_ranges: vec![pipe_substitution.into()], - })); - } - }, - }, - Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?, - Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?, - Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?, - Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?, - Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?, - Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?, - Expr::LabelledExpression(expr) => { - let result = self - .execute_expr(&expr.expr, exec_state, metadata, statement_kind) - .await?; - exec_state - .mut_memory() - .add(&expr.label.name, result.clone(), init.into())?; - // TODO this lets us use the label as a variable name, but not as a tag in most cases - result - } - }; - Ok(item) - } - /// Update the units for the executor. pub fn update_units(&mut self, units: UnitLength) { self.settings.units = units; @@ -2815,176 +843,6 @@ impl ExecutorContext { } } -/// For each argument given, -/// assign it to a parameter of the function, in the given block of function memory. -/// Returns Err if too few/too many arguments were given for the function. -fn assign_args_to_params( - function_expression: NodeRef<'_, FunctionExpression>, - args: Vec, - mut fn_memory: ProgramMemory, -) -> Result { - let num_args = function_expression.number_of_args(); - let (min_params, max_params) = num_args.into_inner(); - let n = args.len(); - - // Check if the user supplied too many arguments - // (we'll check for too few arguments below). - let err_wrong_number_args = KclError::Semantic(KclErrorDetails { - message: if min_params == max_params { - format!("Expected {min_params} arguments, got {n}") - } else { - format!("Expected {min_params}-{max_params} arguments, got {n}") - }, - source_ranges: vec![function_expression.into()], - }); - if n > max_params { - return Err(err_wrong_number_args); - } - - // Add the arguments to the memory. A new call frame should have already - // been created. - for (index, param) in function_expression.params.iter().enumerate() { - if let Some(arg) = args.get(index) { - // Argument was provided. - fn_memory.add(¶m.identifier.name, arg.value.clone(), (¶m.identifier).into())?; - } else { - // Argument was not provided. - if let Some(ref default_val) = param.default_value { - // If the corresponding parameter is optional, - // then it's fine, the user doesn't need to supply it. - fn_memory.add( - ¶m.identifier.name, - default_val.clone().into(), - (¶m.identifier).into(), - )?; - } else { - // But if the corresponding parameter was required, - // then the user has called with too few arguments. - return Err(err_wrong_number_args); - } - } - } - Ok(fn_memory) -} - -fn assign_args_to_params_kw( - function_expression: NodeRef<'_, FunctionExpression>, - mut args: crate::std::args::KwArgs, - mut fn_memory: ProgramMemory, -) -> Result { - // Add the arguments to the memory. A new call frame should have already - // been created. - let source_ranges = vec![function_expression.into()]; - for param in function_expression.params.iter() { - if param.labeled { - let arg = args.labeled.get(¶m.identifier.name); - let arg_val = match arg { - Some(arg) => arg.value.clone(), - None => match param.default_value { - Some(ref default_val) => KclValue::from(default_val.clone()), - None => { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges, - message: format!( - "This function requires a parameter {}, but you haven't passed it one.", - param.identifier.name - ), - })); - } - }, - }; - fn_memory.add(¶m.identifier.name, arg_val, (¶m.identifier).into())?; - } else { - let Some(unlabeled) = args.unlabeled.take() else { - let param_name = ¶m.identifier.name; - return Err(if args.labeled.contains_key(param_name) { - KclError::Semantic(KclErrorDetails { - source_ranges, - message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"), - }) - } else { - KclError::Semantic(KclErrorDetails { - source_ranges, - message: "This function expects an unlabeled first parameter, but you haven't passed it one." - .to_owned(), - }) - }); - }; - fn_memory.add( - ¶m.identifier.name, - unlabeled.value.clone(), - (¶m.identifier).into(), - )?; - } - } - Ok(fn_memory) -} - -pub(crate) async fn call_user_defined_function( - args: Vec, - memory: &ProgramMemory, - function_expression: NodeRef<'_, FunctionExpression>, - exec_state: &mut ExecState, - ctx: &ExecutorContext, -) -> Result, KclError> { - // Create a new environment to execute the function body in so that local - // variables shadow variables in the parent scope. The new environment's - // parent should be the environment of the closure. - let mut body_memory = memory.clone(); - let body_env = body_memory.new_env_for_call(memory.current_env); - body_memory.current_env = body_env; - let fn_memory = assign_args_to_params(function_expression, args, body_memory)?; - - // Execute the function body using the memory we just created. - let (result, fn_memory) = { - let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory); - let result = ctx - .inner_execute(&function_expression.body, exec_state, BodyType::Block) - .await; - // Restore the previous memory. - let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory); - - (result, fn_memory) - }; - - result.map(|_| fn_memory.return_) -} - -pub(crate) async fn call_user_defined_function_kw( - args: crate::std::args::KwArgs, - memory: &ProgramMemory, - function_expression: NodeRef<'_, FunctionExpression>, - exec_state: &mut ExecState, - ctx: &ExecutorContext, -) -> Result, KclError> { - // Create a new environment to execute the function body in so that local - // variables shadow variables in the parent scope. The new environment's - // parent should be the environment of the closure. - let mut body_memory = memory.clone(); - let body_env = body_memory.new_env_for_call(memory.current_env); - body_memory.current_env = body_env; - let fn_memory = assign_args_to_params_kw(function_expression, args, body_memory)?; - - // Execute the function body using the memory we just created. - let (result, fn_memory) = { - let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory); - let result = ctx - .inner_execute(&function_expression.body, exec_state, BodyType::Block) - .await; - // Restore the previous memory. - let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory); - - (result, fn_memory) - }; - - result.map(|_| fn_memory.return_) -} - -pub enum StatementKind<'a> { - Declaration { name: &'a str }, - Expression, -} - #[cfg(test)] mod tests { use std::sync::Arc; diff --git a/src/wasm-lib/kcl/src/execution/state.rs b/src/wasm-lib/kcl/src/execution/state.rs new file mode 100644 index 0000000000..725fc0fe4f --- /dev/null +++ b/src/wasm-lib/kcl/src/execution/state.rs @@ -0,0 +1,300 @@ +use anyhow::Result; +use indexmap::IndexMap; +use kittycad_modeling_cmds::websocket::WebSocketResponse; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::{ + errors::{KclError, KclErrorDetails}, + execution::{ + annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, + ExecutorSettings, KclValue, ModuleInfo, ModuleRepr, Operation, ProgramMemory, SolidLazyIds, UnitAngle, UnitLen, + }, + parsing::ast::types::NonCodeValue, + source_range::{ModuleId, SourceRange}, +}; + +/// State for executing a program. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ExecState { + pub global: GlobalState, + pub mod_local: ModuleState, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GlobalState { + /// The stable artifact ID generator. + pub id_generator: IdGenerator, + /// Map from source file absolute path to module ID. + pub path_to_source_id: IndexMap, + /// Map from module ID to module info. + pub module_infos: IndexMap, + /// Output map of UUIDs to artifacts. + pub artifacts: IndexMap, + /// Output commands to allow building the artifact graph by the caller. + /// These are accumulated in the [`ExecutorContext`] but moved here for + /// convenience of the execution cache. + pub artifact_commands: Vec, + /// Responses from the engine for `artifact_commands`. We need to cache + /// this so that we can build the artifact graph. These are accumulated in + /// the [`ExecutorContext`] but moved here for convenience of the execution + /// cache. + pub artifact_responses: IndexMap, + /// Output artifact graph. + pub artifact_graph: ArtifactGraph, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ModuleState { + /// Program variable bindings. + pub memory: ProgramMemory, + /// Dynamic state that follows dynamic flow of the program. + pub dynamic_state: DynamicState, + /// The current value of the pipe operator returned from the previous + /// expression. If we're not currently in a pipeline, this will be None. + pub pipe_value: Option, + /// Identifiers that have been exported from the current module. + pub module_exports: Vec, + /// The stack of import statements for detecting circular module imports. + /// If this is empty, we're not currently executing an import statement. + pub import_stack: Vec, + /// Operations that have been performed in execution order, for display in + /// the Feature Tree. + pub operations: Vec, + /// Settings specified from annotations. + pub settings: MetaSettings, +} + +impl ExecState { + pub fn new(exec_settings: &ExecutorSettings) -> Self { + ExecState { + global: GlobalState::new(exec_settings), + mod_local: ModuleState::new(exec_settings), + } + } + + pub(super) fn reset(&mut self, exec_settings: &ExecutorSettings) { + let mut id_generator = self.global.id_generator.clone(); + // We do not pop the ids, since we want to keep the same id generator. + // This is for the front end to keep track of the ids. + id_generator.next_id = 0; + + let mut global = GlobalState::new(exec_settings); + global.id_generator = id_generator; + + *self = ExecState { + global, + mod_local: ModuleState::new(exec_settings), + }; + } + + /// Convert to execution outcome when running in WebAssembly. We want to + /// reduce the amount of data that crosses the WASM boundary as much as + /// possible. + pub fn to_wasm_outcome(self) -> ExecOutcome { + // Fields are opt-in so that we don't accidentally leak private internal + // state when we add more to ExecState. + ExecOutcome { + memory: self.mod_local.memory, + operations: self.mod_local.operations, + artifacts: self.global.artifacts, + artifact_commands: self.global.artifact_commands, + artifact_graph: self.global.artifact_graph, + } + } + + pub fn memory(&self) -> &ProgramMemory { + &self.mod_local.memory + } + + pub fn mut_memory(&mut self) -> &mut ProgramMemory { + &mut self.mod_local.memory + } + + pub fn next_uuid(&mut self) -> Uuid { + self.global.id_generator.next_uuid() + } + + pub fn add_artifact(&mut self, artifact: Artifact) { + let id = artifact.id(); + self.global.artifacts.insert(id, artifact); + } + + pub(super) fn add_module(&mut self, id: ModuleId, path: std::path::PathBuf, repr: ModuleRepr) -> ModuleId { + debug_assert!(!self.global.path_to_source_id.contains_key(&path)); + + self.global.path_to_source_id.insert(path.clone(), id); + + let module_info = ModuleInfo { id, repr, path }; + self.global.module_infos.insert(id, module_info); + + id + } + + pub fn length_unit(&self) -> UnitLen { + self.mod_local.settings.default_length_units + } + + pub fn angle_unit(&self) -> UnitAngle { + self.mod_local.settings.default_angle_units + } +} + +impl GlobalState { + fn new(settings: &ExecutorSettings) -> Self { + let mut global = GlobalState { + id_generator: Default::default(), + path_to_source_id: Default::default(), + module_infos: Default::default(), + artifacts: Default::default(), + artifact_commands: Default::default(), + artifact_responses: Default::default(), + artifact_graph: Default::default(), + }; + + let root_id = ModuleId::default(); + let root_path = settings.current_file.clone().unwrap_or_default(); + global.module_infos.insert( + root_id, + ModuleInfo { + id: root_id, + path: root_path.clone(), + repr: ModuleRepr::Root, + }, + ); + global.path_to_source_id.insert(root_path, root_id); + global + } +} + +impl ModuleState { + pub(super) fn new(exec_settings: &ExecutorSettings) -> Self { + ModuleState { + memory: Default::default(), + dynamic_state: Default::default(), + pipe_value: Default::default(), + module_exports: Default::default(), + import_stack: Default::default(), + operations: Default::default(), + settings: MetaSettings { + default_length_units: exec_settings.units.into(), + default_angle_units: Default::default(), + }, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct MetaSettings { + pub default_length_units: kcl_value::UnitLen, + pub default_angle_units: kcl_value::UnitAngle, +} + +impl MetaSettings { + pub(super) fn update_from_annotation( + &mut self, + annotation: &NonCodeValue, + source_range: SourceRange, + ) -> Result<(), KclError> { + let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?; + + for p in properties { + match &*p.inner.key.name { + annotations::SETTINGS_UNIT_LENGTH => { + let value = annotations::expect_ident(&p.inner.value)?; + let value = kcl_value::UnitLen::from_str(value, source_range)?; + self.default_length_units = value; + } + annotations::SETTINGS_UNIT_ANGLE => { + let value = annotations::expect_ident(&p.inner.value)?; + let value = kcl_value::UnitAngle::from_str(value, source_range)?; + self.default_angle_units = value; + } + name => { + return Err(KclError::Semantic(KclErrorDetails { + message: format!( + "Unexpected settings key: `{name}`; expected one of `{}`, `{}`", + annotations::SETTINGS_UNIT_LENGTH, + annotations::SETTINGS_UNIT_ANGLE + ), + source_ranges: vec![source_range], + })) + } + } + } + + Ok(()) + } +} + +/// Dynamic state that depends on the dynamic flow of the program, like the call +/// stack. If the language had exceptions, for example, you could store the +/// stack of exception handlers here. +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct DynamicState { + pub solid_ids: Vec, +} + +impl DynamicState { + #[must_use] + pub(super) fn merge(&self, memory: &ProgramMemory) -> Self { + let mut merged = self.clone(); + merged.append(memory); + merged + } + + fn append(&mut self, memory: &ProgramMemory) { + for env in &memory.environments { + for item in env.bindings.values() { + if let KclValue::Solid { value } = item { + self.solid_ids.push(SolidLazyIds::from(value.as_ref())); + } + } + } + } + + pub(crate) fn edge_cut_ids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec { + self.solid_ids + .iter() + .flat_map(|eg| { + if eg.sketch_id == sketch_id { + eg.edge_cuts.clone() + } else { + Vec::new() + } + }) + .collect::>() + } +} + +/// A generator for ArtifactIds that can be stable across executions. +#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdGenerator { + pub(super) next_id: usize, + ids: Vec, +} + +impl IdGenerator { + pub fn new() -> Self { + Self::default() + } + + pub fn next_uuid(&mut self) -> uuid::Uuid { + if let Some(id) = self.ids.get(self.next_id) { + self.next_id += 1; + *id + } else { + let id = uuid::Uuid::new_v4(); + self.ids.push(id); + self.next_id += 1; + id + } + } +} diff --git a/src/wasm-lib/kcl/src/fs/wasm.rs b/src/wasm-lib/kcl/src/fs/wasm.rs index 4530b98523..b8b16853ed 100644 --- a/src/wasm-lib/kcl/src/fs/wasm.rs +++ b/src/wasm-lib/kcl/src/fs/wasm.rs @@ -183,6 +183,6 @@ impl FileSystem for FileManager { }) })?; - Ok(files.into_iter().map(|s| std::path::PathBuf::from(s)).collect()) + Ok(files.into_iter().map(std::path::PathBuf::from).collect()) } } diff --git a/src/wasm-lib/kcl/src/test_server.rs b/src/wasm-lib/kcl/src/test_server.rs index 733933ed02..6a21464d46 100644 --- a/src/wasm-lib/kcl/src/test_server.rs +++ b/src/wasm-lib/kcl/src/test_server.rs @@ -3,8 +3,9 @@ use std::path::PathBuf; use crate::{ + engine::new_zoo_client, errors::ExecErrorWithState, - execution::{new_zoo_client, ExecutorContext, ExecutorSettings}, + execution::{ExecutorContext, ExecutorSettings}, settings::types::UnitLength, ConnectionError, ExecError, ExecState, KclErrorWithOutputs, Program, }; diff --git a/src/wasm-lib/kcl/src/wasm/mod.rs b/src/wasm-lib/kcl/src/wasm/mod.rs index f24e54b0e6..bf35c635e2 100644 --- a/src/wasm-lib/kcl/src/wasm/mod.rs +++ b/src/wasm-lib/kcl/src/wasm/mod.rs @@ -1,4 +1,4 @@ -///! Web assembly utils. +//! Web assembly utils. use std::{ pin::Pin, task::{Context, Poll}, diff --git a/src/wasm-lib/src/wasm.rs b/src/wasm-lib/src/wasm.rs index bd4a0cf760..4923cb4cc2 100644 --- a/src/wasm-lib/src/wasm.rs +++ b/src/wasm-lib/src/wasm.rs @@ -375,11 +375,7 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String, baseurl: Strin let mut zoo_client = kittycad::Client::new(token); zoo_client.set_base_url(baseurl.as_str()); - let dev_mode = if baseurl == "https://api.dev.zoo.dev" { - true - } else { - false - }; + let dev_mode = baseurl == "https://api.dev.zoo.dev"; let (service, socket) = LspService::build(|client| kcl_lib::CopilotLspBackend::new_wasm(client, fs, zoo_client, dev_mode)) @@ -514,7 +510,7 @@ pub fn default_app_settings() -> Result { pub fn parse_app_settings(toml_str: &str) -> Result { console_error_panic_hook::set_once(); - let settings = kcl_lib::Configuration::backwards_compatible_toml_parse(&toml_str).map_err(|e| e.to_string())?; + let settings = kcl_lib::Configuration::backwards_compatible_toml_parse(toml_str).map_err(|e| e.to_string())?; // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the // gloo-serialize crate instead. @@ -539,7 +535,7 @@ pub fn parse_project_settings(toml_str: &str) -> Result { console_error_panic_hook::set_once(); let settings = - kcl_lib::ProjectConfiguration::backwards_compatible_toml_parse(&toml_str).map_err(|e| e.to_string())?; + kcl_lib::ProjectConfiguration::backwards_compatible_toml_parse(toml_str).map_err(|e| e.to_string())?; // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the // gloo-serialize crate instead. From 28c0d630eefd3bba569f8ba459a0d3ac9dba8ad8 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Tue, 28 Jan 2025 08:39:04 +1300 Subject: [PATCH 3/4] Refactor caching out of ExecutorContext plus some tidying up Signed-off-by: Nick Cameron --- src/wasm-lib/.cargo/config.toml | 2 - src/wasm-lib/kcl-test-server/src/lib.rs | 10 +- src/wasm-lib/kcl/src/execution/cache.rs | 476 +++++++++++- src/wasm-lib/kcl/src/execution/exec_ast.rs | 136 ++++ src/wasm-lib/kcl/src/execution/mod.rs | 731 ++---------------- src/wasm-lib/kcl/src/lsp/test_util.rs | 2 +- .../kcl/src/parsing/token/tokeniser.rs | 8 +- src/wasm-lib/kcl/src/test_server.rs | 2 +- src/wasm-lib/tests/executor/cache.rs | 20 +- 9 files changed, 709 insertions(+), 678 deletions(-) delete mode 100644 src/wasm-lib/.cargo/config.toml diff --git a/src/wasm-lib/.cargo/config.toml b/src/wasm-lib/.cargo/config.toml deleted file mode 100644 index f4e8c002fc..0000000000 --- a/src/wasm-lib/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -target = "wasm32-unknown-unknown" diff --git a/src/wasm-lib/kcl-test-server/src/lib.rs b/src/wasm-lib/kcl-test-server/src/lib.rs index f5c1e6e94f..665ca39bce 100644 --- a/src/wasm-lib/kcl-test-server/src/lib.rs +++ b/src/wasm-lib/kcl-test-server/src/lib.rs @@ -151,7 +151,7 @@ async fn handle_request(req: hyper::Request, state3: Arc) -> /// KCL errors (from engine or the executor) respond with HTTP Bad Gateway. /// Malformed requests are HTTP Bad Request. /// Successful requests contain a PNG as the body. -async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response { +async fn snapshot_endpoint(body: Bytes, ctxt: ExecutorContext) -> Response { let body = match serde_json::from_slice::(body.as_ref()) { Ok(bd) => bd, Err(e) => return bad_request(format!("Invalid request JSON: {e}")), @@ -164,11 +164,11 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response Response(); let timer = time_until(done_rx); - let snapshot = match state.execute_and_prepare_snapshot(&program, &mut exec_state).await { + let snapshot = match ctxt.execute_and_prepare_snapshot(program.into(), &mut exec_state).await { Ok(sn) => sn, Err(e) => return kcl_err(e), }; diff --git a/src/wasm-lib/kcl/src/execution/cache.rs b/src/wasm-lib/kcl/src/execution/cache.rs index f2ff3cd202..427d47c7a7 100644 --- a/src/wasm-lib/kcl/src/execution/cache.rs +++ b/src/wasm-lib/kcl/src/execution/cache.rs @@ -5,8 +5,11 @@ use serde::{Deserialize, Serialize}; use crate::{ execution::ExecState, parsing::ast::types::{Node, Program}, + walk::Node as WalkNode, }; +use super::ExecutorSettings; + /// Information for the caching an AST and smartly re-executing it if we can. #[derive(Debug, Clone, Deserialize, Serialize)] pub struct CacheInformation { @@ -38,9 +41,472 @@ impl From for CacheInformation { /// The result of a cache check. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] -pub struct CacheResult { - /// Should we clear the scene and start over? - pub clear_scene: bool, - /// The program that needs to be executed. - pub program: Node, +#[allow(clippy::large_enum_variant)] +pub(super) enum CacheResult { + ReExecute { + /// Should we clear the scene and start over? + clear_scene: bool, + /// The program that needs to be executed. + program: Node, + }, + ReapplySettings, + NoAction, +} + +/// Given an old ast, old program memory and new ast, find the parts of the code that need to be +/// re-executed. +/// This function should never error, because in the case of any internal error, we should just pop +/// the cache. +/// +/// Returns `None` when there are no changes to the program, i.e. it is +/// fully cached. +pub(super) async fn get_changed_program(info: CacheInformation, settings: &ExecutorSettings) -> CacheResult { + let Some(old) = info.old else { + // We have no old info, we need to re-execute the whole thing. + return CacheResult::ReExecute { + clear_scene: true, + program: info.new_ast, + }; + }; + + // If the settings are different we might need to bust the cache. + // We specifically do this before checking if they are the exact same. + if &old.settings != settings { + // If the units are different we need to re-execute the whole thing. + if old.settings.units != settings.units { + return CacheResult::ReExecute { + clear_scene: true, + program: info.new_ast, + }; + } + + // If anything else is different we do not need to re-execute, but rather just + // run the settings again. + return CacheResult::ReapplySettings; + } + + // If the ASTs are the EXACT same we return None. + // We don't even need to waste time computing the digests. + if old.ast == info.new_ast { + return CacheResult::NoAction; + } + + let mut old_ast = old.ast; + let mut new_ast = info.new_ast; + + // The digests should already be computed, but just in case we don't + // want to compare against none. + old_ast.compute_digest(); + new_ast.compute_digest(); + + // Check if the digest is the same. + if old_ast.digest == new_ast.digest { + return CacheResult::NoAction; + } + + // Check if the changes were only to Non-code areas, like comments or whitespace. + generate_changed_program(old_ast, new_ast) +} + +/// Force-generate a new CacheResult, even if one shouldn't be made. The +/// way in which this gets invoked should always be through +/// [get_changed_program]. This is purely to contain the logic on +/// how we construct a new [CacheResult]. +fn generate_changed_program(old_ast: Node, new_ast: Node) -> CacheResult { + let mut generated_program = new_ast.clone(); + generated_program.body = vec![]; + + if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| { + let old_node: WalkNode = old.into(); + let new_node: WalkNode = new.into(); + old_node.digest() == new_node.digest() + }) { + // If any of the nodes are different in the stretch of body that + // overlaps, we have to bust cache and rebuild the scene. This + // means a single insertion or deletion will result in a cache + // bust. + + return CacheResult::ReExecute { + clear_scene: true, + program: new_ast, + }; + } + + // otherwise the overlapping section of the ast bodies matches. + // Let's see what the rest of the slice looks like. + + match new_ast.body.len().cmp(&old_ast.body.len()) { + std::cmp::Ordering::Less => { + // the new AST is shorter than the old AST -- statements + // were removed from the "current" code in the "new" code. + // + // Statements up until now match which means this is a + // "pure delete" of the remaining slice, when we get to + // supporting that. + + // Cache bust time. + CacheResult::ReExecute { + clear_scene: true, + program: new_ast, + } + } + std::cmp::Ordering::Greater => { + // the new AST is longer than the old AST, which means + // statements were added to the new code we haven't previously + // seen. + // + // Statements up until now are the same, which means this + // is a "pure addition" of the remaining slice. + + generated_program + .body + .extend_from_slice(&new_ast.body[old_ast.body.len()..]); + + CacheResult::ReExecute { + clear_scene: false, + program: generated_program, + } + } + std::cmp::Ordering::Equal => { + // currently unreachable, but let's pretend like the code + // above can do something meaningful here for when we get + // to diffing and yanking chunks of the program apart. + + // We don't actually want to do anything here; so we're going + // to not clear and do nothing. Is this wrong? I don't think + // so but i think many things. This def needs to change + // when the code above changes. + + CacheResult::ReExecute { + clear_scene: false, + program: generated_program, + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::execution::parse_execute; + + use super::*; + + // Easy case where we have no old ast and memory. + // We need to re-execute everything. + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_no_old_information() { + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + let (program, ctx, _) = parse_execute(new).await.unwrap(); + + let result = get_changed_program( + CacheInformation { + old: None, + new_ast: program.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!( + result, + CacheResult::ReExecute { + clear_scene: true, + program: program.ast + } + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_same_code() { + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + + let (program, ctx, exec_state) = parse_execute(new).await.unwrap(); + + let result = get_changed_program( + CacheInformation { + old: Some(OldAstState { + ast: program.ast.clone(), + exec_state, + settings: Default::default(), + }), + new_ast: program.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!(result, CacheResult::NoAction); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_same_code_changed_whitespace() { + let old = r#" // Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#; + + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + + let (program_old, ctx, exec_state) = parse_execute(old).await.unwrap(); + + let program_new = crate::Program::parse_no_errs(new).unwrap(); + + let result = get_changed_program( + CacheInformation { + old: Some(OldAstState { + ast: program_old.ast.clone(), + exec_state, + settings: Default::default(), + }), + new_ast: program_new.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!(result, CacheResult::NoAction); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_same_code_changed_code_comment_start_of_program() { + let old = r#" // Removed the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#; + + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + + let (program, ctx, exec_state) = parse_execute(old).await.unwrap(); + + let program_new = crate::Program::parse_no_errs(new).unwrap(); + + let result = get_changed_program( + CacheInformation { + old: Some(OldAstState { + ast: program.ast.clone(), + exec_state, + settings: Default::default(), + }), + new_ast: program_new.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!(result, CacheResult::NoAction); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_same_code_changed_code_comments() { + let old = r#" // Removed the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) // my thing + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#; + + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + + let (program, ctx, exec_state) = parse_execute(old).await.unwrap(); + + let program_new = crate::Program::parse_no_errs(new).unwrap(); + + let result = get_changed_program( + CacheInformation { + old: Some(OldAstState { + ast: program.ast.clone(), + exec_state, + settings: Default::default(), + }), + new_ast: program_new.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!(result, CacheResult::NoAction); + } + + // Changing the units with the exact same file should bust the cache. + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_same_code_but_different_units() { + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + + let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); + + // Change the settings to cm. + ctx.settings.units = crate::UnitLength::Cm; + + let result = get_changed_program( + CacheInformation { + old: Some(OldAstState { + ast: program.ast.clone(), + exec_state, + settings: Default::default(), + }), + new_ast: program.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!( + result, + CacheResult::ReExecute { + clear_scene: true, + program: program.ast + } + ); + } + + // Changing the grid settings with the exact same file should NOT bust the cache. + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_same_code_but_different_grid_setting() { + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + + let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); + + // Change the settings. + ctx.settings.show_grid = !ctx.settings.show_grid; + + let result = get_changed_program( + CacheInformation { + old: Some(OldAstState { + ast: program.ast.clone(), + exec_state, + settings: Default::default(), + }), + new_ast: program.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!(result, CacheResult::ReapplySettings); + } + + // Changing the edge visibility settings with the exact same file should NOT bust the cache. + #[tokio::test(flavor = "multi_thread")] + async fn test_get_changed_program_same_code_but_different_edge_visiblity_setting() { + let new = r#"// Remove the end face for the extrusion. +firstSketch = startSketchOn('XY') + |> startProfileAt([-12, 12], %) + |> line([24, 0], %) + |> line([0, -24], %) + |> line([-24, 0], %) + |> close(%) + |> extrude(6, %) + +// Remove the end face for the extrusion. +shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + + let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); + + // Change the settings. + ctx.settings.highlight_edges = !ctx.settings.highlight_edges; + + let result = get_changed_program( + CacheInformation { + old: Some(OldAstState { + ast: program.ast.clone(), + exec_state, + settings: Default::default(), + }), + new_ast: program.ast.clone(), + }, + &ctx.settings, + ) + .await; + + assert_eq!(result, CacheResult::ReapplySettings); + } } diff --git a/src/wasm-lib/kcl/src/execution/exec_ast.rs b/src/wasm-lib/kcl/src/execution/exec_ast.rs index 9b61a29722..01e4910d67 100644 --- a/src/wasm-lib/kcl/src/execution/exec_ast.rs +++ b/src/wasm-lib/kcl/src/execution/exec_ast.rs @@ -1634,3 +1634,139 @@ impl JsonSchema for FunctionParam<'_> { gen.subschema_for::<()>() } } + +#[cfg(test)] +mod test { + use crate::parsing::ast::types::{DefaultParamVal, Identifier, Parameter}; + + use super::*; + + #[test] + fn test_assign_args_to_params() { + // Set up a little framework for this test. + fn mem(number: usize) -> KclValue { + KclValue::Int { + value: number as i64, + meta: Default::default(), + } + } + fn ident(s: &'static str) -> Node { + Node::no_src(Identifier { + name: s.to_owned(), + digest: None, + }) + } + fn opt_param(s: &'static str) -> Parameter { + Parameter { + identifier: ident(s), + type_: None, + default_value: Some(DefaultParamVal::none()), + labeled: true, + digest: None, + } + } + fn req_param(s: &'static str) -> Parameter { + Parameter { + identifier: ident(s), + type_: None, + default_value: None, + labeled: true, + digest: None, + } + } + fn additional_program_memory(items: &[(String, KclValue)]) -> ProgramMemory { + let mut program_memory = ProgramMemory::new(); + for (name, item) in items { + program_memory + .add(name.as_str(), item.clone(), SourceRange::default()) + .unwrap(); + } + program_memory + } + // Declare the test cases. + for (test_name, params, args, expected) in [ + ("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())), + ( + "all params required, and all given, should be OK", + vec![req_param("x")], + vec![mem(1)], + Ok(additional_program_memory(&[("x".to_owned(), mem(1))])), + ), + ( + "all params required, none given, should error", + vec![req_param("x")], + vec![], + Err(KclError::Semantic(KclErrorDetails { + source_ranges: vec![SourceRange::default()], + message: "Expected 1 arguments, got 0".to_owned(), + })), + ), + ( + "all params optional, none given, should be OK", + vec![opt_param("x")], + vec![], + Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])), + ), + ( + "mixed params, too few given", + vec![req_param("x"), opt_param("y")], + vec![], + Err(KclError::Semantic(KclErrorDetails { + source_ranges: vec![SourceRange::default()], + message: "Expected 1-2 arguments, got 0".to_owned(), + })), + ), + ( + "mixed params, minimum given, should be OK", + vec![req_param("x"), opt_param("y")], + vec![mem(1)], + Ok(additional_program_memory(&[ + ("x".to_owned(), mem(1)), + ("y".to_owned(), KclValue::none()), + ])), + ), + ( + "mixed params, maximum given, should be OK", + vec![req_param("x"), opt_param("y")], + vec![mem(1), mem(2)], + Ok(additional_program_memory(&[ + ("x".to_owned(), mem(1)), + ("y".to_owned(), mem(2)), + ])), + ), + ( + "mixed params, too many given", + vec![req_param("x"), opt_param("y")], + vec![mem(1), mem(2), mem(3)], + Err(KclError::Semantic(KclErrorDetails { + source_ranges: vec![SourceRange::default()], + message: "Expected 1-2 arguments, got 3".to_owned(), + })), + ), + ] { + // Run each test. + let func_expr = &Node::no_src(FunctionExpression { + params, + body: Node { + inner: crate::parsing::ast::types::Program { + body: Vec::new(), + non_code_meta: Default::default(), + shebang: None, + digest: None, + }, + start: 0, + end: 0, + module_id: ModuleId::default(), + }, + return_type: None, + digest: None, + }); + let args = args.into_iter().map(Arg::synthetic).collect(); + let actual = assign_args_to_params(func_expr, args, ProgramMemory::new()); + assert_eq!( + actual, expected, + "failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}" + ); + } + } +} diff --git a/src/wasm-lib/kcl/src/execution/mod.rs b/src/wasm-lib/kcl/src/execution/mod.rs index 4047215d4e..57e664468a 100644 --- a/src/wasm-lib/kcl/src/execution/mod.rs +++ b/src/wasm-lib/kcl/src/execution/mod.rs @@ -22,19 +22,18 @@ use crate::{ cache::{CacheInformation, CacheResult}, }, fs::FileManager, - parsing::ast::types::{Expr, FunctionExpression, Node, NodeRef, Program as AstProgram}, + parsing::ast::types::{Expr, FunctionExpression, Node, NodeRef, Program}, settings::types::UnitLength, source_range::{ModuleId, SourceRange}, std::{args::Arg, StdLib}, - walk::Node as WalkNode, - ExecError, KclErrorWithOutputs, Program, + ExecError, KclErrorWithOutputs, }; pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId}; pub use cad_op::Operation; pub use exec_ast::FunctionParam; pub use geometry::*; -pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, ZOO_COORD_SYSTEM}; +pub(crate) use import::{import_foreign, ZOO_COORD_SYSTEM}; pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen}; pub use memory::ProgramMemory; pub use state::{ExecState, IdGenerator}; @@ -177,7 +176,7 @@ pub struct ModuleInfo { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum ModuleRepr { Root, - Kcl(Node), + Kcl(Node), Foreign(import::PreImportedGeometry), } @@ -378,19 +377,6 @@ impl ExecutorContext { }) } - #[cfg(not(target_arch = "wasm32"))] - pub async fn new_mock() -> Self { - ExecutorContext { - engine: Arc::new(Box::new( - crate::engine::conn_mock::EngineConnection::new().await.unwrap(), - )), - fs: Arc::new(FileManager::new()), - stdlib: Arc::new(StdLib::new()), - settings: Default::default(), - context_type: ContextType::Mock, - } - } - #[cfg(target_arch = "wasm32")] pub async fn new( engine_manager: crate::engine::conn_wasm::EngineCommandManager, @@ -410,6 +396,19 @@ impl ExecutorContext { }) } + #[cfg(not(target_arch = "wasm32"))] + pub async fn new_mock() -> Self { + ExecutorContext { + engine: Arc::new(Box::new( + crate::engine::conn_mock::EngineConnection::new().await.unwrap(), + )), + fs: Arc::new(FileManager::new()), + stdlib: Arc::new(StdLib::new()), + settings: Default::default(), + context_type: ContextType::Mock, + } + } + #[cfg(target_arch = "wasm32")] pub async fn new_mock( fs_manager: crate::fs::wasm::FileSystemManager, @@ -476,10 +475,6 @@ impl ExecutorContext { Ok(ctx) } - pub fn is_mock(&self) -> bool { - self.context_type == ContextType::Mock || self.context_type == ContextType::MockCustomForwarded - } - /// For executing unit tests. #[cfg(not(target_arch = "wasm32"))] pub async fn new_for_unit_test(units: UnitLength, engine_addr: Option) -> Result { @@ -500,7 +495,11 @@ impl ExecutorContext { Ok(ctx) } - pub async fn reset_scene( + pub fn is_mock(&self) -> bool { + self.context_type == ContextType::Mock || self.context_type == ContextType::MockCustomForwarded + } + + pub async fn send_clear_scene( &self, exec_state: &mut ExecState, source_range: crate::execution::SourceRange, @@ -514,159 +513,27 @@ impl ExecutorContext { Ok(()) } - /// Given an old ast, old program memory and new ast, find the parts of the code that need to be - /// re-executed. - /// This function should never error, because in the case of any internal error, we should just pop - /// the cache. - /// - /// Returns `None` when there are no changes to the program, i.e. it is - /// fully cached. - pub async fn get_changed_program(&self, info: CacheInformation) -> Option { - let Some(old) = info.old else { - // We have no old info, we need to re-execute the whole thing. - return Some(CacheResult { - clear_scene: true, - program: info.new_ast, - }); - }; - - // If the settings are different we might need to bust the cache. - // We specifically do this before checking if they are the exact same. - if old.settings != self.settings { - // If the units are different we need to re-execute the whole thing. - if old.settings.units != self.settings.units { - return Some(CacheResult { - clear_scene: true, - program: info.new_ast, - }); - } - - // If anything else is different we do not need to re-execute, but rather just - // run the settings again. - - if self - .engine - .reapply_settings(&self.settings, Default::default()) - .await - .is_err() - { - // Bust the cache, we errored. - return Some(CacheResult { - clear_scene: true, - program: info.new_ast, - }); - } - } - - // If the ASTs are the EXACT same we return None. - // We don't even need to waste time computing the digests. - if old.ast == info.new_ast { - return None; - } - - let mut old_ast = old.ast; - let mut new_ast = info.new_ast; - - // The digests should already be computed, but just in case we don't - // want to compare against none. - old_ast.compute_digest(); - new_ast.compute_digest(); - - // Check if the digest is the same. - if old_ast.digest == new_ast.digest { - return None; - } - - // Check if the changes were only to Non-code areas, like comments or whitespace. - Some(Self::generate_changed_program(old_ast, new_ast)) - } - - /// Force-generate a new CacheResult, even if one shouldn't be made. The - /// way in which this gets invoked should always be through - /// [Self::get_changed_program]. This is purely to contain the logic on - /// how we construct a new [CacheResult]. - fn generate_changed_program(old_ast: Node, new_ast: Node) -> CacheResult { - let mut generated_program = new_ast.clone(); - generated_program.body = vec![]; - - if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| { - let old_node: WalkNode = old.into(); - let new_node: WalkNode = new.into(); - old_node.digest() == new_node.digest() - }) { - // If any of the nodes are different in the stretch of body that - // overlaps, we have to bust cache and rebuild the scene. This - // means a single insertion or deletion will result in a cache - // bust. - - return CacheResult { - clear_scene: true, - program: new_ast, - }; - } - - // otherwise the overlapping section of the ast bodies matches. - // Let's see what the rest of the slice looks like. - - match new_ast.body.len().cmp(&old_ast.body.len()) { - std::cmp::Ordering::Less => { - // the new AST is shorter than the old AST -- statements - // were removed from the "current" code in the "new" code. - // - // Statements up until now match which means this is a - // "pure delete" of the remaining slice, when we get to - // supporting that. - - // Cache bust time. - CacheResult { - clear_scene: true, - program: new_ast, - } - } - std::cmp::Ordering::Greater => { - // the new AST is longer than the old AST, which means - // statements were added to the new code we haven't previously - // seen. - // - // Statements up until now are the same, which means this - // is a "pure addition" of the remaining slice. - - generated_program - .body - .extend_from_slice(&new_ast.body[old_ast.body.len()..]); - - CacheResult { - clear_scene: false, - program: generated_program, - } - } - std::cmp::Ordering::Equal => { - // currently unreachable, but let's pretend like the code - // above can do something meaningful here for when we get - // to diffing and yanking chunks of the program apart. - - // We don't actually want to do anything here; so we're going - // to not clear and do nothing. Is this wrong? I don't think - // so but i think many things. This def needs to change - // when the code above changes. - - CacheResult { - clear_scene: false, - program: generated_program, - } - } - } - } - /// Perform the execution of a program. /// /// You can optionally pass in some initialization memory for partial /// execution. pub async fn run(&self, cache_info: CacheInformation, exec_state: &mut ExecState) -> Result<(), KclError> { - self.run_with_session_data(cache_info, exec_state).await?; + self.inner_run(cache_info, exec_state).await?; Ok(()) } + // TODO program.clone().into() + /// Execute the program, then get a PNG screenshot. + pub async fn execute_and_prepare_snapshot( + &self, + cache_info: CacheInformation, + exec_state: &mut ExecState, + ) -> std::result::Result { + self.inner_run(cache_info, exec_state).await?; + + self.prepare_snapshot().await + } + /// Perform the execution of a program. /// /// You can optionally pass in some initialization memory for partial @@ -706,20 +573,29 @@ impl ExecutorContext { let _stats = crate::log::LogPerfStats::new("Interpretation"); // Get the program that actually changed from the old and new information. - let cache_result = self.get_changed_program(cache_info.clone()).await; - - // Check if we don't need to re-execute. - let Some(cache_result) = cache_result else { - return Ok(None); + let (clear_scene, program) = match cache::get_changed_program(cache_info.clone(), &self.settings).await { + CacheResult::ReExecute { clear_scene, program } => (clear_scene, program), + CacheResult::ReapplySettings => { + if self + .engine + .reapply_settings(&self.settings, Default::default()) + .await + .is_ok() + { + return Ok(None); + } + (true, cache_info.new_ast) + } + CacheResult::NoAction => return Ok(None), }; - if cache_result.clear_scene && !self.is_mock() { + if clear_scene && !self.is_mock() { // Pop the execution state, since we are starting fresh. exec_state.reset(&self.settings); // We don't do this in mock mode since there is no engine connection // anyways and from the TS side we override memory and don't want to clear it. - self.reset_scene(exec_state, Default::default()) + self.send_clear_scene(exec_state, Default::default()) .await .map_err(KclErrorWithOutputs::no_outputs)?; } @@ -730,16 +606,14 @@ impl ExecutorContext { .await .map_err(KclErrorWithOutputs::no_outputs)?; - self.execute_and_build_graph(&cache_result.program, exec_state) - .await - .map_err(|e| { - KclErrorWithOutputs::new( - e, - exec_state.mod_local.operations.clone(), - exec_state.global.artifact_commands.clone(), - exec_state.global.artifact_graph.clone(), - ) - })?; + self.execute_and_build_graph(&program, exec_state).await.map_err(|e| { + KclErrorWithOutputs::new( + e, + exec_state.mod_local.operations.clone(), + exec_state.global.artifact_commands.clone(), + exec_state.global.artifact_graph.clone(), + ) + })?; let session_data = self.engine.get_session_data(); Ok(session_data) @@ -783,12 +657,12 @@ impl ExecutorContext { } /// Update the units for the executor. - pub fn update_units(&mut self, units: UnitLength) { + pub(crate) fn update_units(&mut self, units: UnitLength) { self.settings.units = units; } /// Get a snapshot of the current scene. - pub async fn prepare_snapshot(&self) -> std::result::Result { + async fn prepare_snapshot(&self) -> std::result::Result { // Zoom to fit. self.engine .send_modeling_cmd( @@ -827,49 +701,34 @@ impl ExecutorContext { Ok(contents) } - /// Execute the program, then get a PNG screenshot. - pub async fn execute_and_prepare_snapshot( - &self, - program: &Program, - exec_state: &mut ExecState, - ) -> std::result::Result { - self.run_with_ui_outputs(program.clone().into(), exec_state).await?; - - self.prepare_snapshot().await - } - pub async fn close(&self) { self.engine.close().await; } } #[cfg(test)] -mod tests { - use std::sync::Arc; - - use pretty_assertions::assert_eq; - - use super::*; - use crate::{ - parsing::ast::types::{DefaultParamVal, Identifier, Node, Parameter}, - OldAstState, +async fn parse_execute(code: &str) -> Result<(crate::Program, ExecutorContext, ExecState)> { + let program = crate::Program::parse_no_errs(code)?; + + let ctx = ExecutorContext { + engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)), + fs: Arc::new(crate::fs::FileManager::new()), + stdlib: Arc::new(crate::std::StdLib::new()), + settings: Default::default(), + context_type: ContextType::Mock, }; + let mut exec_state = ExecState::new(&ctx.settings); + ctx.run(program.clone().into(), &mut exec_state).await?; - async fn parse_execute(code: &str) -> Result<(Program, ExecutorContext, ExecState)> { - let program = Program::parse_no_errs(code)?; + Ok((program, ctx, exec_state)) +} - let ctx = ExecutorContext { - engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)), - fs: Arc::new(crate::fs::FileManager::new()), - stdlib: Arc::new(crate::std::StdLib::new()), - settings: Default::default(), - context_type: ContextType::Mock, - }; - let mut exec_state = ExecState::new(&ctx.settings); - ctx.run(program.clone().into(), &mut exec_state).await?; +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; - Ok((program, ctx, exec_state)) - } + use super::*; + use crate::errors::KclErrorDetails; /// Convenience function to get a JSON value from memory and unwrap. #[track_caller] @@ -1649,135 +1508,6 @@ let w = f() + f() parse_execute(ast).await.unwrap(); } - #[test] - fn test_assign_args_to_params() { - // Set up a little framework for this test. - fn mem(number: usize) -> KclValue { - KclValue::Int { - value: number as i64, - meta: Default::default(), - } - } - fn ident(s: &'static str) -> Node { - Node::no_src(Identifier { - name: s.to_owned(), - digest: None, - }) - } - fn opt_param(s: &'static str) -> Parameter { - Parameter { - identifier: ident(s), - type_: None, - default_value: Some(DefaultParamVal::none()), - labeled: true, - digest: None, - } - } - fn req_param(s: &'static str) -> Parameter { - Parameter { - identifier: ident(s), - type_: None, - default_value: None, - labeled: true, - digest: None, - } - } - fn additional_program_memory(items: &[(String, KclValue)]) -> ProgramMemory { - let mut program_memory = ProgramMemory::new(); - for (name, item) in items { - program_memory - .add(name.as_str(), item.clone(), SourceRange::default()) - .unwrap(); - } - program_memory - } - // Declare the test cases. - for (test_name, params, args, expected) in [ - ("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())), - ( - "all params required, and all given, should be OK", - vec![req_param("x")], - vec![mem(1)], - Ok(additional_program_memory(&[("x".to_owned(), mem(1))])), - ), - ( - "all params required, none given, should error", - vec![req_param("x")], - vec![], - Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![SourceRange::default()], - message: "Expected 1 arguments, got 0".to_owned(), - })), - ), - ( - "all params optional, none given, should be OK", - vec![opt_param("x")], - vec![], - Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])), - ), - ( - "mixed params, too few given", - vec![req_param("x"), opt_param("y")], - vec![], - Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![SourceRange::default()], - message: "Expected 1-2 arguments, got 0".to_owned(), - })), - ), - ( - "mixed params, minimum given, should be OK", - vec![req_param("x"), opt_param("y")], - vec![mem(1)], - Ok(additional_program_memory(&[ - ("x".to_owned(), mem(1)), - ("y".to_owned(), KclValue::none()), - ])), - ), - ( - "mixed params, maximum given, should be OK", - vec![req_param("x"), opt_param("y")], - vec![mem(1), mem(2)], - Ok(additional_program_memory(&[ - ("x".to_owned(), mem(1)), - ("y".to_owned(), mem(2)), - ])), - ), - ( - "mixed params, too many given", - vec![req_param("x"), opt_param("y")], - vec![mem(1), mem(2), mem(3)], - Err(KclError::Semantic(KclErrorDetails { - source_ranges: vec![SourceRange::default()], - message: "Expected 1-2 arguments, got 3".to_owned(), - })), - ), - ] { - // Run each test. - let func_expr = &Node::no_src(FunctionExpression { - params, - body: Node { - inner: crate::parsing::ast::types::Program { - body: Vec::new(), - non_code_meta: Default::default(), - shebang: None, - digest: None, - }, - start: 0, - end: 0, - module_id: ModuleId::default(), - }, - return_type: None, - digest: None, - }); - let args = args.into_iter().map(Arg::synthetic).collect(); - let actual = assign_args_to_params(func_expr, args, ProgramMemory::new()); - assert_eq!( - actual, expected, - "failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}" - ); - } - } - #[test] fn test_serialize_memory_item() { let mem = KclValue::Solids { @@ -1787,307 +1517,6 @@ let w = f() + f() assert_eq!(json, r#"{"type":"Solids","value":[]}"#); } - // Easy case where we have no old ast and memory. - // We need to re-execute everything. - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_no_old_information() { - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - let (program, ctx, _) = parse_execute(new).await.unwrap(); - - let result = ctx - .get_changed_program(CacheInformation { - old: None, - new_ast: program.ast.clone(), - }) - .await; - - assert!(result.is_some()); - - let result = result.unwrap(); - - assert_eq!(result.program, program.ast); - assert!(result.clear_scene); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code() { - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program, ctx, exec_state) = parse_execute(new).await.unwrap(); - - let result = ctx - .get_changed_program(CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), - }) - .await; - - assert_eq!(result, None); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code_changed_whitespace() { - let old = r#" // Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#; - - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program_old, ctx, exec_state) = parse_execute(old).await.unwrap(); - - let program_new = crate::Program::parse_no_errs(new).unwrap(); - - let result = ctx - .get_changed_program(CacheInformation { - old: Some(OldAstState { - ast: program_old.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program_new.ast.clone(), - }) - .await; - - assert_eq!(result, None); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code_changed_code_comment_start_of_program() { - let old = r#" // Removed the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#; - - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program, ctx, exec_state) = parse_execute(old).await.unwrap(); - - let program_new = crate::Program::parse_no_errs(new).unwrap(); - - let result = ctx - .get_changed_program(CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program_new.ast.clone(), - }) - .await; - - assert_eq!(result, None); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code_changed_code_comments() { - let old = r#" // Removed the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) // my thing - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#; - - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program, ctx, exec_state) = parse_execute(old).await.unwrap(); - - let program_new = crate::Program::parse_no_errs(new).unwrap(); - - let result = ctx - .get_changed_program(CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program_new.ast.clone(), - }) - .await; - - assert!(result.is_none()); - } - - // Changing the units with the exact same file should bust the cache. - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code_but_different_units() { - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); - - // Change the settings to cm. - ctx.settings.units = crate::UnitLength::Cm; - - let result = ctx - .get_changed_program(CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), - }) - .await; - - assert!(result.is_some()); - - let result = result.unwrap(); - - assert_eq!(result.program, program.ast); - assert!(result.clear_scene); - } - - // Changing the grid settings with the exact same file should NOT bust the cache. - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code_but_different_grid_setting() { - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); - - // Change the settings. - ctx.settings.show_grid = !ctx.settings.show_grid; - - let result = ctx - .get_changed_program(CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), - }) - .await; - - assert_eq!(result, None); - } - - // Changing the edge visibility settings with the exact same file should NOT bust the cache. - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code_but_different_edge_visiblity_setting() { - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); - - // Change the settings. - ctx.settings.highlight_edges = !ctx.settings.highlight_edges; - - let result = ctx - .get_changed_program(CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), - }) - .await; - - assert_eq!(result, None); - } - #[tokio::test(flavor = "multi_thread")] async fn kcl_test_ids_stable_between_executions() { let code = r#"sketch001 = startSketchOn('XZ') @@ -2134,7 +1563,7 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; "#; // Execute a slightly different program again. - let program: Program = crate::Program::parse_no_errs(code).unwrap(); + let program = crate::Program::parse_no_errs(code).unwrap(); let cache_info = crate::CacheInformation { old: Some(crate::OldAstState { ast: old_program.ast.clone(), diff --git a/src/wasm-lib/kcl/src/lsp/test_util.rs b/src/wasm-lib/kcl/src/lsp/test_util.rs index 9141775994..3450743a4c 100644 --- a/src/wasm-lib/kcl/src/lsp/test_util.rs +++ b/src/wasm-lib/kcl/src/lsp/test_util.rs @@ -9,7 +9,7 @@ pub async fn kcl_lsp_server(execute: bool) -> Result { let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib)?; let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib)?; - let zoo_client = crate::execution::new_zoo_client(None, None)?; + let zoo_client = crate::engine::new_zoo_client(None, None)?; let executor_ctx = if execute { Some(crate::execution::ExecutorContext::new(&zoo_client, Default::default()).await?) diff --git a/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs b/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs index 660e22330f..3b47190c42 100644 --- a/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs +++ b/src/wasm-lib/kcl/src/parsing/token/tokeniser.rs @@ -361,7 +361,7 @@ fn keyword_type_or_word(i: &mut Input<'_>) -> PResult { #[cfg(test)] mod tests { - use winnow::Located; + use winnow::LocatingSlice; use super::*; use crate::parsing::token::TokenSlice; @@ -373,7 +373,7 @@ mod tests { { let state = State::new(ModuleId::default()); let mut input = Input { - input: Located::new(s), + input: LocatingSlice::new(s), state, }; assert!(p.parse_next(&mut input).is_err(), "parsed {s} but should have failed"); @@ -388,7 +388,7 @@ mod tests { { let state = State::new(ModuleId::default()); let mut input = Input { - input: Located::new(s), + input: LocatingSlice::new(s), state, }; let res = p.parse_next(&mut input); @@ -422,7 +422,7 @@ mod tests { let module_id = ModuleId::from_usize(1); let input = Input { - input: Located::new("0.0000000000"), + input: LocatingSlice::new("0.0000000000"), state: State::new(module_id), }; diff --git a/src/wasm-lib/kcl/src/test_server.rs b/src/wasm-lib/kcl/src/test_server.rs index 6a21464d46..54a5fdaa04 100644 --- a/src/wasm-lib/kcl/src/test_server.rs +++ b/src/wasm-lib/kcl/src/test_server.rs @@ -68,7 +68,7 @@ async fn do_execute_and_snapshot( ) -> Result<(ExecState, image::DynamicImage), ExecErrorWithState> { let mut exec_state = ExecState::new(&ctx.settings); let snapshot_png_bytes = ctx - .execute_and_prepare_snapshot(&program, &mut exec_state) + .execute_and_prepare_snapshot(program.into(), &mut exec_state) .await .map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))? .contents diff --git a/src/wasm-lib/tests/executor/cache.rs b/src/wasm-lib/tests/executor/cache.rs index 85e9cc2c03..19d61d732a 100644 --- a/src/wasm-lib/tests/executor/cache.rs +++ b/src/wasm-lib/tests/executor/cache.rs @@ -28,15 +28,17 @@ async fn cache_test( // set the new settings. ctx.settings = variation.settings.clone(); - ctx.run( - kcl_lib::CacheInformation { - old: old_ast_state, - new_ast: program.ast.clone(), - }, - &mut exec_state, - ) - .await?; - let snapshot_png_bytes = ctx.prepare_snapshot().await?.contents.0; + let snapshot_png_bytes = ctx + .execute_and_prepare_snapshot( + kcl_lib::CacheInformation { + old: old_ast_state, + new_ast: program.ast.clone(), + }, + &mut exec_state, + ) + .await? + .contents + .0; // Decode the snapshot, return it. let img = image::ImageReader::new(std::io::Cursor::new(snapshot_png_bytes)) From c59c304a60261256476c7e214d912c72e2a2fe87 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Tue, 28 Jan 2025 12:03:26 +1300 Subject: [PATCH 4/4] Move caching logic to inside execution Signed-off-by: Nick Cameron --- src/wasm-lib/derive-docs/src/lib.rs | 2 +- .../derive-docs/tests/args_with_lifetime.gen | 2 +- .../derive-docs/tests/args_with_refs.gen | 2 +- src/wasm-lib/derive-docs/tests/array.gen | 4 +- src/wasm-lib/derive-docs/tests/box.gen | 2 +- .../tests/doc_comment_with_code.gen | 4 +- src/wasm-lib/derive-docs/tests/lineTo.gen | 4 +- src/wasm-lib/derive-docs/tests/min.gen | 4 +- src/wasm-lib/derive-docs/tests/option.gen | 2 +- .../derive-docs/tests/option_input_format.gen | 2 +- .../tests/return_vec_box_sketch.gen | 2 +- .../derive-docs/tests/return_vec_sketch.gen | 2 +- src/wasm-lib/derive-docs/tests/show.gen | 2 +- .../tests/test_args_with_exec_state.gen | 2 +- src/wasm-lib/kcl-test-server/src/lib.rs | 7 +- src/wasm-lib/kcl-to-core/src/lib.rs | 2 +- src/wasm-lib/kcl/src/execution/cache.rs | 230 +++++++----------- src/wasm-lib/kcl/src/execution/exec_ast.rs | 5 +- src/wasm-lib/kcl/src/execution/mod.rs | 218 ++++++++++------- src/wasm-lib/kcl/src/execution/state.rs | 4 +- src/wasm-lib/kcl/src/lib.rs | 5 +- src/wasm-lib/kcl/src/lsp/kcl/mod.rs | 73 ++---- src/wasm-lib/kcl/src/lsp/test_util.rs | 1 - src/wasm-lib/kcl/src/test_server.rs | 5 +- .../kw_fn_too_few_args/execution_error.snap | 5 +- src/wasm-lib/src/wasm.rs | 107 ++------ src/wasm-lib/tests/executor/cache.rs | 45 +--- src/wasm-lib/tests/modify/main.rs | 2 +- 28 files changed, 312 insertions(+), 433 deletions(-) diff --git a/src/wasm-lib/derive-docs/src/lib.rs b/src/wasm-lib/derive-docs/src/lib.rs index 3fbfd8043f..d832b7435a 100644 --- a/src/wasm-lib/derive-docs/src/lib.rs +++ b/src/wasm-lib/derive-docs/src/lib.rs @@ -815,7 +815,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr context_type: crate::execution::ContextType::Mock, }; - if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new(&ctx.settings)).await { + if let Err(e) = ctx.run(&program, &mut crate::ExecState::new(&ctx.settings)).await { return Err(miette::Report::new(crate::errors::Report { error: e, filename: format!("{}{}", #fn_name, #index), diff --git a/src/wasm-lib/derive-docs/tests/args_with_lifetime.gen b/src/wasm-lib/derive-docs/tests/args_with_lifetime.gen index 72fbd96b0b..af3d054b33 100644 --- a/src/wasm-lib/derive-docs/tests/args_with_lifetime.gen +++ b/src/wasm-lib/derive-docs/tests/args_with_lifetime.gen @@ -15,7 +15,7 @@ mod test_examples_someFn { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/args_with_refs.gen b/src/wasm-lib/derive-docs/tests/args_with_refs.gen index bfce2913c8..83154f62b4 100644 --- a/src/wasm-lib/derive-docs/tests/args_with_refs.gen +++ b/src/wasm-lib/derive-docs/tests/args_with_refs.gen @@ -15,7 +15,7 @@ mod test_examples_someFn { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/array.gen b/src/wasm-lib/derive-docs/tests/array.gen index 70ce2ee2be..7013206169 100644 --- a/src/wasm-lib/derive-docs/tests/array.gen +++ b/src/wasm-lib/derive-docs/tests/array.gen @@ -16,7 +16,7 @@ mod test_examples_show { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { @@ -73,7 +73,7 @@ mod test_examples_show { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/box.gen b/src/wasm-lib/derive-docs/tests/box.gen index 30df8bb06e..7812a18e5d 100644 --- a/src/wasm-lib/derive-docs/tests/box.gen +++ b/src/wasm-lib/derive-docs/tests/box.gen @@ -16,7 +16,7 @@ mod test_examples_show { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen b/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen index 1939b07136..c33cf12564 100644 --- a/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen +++ b/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen @@ -17,7 +17,7 @@ mod test_examples_my_func { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { @@ -74,7 +74,7 @@ mod test_examples_my_func { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/lineTo.gen b/src/wasm-lib/derive-docs/tests/lineTo.gen index 5e5b030072..fcdb11665c 100644 --- a/src/wasm-lib/derive-docs/tests/lineTo.gen +++ b/src/wasm-lib/derive-docs/tests/lineTo.gen @@ -17,7 +17,7 @@ mod test_examples_line_to { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { @@ -74,7 +74,7 @@ mod test_examples_line_to { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/min.gen b/src/wasm-lib/derive-docs/tests/min.gen index 891ef69fd5..5c3c1f558d 100644 --- a/src/wasm-lib/derive-docs/tests/min.gen +++ b/src/wasm-lib/derive-docs/tests/min.gen @@ -16,7 +16,7 @@ mod test_examples_min { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { @@ -73,7 +73,7 @@ mod test_examples_min { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/option.gen b/src/wasm-lib/derive-docs/tests/option.gen index cb71948ad8..9323159c56 100644 --- a/src/wasm-lib/derive-docs/tests/option.gen +++ b/src/wasm-lib/derive-docs/tests/option.gen @@ -16,7 +16,7 @@ mod test_examples_show { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/option_input_format.gen b/src/wasm-lib/derive-docs/tests/option_input_format.gen index ef01610da3..e03790be0d 100644 --- a/src/wasm-lib/derive-docs/tests/option_input_format.gen +++ b/src/wasm-lib/derive-docs/tests/option_input_format.gen @@ -16,7 +16,7 @@ mod test_examples_import { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/return_vec_box_sketch.gen b/src/wasm-lib/derive-docs/tests/return_vec_box_sketch.gen index 3c366d6eab..4637940934 100644 --- a/src/wasm-lib/derive-docs/tests/return_vec_box_sketch.gen +++ b/src/wasm-lib/derive-docs/tests/return_vec_box_sketch.gen @@ -16,7 +16,7 @@ mod test_examples_import { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/return_vec_sketch.gen b/src/wasm-lib/derive-docs/tests/return_vec_sketch.gen index 5fd19acebb..a593cc5b48 100644 --- a/src/wasm-lib/derive-docs/tests/return_vec_sketch.gen +++ b/src/wasm-lib/derive-docs/tests/return_vec_sketch.gen @@ -16,7 +16,7 @@ mod test_examples_import { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/show.gen b/src/wasm-lib/derive-docs/tests/show.gen index 99c412663e..8b698d53e7 100644 --- a/src/wasm-lib/derive-docs/tests/show.gen +++ b/src/wasm-lib/derive-docs/tests/show.gen @@ -16,7 +16,7 @@ mod test_examples_show { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/derive-docs/tests/test_args_with_exec_state.gen b/src/wasm-lib/derive-docs/tests/test_args_with_exec_state.gen index 409b870f78..edd01d5b10 100644 --- a/src/wasm-lib/derive-docs/tests/test_args_with_exec_state.gen +++ b/src/wasm-lib/derive-docs/tests/test_args_with_exec_state.gen @@ -15,7 +15,7 @@ mod test_examples_some_function { context_type: crate::execution::ContextType::Mock, }; if let Err(e) = ctx - .run(program.into(), &mut crate::ExecState::new(&ctx.settings)) + .run(&program, &mut crate::ExecState::new(&ctx.settings)) .await { return Err(miette::Report::new(crate::errors::Report { diff --git a/src/wasm-lib/kcl-test-server/src/lib.rs b/src/wasm-lib/kcl-test-server/src/lib.rs index 665ca39bce..7bc907d0e8 100644 --- a/src/wasm-lib/kcl-test-server/src/lib.rs +++ b/src/wasm-lib/kcl-test-server/src/lib.rs @@ -176,8 +176,11 @@ async fn snapshot_endpoint(body: Bytes, ctxt: ExecutorContext) -> Response // Let users know if the test is taking a long time. let (done_tx, done_rx) = oneshot::channel::<()>(); let timer = time_until(done_rx); - let snapshot = match ctxt.execute_and_prepare_snapshot(program.into(), &mut exec_state).await { - Ok(sn) => sn, + if let Err(e) = ctxt.run(&program, &mut exec_state).await { + return kcl_err(e); + } + let snapshot = match ctxt.prepare_snapshot().await { + Ok(s) => s, Err(e) => return kcl_err(e), }; let _ = done_tx.send(()); diff --git a/src/wasm-lib/kcl-to-core/src/lib.rs b/src/wasm-lib/kcl-to-core/src/lib.rs index 1b14aa1a66..d018b1e4ad 100644 --- a/src/wasm-lib/kcl-to-core/src/lib.rs +++ b/src/wasm-lib/kcl-to-core/src/lib.rs @@ -16,7 +16,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result { let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new( crate::conn_mock_core::EngineConnection::new(ref_result).await?, ))); - ctx.run(program.into(), &mut ExecState::new(&ctx.settings)).await?; + ctx.run(&program, &mut ExecState::new(&ctx.settings)).await?; let result = result.lock().expect("mutex lock").clone(); Ok(result) diff --git a/src/wasm-lib/kcl/src/execution/cache.rs b/src/wasm-lib/kcl/src/execution/cache.rs index 427d47c7a7..66d0cbf40f 100644 --- a/src/wasm-lib/kcl/src/execution/cache.rs +++ b/src/wasm-lib/kcl/src/execution/cache.rs @@ -1,26 +1,46 @@ //! Functions for helping with caching an ast and finding the parts the changed. -use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use tokio::sync::RwLock; use crate::{ - execution::ExecState, + execution::{ExecState, ExecutorSettings}, parsing::ast::types::{Node, Program}, walk::Node as WalkNode, }; -use super::ExecutorSettings; +lazy_static::lazy_static! { + /// A static mutable lock for updating the last successful execution state for the cache. + static ref OLD_AST_MEMORY: Arc>> = Default::default(); +} + +/// Read the old ast memory from the lock. +pub(super) async fn read_old_ast_memory() -> Option { + let old_ast = OLD_AST_MEMORY.read().await; + old_ast.clone() +} + +pub(super) async fn write_old_ast_memory(old_state: OldAstState) { + let mut old_ast = OLD_AST_MEMORY.write().await; + *old_ast = Some(old_state); +} + +pub async fn bust_cache() { + let mut old_ast = OLD_AST_MEMORY.write().await; + // Set the cache to None. + *old_ast = None; +} /// Information for the caching an AST and smartly re-executing it if we can. -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct CacheInformation { - /// The old information. - pub old: Option, - /// The new ast to executed. - pub new_ast: Node, +#[derive(Debug, Clone)] +pub struct CacheInformation<'a> { + pub ast: &'a Node, + pub settings: &'a ExecutorSettings, } /// The old ast and program memory. -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone)] pub struct OldAstState { /// The ast. pub ast: Node, @@ -30,17 +50,8 @@ pub struct OldAstState { pub settings: crate::execution::ExecutorSettings, } -impl From for CacheInformation { - fn from(program: crate::Program) -> Self { - CacheInformation { - old: None, - new_ast: program.ast, - } - } -} - /// The result of a cache check. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Clone, PartialEq)] #[allow(clippy::large_enum_variant)] pub(super) enum CacheResult { ReExecute { @@ -60,23 +71,15 @@ pub(super) enum CacheResult { /// /// Returns `None` when there are no changes to the program, i.e. it is /// fully cached. -pub(super) async fn get_changed_program(info: CacheInformation, settings: &ExecutorSettings) -> CacheResult { - let Some(old) = info.old else { - // We have no old info, we need to re-execute the whole thing. - return CacheResult::ReExecute { - clear_scene: true, - program: info.new_ast, - }; - }; - +pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInformation<'_>) -> CacheResult { // If the settings are different we might need to bust the cache. // We specifically do this before checking if they are the exact same. - if &old.settings != settings { + if old.settings != new.settings { // If the units are different we need to re-execute the whole thing. - if old.settings.units != settings.units { + if old.settings.units != new.settings.units { return CacheResult::ReExecute { clear_scene: true, - program: info.new_ast, + program: new.ast.clone(), }; } @@ -87,12 +90,13 @@ pub(super) async fn get_changed_program(info: CacheInformation, settings: &Execu // If the ASTs are the EXACT same we return None. // We don't even need to waste time computing the digests. - if old.ast == info.new_ast { + if old.ast == new.ast { return CacheResult::NoAction; } - let mut old_ast = old.ast; - let mut new_ast = info.new_ast; + // We have to clone just because the digests are stored inline :-( + let mut old_ast = old.ast.clone(); + let mut new_ast = new.ast.clone(); // The digests should already be computed, but just in case we don't // want to compare against none. @@ -112,10 +116,7 @@ pub(super) async fn get_changed_program(info: CacheInformation, settings: &Execu /// way in which this gets invoked should always be through /// [get_changed_program]. This is purely to contain the logic on /// how we construct a new [CacheResult]. -fn generate_changed_program(old_ast: Node, new_ast: Node) -> CacheResult { - let mut generated_program = new_ast.clone(); - generated_program.body = vec![]; - +fn generate_changed_program(old_ast: Node, mut new_ast: Node) -> CacheResult { if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| { let old_node: WalkNode = old.into(); let new_node: WalkNode = new.into(); @@ -158,13 +159,11 @@ fn generate_changed_program(old_ast: Node, new_ast: Node) -> C // Statements up until now are the same, which means this // is a "pure addition" of the remaining slice. - generated_program - .body - .extend_from_slice(&new_ast.body[old_ast.body.len()..]); + new_ast.body = new_ast.body[old_ast.body.len()..].to_owned(); CacheResult::ReExecute { clear_scene: false, - program: generated_program, + program: new_ast, } } std::cmp::Ordering::Equal => { @@ -177,9 +176,11 @@ fn generate_changed_program(old_ast: Node, new_ast: Node) -> C // so but i think many things. This def needs to change // when the code above changes. + new_ast.body = vec![]; + CacheResult::ReExecute { clear_scene: false, - program: generated_program, + program: new_ast, } } } @@ -187,14 +188,11 @@ fn generate_changed_program(old_ast: Node, new_ast: Node) -> C #[cfg(test)] mod tests { - use crate::execution::parse_execute; - use super::*; + use crate::execution::parse_execute; - // Easy case where we have no old ast and memory. - // We need to re-execute everything. #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_no_old_information() { + async fn test_get_changed_program_same_code() { let new = r#"// Remove the end face for the extrusion. firstSketch = startSketchOn('XY') |> startProfileAt([-12, 12], %) @@ -206,52 +204,18 @@ firstSketch = startSketchOn('XY') // Remove the end face for the extrusion. shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; + let (program, ctx, _) = parse_execute(new).await.unwrap(); let result = get_changed_program( CacheInformation { - old: None, - new_ast: program.ast.clone(), + ast: &program.ast, + settings: &ctx.settings, }, - &ctx.settings, - ) - .await; - - assert_eq!( - result, - CacheResult::ReExecute { - clear_scene: true, - program: program.ast - } - ); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_get_changed_program_same_code() { - let new = r#"// Remove the end face for the extrusion. -firstSketch = startSketchOn('XY') - |> startProfileAt([-12, 12], %) - |> line([24, 0], %) - |> line([0, -24], %) - |> line([-24, 0], %) - |> close(%) - |> extrude(6, %) - -// Remove the end face for the extrusion. -shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - - let (program, ctx, exec_state) = parse_execute(new).await.unwrap(); - - let result = get_changed_program( CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), + ast: &program.ast, + settings: &ctx.settings, }, - &ctx.settings, ) .await; @@ -284,20 +248,19 @@ firstSketch = startSketchOn('XY') // Remove the end face for the extrusion. shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - let (program_old, ctx, exec_state) = parse_execute(old).await.unwrap(); + let (program_old, ctx, _) = parse_execute(old).await.unwrap(); let program_new = crate::Program::parse_no_errs(new).unwrap(); let result = get_changed_program( CacheInformation { - old: Some(OldAstState { - ast: program_old.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program_new.ast.clone(), + ast: &program_old.ast, + settings: &ctx.settings, + }, + CacheInformation { + ast: &program_new.ast, + settings: &ctx.settings, }, - &ctx.settings, ) .await; @@ -330,20 +293,19 @@ firstSketch = startSketchOn('XY') // Remove the end face for the extrusion. shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - let (program, ctx, exec_state) = parse_execute(old).await.unwrap(); + let (program, ctx, _) = parse_execute(old).await.unwrap(); let program_new = crate::Program::parse_no_errs(new).unwrap(); let result = get_changed_program( CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program_new.ast.clone(), + ast: &program.ast, + settings: &ctx.settings, + }, + CacheInformation { + ast: &program_new.ast, + settings: &ctx.settings, }, - &ctx.settings, ) .await; @@ -376,20 +338,19 @@ firstSketch = startSketchOn('XY') // Remove the end face for the extrusion. shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - let (program, ctx, exec_state) = parse_execute(old).await.unwrap(); + let (program, ctx, _) = parse_execute(old).await.unwrap(); let program_new = crate::Program::parse_no_errs(new).unwrap(); let result = get_changed_program( CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program_new.ast.clone(), + ast: &program.ast, + settings: &ctx.settings, + }, + CacheInformation { + ast: &program_new.ast, + settings: &ctx.settings, }, - &ctx.settings, ) .await; @@ -411,21 +372,20 @@ firstSketch = startSketchOn('XY') // Remove the end face for the extrusion. shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); + let (program, mut ctx, _) = parse_execute(new).await.unwrap(); // Change the settings to cm. ctx.settings.units = crate::UnitLength::Cm; let result = get_changed_program( CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), + ast: &program.ast, + settings: &Default::default(), + }, + CacheInformation { + ast: &program.ast, + settings: &ctx.settings, }, - &ctx.settings, ) .await; @@ -453,21 +413,20 @@ firstSketch = startSketchOn('XY') // Remove the end face for the extrusion. shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); + let (program, mut ctx, _) = parse_execute(new).await.unwrap(); // Change the settings. ctx.settings.show_grid = !ctx.settings.show_grid; let result = get_changed_program( CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), + ast: &program.ast, + settings: &Default::default(), + }, + CacheInformation { + ast: &program.ast, + settings: &ctx.settings, }, - &ctx.settings, ) .await; @@ -489,21 +448,20 @@ firstSketch = startSketchOn('XY') // Remove the end face for the extrusion. shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#; - let (program, mut ctx, exec_state) = parse_execute(new).await.unwrap(); + let (program, mut ctx, _) = parse_execute(new).await.unwrap(); // Change the settings. ctx.settings.highlight_edges = !ctx.settings.highlight_edges; let result = get_changed_program( CacheInformation { - old: Some(OldAstState { - ast: program.ast.clone(), - exec_state, - settings: Default::default(), - }), - new_ast: program.ast.clone(), + ast: &program.ast, + settings: &Default::default(), + }, + CacheInformation { + ast: &program.ast, + settings: &ctx.settings, }, - &ctx.settings, ) .await; diff --git a/src/wasm-lib/kcl/src/execution/exec_ast.rs b/src/wasm-lib/kcl/src/execution/exec_ast.rs index 01e4910d67..51cb66501c 100644 --- a/src/wasm-lib/kcl/src/execution/exec_ast.rs +++ b/src/wasm-lib/kcl/src/execution/exec_ast.rs @@ -13,7 +13,10 @@ use crate::{ }, fs::FileSystem, parsing::ast::types::{ - ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression, PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator + ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, + CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility, + LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression, + PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator, }, source_range::{ModuleId, SourceRange}, std::{ diff --git a/src/wasm-lib/kcl/src/execution/mod.rs b/src/wasm-lib/kcl/src/execution/mod.rs index 57e664468a..f014865a9f 100644 --- a/src/wasm-lib/kcl/src/execution/mod.rs +++ b/src/wasm-lib/kcl/src/execution/mod.rs @@ -3,6 +3,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::Result; +use cache::OldAstState; use indexmap::IndexMap; use kcmc::{ each_cmd as mcmd, @@ -30,17 +31,18 @@ use crate::{ }; pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId}; +pub use cache::bust_cache; pub use cad_op::Operation; pub use exec_ast::FunctionParam; pub use geometry::*; -pub(crate) use import::{import_foreign, ZOO_COORD_SYSTEM}; +pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, ZOO_COORD_SYSTEM}; pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen}; pub use memory::ProgramMemory; pub use state::{ExecState, IdGenerator}; mod annotations; mod artifact; -pub(crate) mod cache; +mod cache; mod cad_op; mod exec_ast; mod geometry; @@ -506,32 +508,117 @@ impl ExecutorContext { ) -> Result<(), KclError> { self.engine .clear_scene(&mut exec_state.global.id_generator, source_range) - .await?; + .await + } + + pub async fn run_mock( + &self, + program: crate::Program, + program_memory_override: Option, + ) -> Result { + assert!(self.is_mock()); + + let mut exec_state = ExecState::new(&self.settings); + if let Some(program_memory_override) = program_memory_override { + exec_state.mod_local.memory = program_memory_override; + } + + self.inner_run(&program.ast, &mut exec_state).await?; + Ok(exec_state.to_wasm_outcome()) + } + + pub async fn run_with_caching(&self, program: crate::Program) -> Result { + assert!(!self.is_mock()); + + let (program, mut exec_state) = if let Some(OldAstState { + ast: old_ast, + exec_state: old_state, + settings: old_settings, + }) = cache::read_old_ast_memory().await + { + let old = CacheInformation { + ast: &old_ast, + settings: &old_settings, + }; + let new = CacheInformation { + ast: &program.ast, + settings: &self.settings, + }; + + // Get the program that actually changed from the old and new information. + let (clear_scene, program) = match cache::get_changed_program(old, new).await { + CacheResult::ReExecute { clear_scene, program } => (clear_scene, program), + CacheResult::ReapplySettings => { + if self + .engine + .reapply_settings(&self.settings, Default::default()) + .await + .is_ok() + { + return Ok(old_state.to_wasm_outcome()); + } + (true, program.ast) + } + CacheResult::NoAction => return Ok(old_state.to_wasm_outcome()), + }; + + let exec_state = if clear_scene { + // Pop the execution state, since we are starting fresh. + let mut exec_state = old_state; + exec_state.reset(&self.settings); + + // We don't do this in mock mode since there is no engine connection + // anyways and from the TS side we override memory and don't want to clear it. + self.send_clear_scene(&mut exec_state, Default::default()) + .await + .map_err(KclErrorWithOutputs::no_outputs)?; + + exec_state + } else { + old_state + }; + + (program, exec_state) + } else { + let mut exec_state = ExecState::new(&self.settings); + self.send_clear_scene(&mut exec_state, Default::default()) + .await + .map_err(KclErrorWithOutputs::no_outputs)?; + (program.ast, exec_state) + }; + + let result = self.inner_run(&program, &mut exec_state).await; - // We do not create the planes here as the post hook in wasm will do that - // AND if we aren't in wasm it doesn't really matter. - Ok(()) + if result.is_err() { + cache::bust_cache().await; + } + + // Throw the error. + result?; + + // Save this as the last successful execution to the cache. + cache::write_old_ast_memory(OldAstState { + ast: program, + exec_state: exec_state.clone(), + settings: self.settings.clone(), + }) + .await; + + Ok(exec_state.to_wasm_outcome()) } /// Perform the execution of a program. /// /// You can optionally pass in some initialization memory for partial /// execution. - pub async fn run(&self, cache_info: CacheInformation, exec_state: &mut ExecState) -> Result<(), KclError> { - self.inner_run(cache_info, exec_state).await?; - Ok(()) - } - - // TODO program.clone().into() - /// Execute the program, then get a PNG screenshot. - pub async fn execute_and_prepare_snapshot( + pub async fn run( &self, - cache_info: CacheInformation, + program: &crate::Program, exec_state: &mut ExecState, - ) -> std::result::Result { - self.inner_run(cache_info, exec_state).await?; - - self.prepare_snapshot().await + ) -> Result, KclError> { + self.run_with_ui_outputs(program, exec_state) + .await + .map_err(|e| e.into()) } /// Perform the execution of a program. @@ -543,70 +630,31 @@ impl ExecutorContext { /// artifact graph. pub async fn run_with_ui_outputs( &self, - cache_info: CacheInformation, + program: &crate::Program, exec_state: &mut ExecState, - ) -> Result<(), KclErrorWithOutputs> { - self.inner_run(cache_info, exec_state).await?; - Ok(()) - } - - /// Perform the execution of a program. Additionally return engine session - /// data. - pub async fn run_with_session_data( - &self, - cache_info: CacheInformation, - exec_state: &mut ExecState, - ) -> Result, KclError> { - self.inner_run(cache_info, exec_state).await.map_err(|e| e.into()) + ) -> Result, KclErrorWithOutputs> { + self.send_clear_scene(exec_state, Default::default()) + .await + .map_err(KclErrorWithOutputs::no_outputs)?; + self.inner_run(&program.ast, exec_state).await } /// Perform the execution of a program. Accept all possible parameters and /// output everything. - /// - /// You can optionally pass in some initialization memory for partial - /// execution. async fn inner_run( &self, - cache_info: CacheInformation, + program: &Node, exec_state: &mut ExecState, ) -> Result, KclErrorWithOutputs> { let _stats = crate::log::LogPerfStats::new("Interpretation"); - // Get the program that actually changed from the old and new information. - let (clear_scene, program) = match cache::get_changed_program(cache_info.clone(), &self.settings).await { - CacheResult::ReExecute { clear_scene, program } => (clear_scene, program), - CacheResult::ReapplySettings => { - if self - .engine - .reapply_settings(&self.settings, Default::default()) - .await - .is_ok() - { - return Ok(None); - } - (true, cache_info.new_ast) - } - CacheResult::NoAction => return Ok(None), - }; - - if clear_scene && !self.is_mock() { - // Pop the execution state, since we are starting fresh. - exec_state.reset(&self.settings); - - // We don't do this in mock mode since there is no engine connection - // anyways and from the TS side we override memory and don't want to clear it. - self.send_clear_scene(exec_state, Default::default()) - .await - .map_err(KclErrorWithOutputs::no_outputs)?; - } - // Re-apply the settings, in case the cache was busted. self.engine .reapply_settings(&self.settings, Default::default()) .await .map_err(KclErrorWithOutputs::no_outputs)?; - self.execute_and_build_graph(&program, exec_state).await.map_err(|e| { + self.execute_and_build_graph(program, exec_state).await.map_err(|e| { KclErrorWithOutputs::new( e, exec_state.mod_local.operations.clone(), @@ -662,7 +710,7 @@ impl ExecutorContext { } /// Get a snapshot of the current scene. - async fn prepare_snapshot(&self) -> std::result::Result { + pub async fn prepare_snapshot(&self) -> std::result::Result { // Zoom to fit. self.engine .send_modeling_cmd( @@ -718,7 +766,7 @@ async fn parse_execute(code: &str) -> Result<(crate::Program, ExecutorContext, E context_type: ContextType::Mock, }; let mut exec_state = ExecState::new(&ctx.settings); - ctx.run(program.clone().into(), &mut exec_state).await?; + ctx.run(&program, &mut exec_state).await?; Ok((program, ctx, exec_state)) } @@ -1537,16 +1585,17 @@ let w = f() + f() .await .unwrap(); let old_program = crate::Program::parse_no_errs(code).unwrap(); + // Execute the program. - let mut exec_state = ExecState::new(&ctx.settings); - let cache_info = crate::CacheInformation { - old: None, - new_ast: old_program.ast.clone(), - }; - ctx.run(cache_info, &mut exec_state).await.unwrap(); + ctx.run_with_caching(old_program).await.unwrap(); // Get the id_generator from the first execution. - let id_generator = exec_state.global.id_generator.clone(); + let id_generator = cache::read_old_ast_memory() + .await + .unwrap() + .exec_state + .global + .id_generator; let code = r#"sketch001 = startSketchOn('XZ') |> startProfileAt([62.74, 206.13], %) @@ -1564,17 +1613,16 @@ let w = f() + f() // Execute a slightly different program again. let program = crate::Program::parse_no_errs(code).unwrap(); - let cache_info = crate::CacheInformation { - old: Some(crate::OldAstState { - ast: old_program.ast.clone(), - exec_state: exec_state.clone(), - settings: ctx.settings.clone(), - }), - new_ast: program.ast.clone(), - }; // Execute the program. - ctx.run(cache_info, &mut exec_state).await.unwrap(); + ctx.run_with_caching(program).await.unwrap(); + + let new_id_generator = cache::read_old_ast_memory() + .await + .unwrap() + .exec_state + .global + .id_generator; - assert_eq!(id_generator, exec_state.global.id_generator); + assert_eq!(id_generator, new_id_generator); } } diff --git a/src/wasm-lib/kcl/src/execution/state.rs b/src/wasm-lib/kcl/src/execution/state.rs index 725fc0fe4f..929bc7c218 100644 --- a/src/wasm-lib/kcl/src/execution/state.rs +++ b/src/wasm-lib/kcl/src/execution/state.rs @@ -8,8 +8,8 @@ use uuid::Uuid; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, - ExecutorSettings, KclValue, ModuleInfo, ModuleRepr, Operation, ProgramMemory, SolidLazyIds, UnitAngle, UnitLen, + annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, ExecutorSettings, + KclValue, ModuleInfo, ModuleRepr, Operation, ProgramMemory, SolidLazyIds, UnitAngle, UnitLen, }, parsing::ast::types::NonCodeValue, source_range::{ModuleId, SourceRange}, diff --git a/src/wasm-lib/kcl/src/lib.rs b/src/wasm-lib/kcl/src/lib.rs index 00ce5e7f49..6f8d8b5f86 100644 --- a/src/wasm-lib/kcl/src/lib.rs +++ b/src/wasm-lib/kcl/src/lib.rs @@ -82,10 +82,7 @@ mod wasm; pub use coredump::CoreDump; pub use engine::{EngineManager, ExecutionKind}; pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs}; -pub use execution::{ - cache::{CacheInformation, OldAstState}, - ExecState, ExecutorContext, ExecutorSettings, Point2d, -}; +pub use execution::{bust_cache, ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, Point2d}; pub use lsp::{ copilot::Backend as CopilotLspBackend, kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand}, diff --git a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs index 0c253ebcde..d1f8006eb0 100644 --- a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs +++ b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs @@ -49,7 +49,7 @@ use crate::{ token::TokenStream, PIPE_OPERATOR, }, - CacheInformation, ExecState, ModuleId, OldAstState, Program, SourceRange, + ModuleId, Program, SourceRange, }; const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [ SemanticTokenType::NUMBER, @@ -102,12 +102,6 @@ pub struct Backend { pub(super) token_map: DashMap, /// AST maps. pub ast_map: DashMap>, - /// Last successful execution. - /// This gets set to None when execution errors, or we want to bust the cache on purpose to - /// force a re-execution. - /// We do not need to manually bust the cache for changed units, that's handled by the cache - /// information. - pub last_successful_ast_state: Arc>>, /// Memory maps. pub memory_map: DashMap, /// Current code. @@ -192,7 +186,6 @@ impl Backend { diagnostics_map: Default::default(), symbols_map: Default::default(), semantic_tokens_map: Default::default(), - last_successful_ast_state: Default::default(), is_initialized: Default::default(), }) } @@ -267,10 +260,7 @@ impl crate::lsp::backend::Backend for Backend { async fn inner_on_change(&self, params: TextDocumentItem, force: bool) { if force { - // Bust the execution cache. - let mut old_ast_state = self.last_successful_ast_state.write().await; - *old_ast_state = None; - drop(old_ast_state); + crate::bust_cache().await; } let filename = params.uri.to_string(); @@ -688,52 +678,27 @@ impl Backend { return Ok(()); } - let mut last_successful_ast_state = self.last_successful_ast_state.write().await; - - let mut exec_state = if let Some(last_successful_ast_state) = last_successful_ast_state.clone() { - last_successful_ast_state.exec_state - } else { - ExecState::new(&executor_ctx.settings) - }; + match executor_ctx.run_with_caching(ast.clone()).await { + Err(err) => { + self.memory_map.remove(params.uri.as_str()); + self.add_to_diagnostics(params, &[err.error], false).await; - if let Err(err) = executor_ctx - .run( - CacheInformation { - old: last_successful_ast_state.clone(), - new_ast: ast.ast.clone(), - }, - &mut exec_state, - ) - .await - { - self.memory_map.remove(params.uri.as_str()); - self.add_to_diagnostics(params, &[err], false).await; + // Since we already published the diagnostics we don't really care about the error + // string. + Err(anyhow::anyhow!("failed to execute code")) + } + Ok(outcome) => { + let memory = outcome.memory; + self.memory_map.insert(params.uri.to_string(), memory.clone()); - // Update the last successful ast state to be None. - *last_successful_ast_state = None; + // Send the notification to the client that the memory was updated. + self.client + .send_notification::(memory) + .await; - // Since we already published the diagnostics we don't really care about the error - // string. - return Err(anyhow::anyhow!("failed to execute code")); + Ok(()) + } } - - // Update the last successful ast state. - *last_successful_ast_state = Some(OldAstState { - ast: ast.ast.clone(), - exec_state: exec_state.clone(), - settings: executor_ctx.settings.clone(), - }); - drop(last_successful_ast_state); - - self.memory_map - .insert(params.uri.to_string(), exec_state.memory().clone()); - - // Send the notification to the client that the memory was updated. - self.client - .send_notification::(exec_state.mod_local.memory) - .await; - - Ok(()) } pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option { diff --git a/src/wasm-lib/kcl/src/lsp/test_util.rs b/src/wasm-lib/kcl/src/lsp/test_util.rs index 3450743a4c..ff5c8e69b7 100644 --- a/src/wasm-lib/kcl/src/lsp/test_util.rs +++ b/src/wasm-lib/kcl/src/lsp/test_util.rs @@ -37,7 +37,6 @@ pub async fn kcl_lsp_server(execute: bool) -> Result { can_send_telemetry: true, executor_ctx: Arc::new(tokio::sync::RwLock::new(executor_ctx)), can_execute: Arc::new(tokio::sync::RwLock::new(can_execute)), - last_successful_ast_state: Default::default(), is_initialized: Default::default(), }) .custom_method("kcl/updateUnits", crate::lsp::kcl::Backend::update_units) diff --git a/src/wasm-lib/kcl/src/test_server.rs b/src/wasm-lib/kcl/src/test_server.rs index 54a5fdaa04..0376d9132a 100644 --- a/src/wasm-lib/kcl/src/test_server.rs +++ b/src/wasm-lib/kcl/src/test_server.rs @@ -67,8 +67,11 @@ async fn do_execute_and_snapshot( program: Program, ) -> Result<(ExecState, image::DynamicImage), ExecErrorWithState> { let mut exec_state = ExecState::new(&ctx.settings); + ctx.run_with_ui_outputs(&program, &mut exec_state) + .await + .map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone()))?; let snapshot_png_bytes = ctx - .execute_and_prepare_snapshot(program.into(), &mut exec_state) + .prepare_snapshot() .await .map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))? .contents diff --git a/src/wasm-lib/kcl/tests/kw_fn_too_few_args/execution_error.snap b/src/wasm-lib/kcl/tests/kw_fn_too_few_args/execution_error.snap index 0f811ae264..6932ee48d6 100644 --- a/src/wasm-lib/kcl/tests/kw_fn_too_few_args/execution_error.snap +++ b/src/wasm-lib/kcl/tests/kw_fn_too_few_args/execution_error.snap @@ -1,12 +1,11 @@ --- source: kcl/src/simulation_tests.rs description: Error from executing kw_fn_too_few_args.kcl -snapshot_kind: text --- KCL Semantic error - × semantic: This function requires a parameter y, but you haven't passed - │ it one. + × semantic: This function requires a parameter y, but you haven't passed it + │ one. ╭─[1:7] 1 │ ╭─▶ fn add(x, y) { 2 │ │ return x + y diff --git a/src/wasm-lib/src/wasm.rs b/src/wasm-lib/src/wasm.rs index 4923cb4cc2..82087cf7ca 100644 --- a/src/wasm-lib/src/wasm.rs +++ b/src/wasm-lib/src/wasm.rs @@ -4,33 +4,10 @@ use std::sync::Arc; use futures::stream::TryStreamExt; use gloo_utils::format::JsValueSerdeExt; -use kcl_lib::{ - exec::IdGenerator, CacheInformation, CoreDump, EngineManager, ExecState, ModuleId, OldAstState, Point2d, Program, -}; -use tokio::sync::RwLock; +use kcl_lib::{bust_cache, exec::IdGenerator, CoreDump, EngineManager, ModuleId, Point2d, Program}; use tower_lsp::{LspService, Server}; use wasm_bindgen::prelude::*; -lazy_static::lazy_static! { - /// A static mutable lock for updating the last successful execution state for the cache. - static ref OLD_AST_MEMORY: Arc>> = Default::default(); -} - -// Read the old ast memory from the lock, this should never fail since -// in failure scenarios we should just bust the cache and send back None as the previous -// state. -async fn read_old_ast_memory() -> Option { - let lock = OLD_AST_MEMORY.read().await; - lock.clone() -} - -async fn bust_cache() { - // We don't use the cache in mock mode. - let mut current_cache = OLD_AST_MEMORY.write().await; - // Set the cache to None. - *current_cache = None; -} - // wasm_bindgen wrapper for clearing the scene and busting the cache. #[wasm_bindgen] pub async fn clear_scene_and_bust_cache( @@ -38,7 +15,6 @@ pub async fn clear_scene_and_bust_cache( ) -> Result<(), String> { console_error_panic_hook::set_once(); - // Bust the cache. bust_cache().await; let engine = kcl_lib::wasm_engine::EngineConnection::new(engine_manager) @@ -69,74 +45,31 @@ pub async fn execute( let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?; let program_memory_override: Option = serde_json::from_str(program_memory_override_str).map_err(|e| e.to_string())?; - - // If we have a program memory override, assume we are in mock mode. - // You cannot override the memory in non-mock mode. - let is_mock = program_memory_override.is_some(); - let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?; let mut settings: kcl_lib::ExecutorSettings = config.into(); if let Some(path) = path { settings.with_current_file(std::path::PathBuf::from(path)); } - let ctx = if is_mock { - kcl_lib::ExecutorContext::new_mock(fs_manager, settings).await? - } else { - kcl_lib::ExecutorContext::new(engine_manager, fs_manager, settings).await? - }; - - let mut exec_state = ExecState::new(&ctx.settings); - let mut old_ast_memory = None; - - // Populate from the old exec state if it exists. - if let Some(program_memory_override) = program_memory_override { - // We are in mock mode, so don't use any cache. - exec_state.mod_local.memory = program_memory_override; - } else { - // If we are in mock mode, we don't want to use any cache. - if let Some(old) = read_old_ast_memory().await { - exec_state = old.exec_state.clone(); - old_ast_memory = Some(old); + // If we have a program memory override, assume we are in mock mode. + // You cannot override the memory in non-mock mode. + if program_memory_override.is_some() { + let ctx = kcl_lib::ExecutorContext::new_mock(fs_manager, settings.into()).await?; + match ctx.run_mock(program, program_memory_override).await { + // The serde-wasm-bindgen does not work here because of weird HashMap issues. + // DO NOT USE serde_wasm_bindgen::to_value it will break the frontend. + Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()), + Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?), } - } - - if let Err(err) = ctx - .run_with_ui_outputs( - CacheInformation { - old: old_ast_memory, - new_ast: program.ast.clone(), - }, - &mut exec_state, - ) - .await - { - if !is_mock { - bust_cache().await; + } else { + let ctx = kcl_lib::ExecutorContext::new(engine_manager, fs_manager, settings.into()).await?; + match ctx.run_with_caching(program).await { + // The serde-wasm-bindgen does not work here because of weird HashMap issues. + // DO NOT USE serde_wasm_bindgen::to_value it will break the frontend. + Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()), + Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?), } - - // Throw the error. - return Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?); - } - - if !is_mock { - // We don't use the cache in mock mode. - let mut current_cache = OLD_AST_MEMORY.write().await; - - // If we aren't in mock mode, save this as the last successful execution to the cache. - *current_cache = Some(OldAstState { - ast: program.ast.clone(), - exec_state: exec_state.clone(), - settings: ctx.settings.clone(), - }); - drop(current_cache); } - - // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the - // gloo-serialize crate instead. - // DO NOT USE serde_wasm_bindgen::to_value(&exec_state).map_err(|e| e.to_string()) - // it will break the frontend. - JsValue::from_serde(&exec_state.to_wasm_outcome()).map_err(|e| e.to_string()) } // wasm_bindgen wrapper for execute @@ -159,7 +92,6 @@ pub async fn make_default_planes( engine_manager: kcl_lib::wasm_engine::EngineCommandManager, ) -> Result { console_error_panic_hook::set_once(); - // deserialize the ast from a stringified json let engine = kcl_lib::wasm_engine::EngineConnection::new(engine_manager) .await @@ -169,12 +101,9 @@ pub async fn make_default_planes( .await .map_err(String::from)?; - // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the - // gloo-serialize crate instead. JsValue::from_serde(&default_planes).map_err(|e| e.to_string()) } -// wasm_bindgen wrapper for execute #[wasm_bindgen] pub async fn modify_ast_for_sketch_wasm( manager: kcl_lib::wasm_engine::EngineCommandManager, @@ -207,8 +136,6 @@ pub async fn modify_ast_for_sketch_wasm( .await .map_err(String::from)?; - // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the - // gloo-serialize crate instead. JsValue::from_serde(&program).map_err(|e| e.to_string()) } diff --git a/src/wasm-lib/tests/executor/cache.rs b/src/wasm-lib/tests/executor/cache.rs index 19d61d732a..830831d6c5 100644 --- a/src/wasm-lib/tests/executor/cache.rs +++ b/src/wasm-lib/tests/executor/cache.rs @@ -1,7 +1,7 @@ //! Cache testing framework. use anyhow::Result; -use kcl_lib::{ExecError, ExecState}; +use kcl_lib::{bust_cache, ExecError, ExecOutcome}; #[derive(Debug)] struct Variation<'a> { @@ -12,15 +12,14 @@ struct Variation<'a> { async fn cache_test( test_name: &str, variations: Vec>, -) -> Result> { +) -> Result> { let first = variations .first() .ok_or_else(|| anyhow::anyhow!("No variations provided for test '{}'", test_name))?; let mut ctx = kcl_lib::ExecutorContext::new_with_client(first.settings.clone(), None, None).await?; - let mut exec_state = kcl_lib::ExecState::new(&ctx.settings); - let mut old_ast_state = None; + bust_cache().await; let mut img_results = Vec::new(); for (index, variation) in variations.iter().enumerate() { let program = kcl_lib::Program::parse_no_errs(variation.code)?; @@ -28,17 +27,8 @@ async fn cache_test( // set the new settings. ctx.settings = variation.settings.clone(); - let snapshot_png_bytes = ctx - .execute_and_prepare_snapshot( - kcl_lib::CacheInformation { - old: old_ast_state, - new_ast: program.ast.clone(), - }, - &mut exec_state, - ) - .await? - .contents - .0; + let outcome = ctx.run_with_caching(program).await?; + let snapshot_png_bytes = ctx.prepare_snapshot().await?.contents.0; // Decode the snapshot, return it. let img = image::ImageReader::new(std::io::Cursor::new(snapshot_png_bytes)) @@ -48,14 +38,7 @@ async fn cache_test( // Save the snapshot. let path = crate::assert_out(&format!("cache_{}_{}", test_name, index), &img); - img_results.push((path, img, exec_state.clone())); - - // Prepare the last state. - old_ast_state = Some(kcl_lib::OldAstState { - ast: program.ast, - exec_state: exec_state.clone(), - settings: variation.settings.clone(), - }); + img_results.push((path, img, outcome)); } ctx.close().await; @@ -256,19 +239,13 @@ extrude(4, sketch001) .await .unwrap(); - let first = result.first().unwrap(); - let second = result.last().unwrap(); + let first = result.first().unwrap().2.artifact_commands.len(); + let second = result.last().unwrap().2.artifact_commands.len(); assert!( - first.2.global.artifact_commands.len() < second.2.global.artifact_commands.len(), + first < second, "Second should have all the artifact commands of the first, plus more. first={:?}, second={:?}", - first.2.global.artifact_commands.len(), - second.2.global.artifact_commands.len() - ); - assert!( - first.2.global.artifact_responses.len() < second.2.global.artifact_responses.len(), - "Second should have all the artifact responses of the first, plus more. first={:?}, second={:?}", - first.2.global.artifact_responses.len(), - second.2.global.artifact_responses.len() + first, + second ); } diff --git a/src/wasm-lib/tests/modify/main.rs b/src/wasm-lib/tests/modify/main.rs index 375e9959d0..0e3079a08f 100644 --- a/src/wasm-lib/tests/modify/main.rs +++ b/src/wasm-lib/tests/modify/main.rs @@ -11,7 +11,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, Modu let program = Program::parse_no_errs(code)?; let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?; let mut exec_state = ExecState::new(&ctx.settings); - ctx.run(program.clone().into(), &mut exec_state).await?; + ctx.run(&program, &mut exec_state).await?; // We need to get the sketch ID. // Get the sketch ID from memory.