diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 72cb2cbd22..a7e7ac0ffe 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -7,6 +7,7 @@ name: Benchmarks env: RUST_BACKTRACE: 1 + ROC_NUM_WORKERS: 1 jobs: prep-dependency-container: @@ -42,4 +43,4 @@ jobs: run: cd ci/bench-runner && cargo build --release && cd ../.. - name: run benchmarks with regression check - run: echo "TODO re-enable benchmarks once race condition is fixed"#./ci/bench-runner/target/release/bench-runner --check-executables-changed + run: ./ci/bench-runner/target/release/bench-runner --check-executables-changed diff --git a/.gitignore b/.gitignore index 1c7ea24a83..239f4d05dc 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ editor/benches/resources/25000000_lines.roc editor/benches/resources/50000_lines.roc editor/benches/resources/500_lines.roc +# file editor creates when no arg is passed +new-roc-project + # rust cache (sccache folder) sccache_dir diff --git a/Cargo.lock b/Cargo.lock index e8289983d3..6f9b42c036 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,10 +131,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] -name = "ash" -version = "0.32.1" +name = "arrayvec" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112" +checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" + +[[package]] +name = "ash" +version = "0.33.3+1.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" dependencies = [ "libloading 0.7.0", ] @@ -332,9 +338,6 @@ name = "cc" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -1074,15 +1077,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" -dependencies = [ - "serde", -] - [[package]] name = "dtoa" version = "0.4.8" @@ -1154,16 +1148,6 @@ dependencies = [ "serde", ] -[[package]] -name = "external-memory" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4dfe8d292b014422776a8c516862d2bff8a81b223a4461dfdc45f3862dc9d39" -dependencies = [ - "bitflags", - "drm-fourcc", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -1182,6 +1166,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "fixedbitset" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" + [[package]] name = "flate2" version = "1.0.21" @@ -1215,6 +1205,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1391,168 +1387,6 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] -[[package]] -name = "gfx-auxil" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1694991b11d642680e82075a75c7c2bd75556b805efa7660b705689f05b1ab1c" -dependencies = [ - "fxhash", - "gfx-hal", - "spirv_cross", -] - -[[package]] -name = "gfx-backend-dx11" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9e453baf3aaef2b0c354ce0b3d63d76402e406a59b64b7182d123cfa6635ae" -dependencies = [ - "arrayvec", - "bitflags", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", - "libloading 0.7.0", - "log", - "parking_lot", - "range-alloc", - "raw-window-handle", - "smallvec", - "spirv_cross", - "thunderdome", - "winapi 0.3.9", - "wio", -] - -[[package]] -name = "gfx-backend-dx12" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21506399f64a3c4d389182a89a30073856ae33eb712315456b4fd8f39ee7682a" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags", - "d3d12", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", - "log", - "parking_lot", - "range-alloc", - "raw-window-handle", - "smallvec", - "spirv_cross", - "thunderdome", - "winapi 0.3.9", -] - -[[package]] -name = "gfx-backend-empty" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c8f813c47791918aa00dc9c9ddf961d23fa8c2a5d869e6cb8ea84f944820f4" -dependencies = [ - "gfx-hal", - "log", - "raw-window-handle", -] - -[[package]] -name = "gfx-backend-gl" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bae057fc3a0ab23ecf97ae51d4017d27d5ddf0aab16ee6dcb58981af88c3152" -dependencies = [ - "arrayvec", - "bitflags", - "fxhash", - "gfx-hal", - "glow", - "js-sys", - "khronos-egl", - "libloading 0.7.0", - "log", - "naga", - "parking_lot", - "raw-window-handle", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gfx-backend-metal" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de85808e2a98994c6af925253f8a9593bc57180ef1ea137deab6d35cc949517" -dependencies = [ - "arrayvec", - "bitflags", - "block", - "cocoa-foundation", - "copyless", - "core-graphics-types", - "foreign-types", - "fxhash", - "gfx-hal", - "log", - "metal", - "naga", - "objc", - "parking_lot", - "profiling", - "range-alloc", - "raw-window-handle", - "storage-map", -] - -[[package]] -name = "gfx-backend-vulkan" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9861ec855acbbc65c0e4f966d761224886e811dc2c6d413a4776e9293d0e5c0" -dependencies = [ - "arrayvec", - "ash", - "byteorder", - "core-graphics-types", - "gfx-hal", - "gfx-renderdoc", - "inplace_it", - "log", - "naga", - "objc", - "parking_lot", - "raw-window-handle", - "smallvec", - "winapi 0.3.9", -] - -[[package]] -name = "gfx-hal" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbb575ea793dd0507b3082f4f2cde62dc9f3cebd98f5cd49ba2a4da97a976fd" -dependencies = [ - "bitflags", - "external-memory", - "naga", - "raw-window-handle", - "thiserror", -] - -[[package]] -name = "gfx-renderdoc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8027995e247e2426d3a00d13f5191dd56c314bff02dc4b54cbf727f1ba9c40a" -dependencies = [ - "libloading 0.7.0", - "log", - "renderdoc-sys", -] - [[package]] name = "ghost" version = "0.1.2" @@ -1583,9 +1417,9 @@ checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" [[package]] name = "glow" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108" +checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520" dependencies = [ "js-sys", "slotmap", @@ -1634,9 +1468,9 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.4.7" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc1b6ca374e81862526786d9cb42357ce03706ed1b8761730caafd02ab91f3a" +checksum = "ab8524eac5fc9d05625c891adf78fcf64dc0ee9f8d0882874b9f220f42b442bf" dependencies = [ "bitflags", "gpu-alloc-types", @@ -1653,9 +1487,9 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" +checksum = "d7a237f0419ab10d17006d55c62ac4f689a6bf52c75d3f38b8361d249e8d4b0b" dependencies = [ "bitflags", "gpu-descriptor-types", @@ -1948,15 +1782,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.54" @@ -2279,13 +2104,14 @@ dependencies = [ "sha2", "smallvec", "thiserror", + "typed-arena", ] [[package]] name = "naga" -version = "0.5.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef670817eef03d356d5a509ea275e7dd3a78ea9e24261ea3cb2dfed1abb08f64" +checksum = "8c5859e55c51da10b98e7a73068e0a0c5da7bbcae4fc38f86043d0c6d1b917cf" dependencies = [ "bit-set", "bitflags", @@ -2293,9 +2119,8 @@ dependencies = [ "fxhash", "log", "num-traits", - "petgraph", - "rose_tree", - "spirv_headers", + "petgraph 0.6.0", + "spirv", "thiserror", ] @@ -2644,7 +2469,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "petgraph", + "petgraph 0.5.1", "redox_syscall", "smallvec", "thread-id", @@ -2706,7 +2531,17 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ - "fixedbitset", + "fixedbitset 0.2.0", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset 0.4.0", "indexmap", ] @@ -3566,6 +3401,7 @@ dependencies = [ "confy", "copypasta", "env_logger 0.8.4", + "fs_extra", "futures", "glyph_brush", "im 15.0.0", @@ -3583,9 +3419,11 @@ dependencies = [ "quickcheck 1.0.3", "quickcheck_macros 1.0.0", "rand 0.8.4", + "roc_builtins", "roc_can", "roc_collections", "roc_fmt", + "roc_load", "roc_module", "roc_parse", "roc_problem", @@ -3597,6 +3435,8 @@ dependencies = [ "ropey", "serde", "snafu", + "tempfile", + "uuid", "ven_graph", "wgpu", "wgpu_glyph", @@ -3942,15 +3782,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "rose_tree" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2" -dependencies = [ - "petgraph", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -4227,9 +4058,12 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "slotmap" -version = "0.4.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf34684c5767b87de9119790e92e9a1d60056be2ceeaf16a8e6ef13082aeab1" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] [[package]] name = "smallvec" @@ -4306,21 +4140,10 @@ dependencies = [ ] [[package]] -name = "spirv_cross" -version = "0.23.1" +name = "spirv" +version = "0.2.0+1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" -dependencies = [ - "cc", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "spirv_headers" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" dependencies = [ "bitflags", "num-traits", @@ -4338,15 +4161,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "storage-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" -dependencies = [ - "lock_api", -] - [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -4565,12 +4379,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "thunderdome" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b4947742c93ece24a0032141d9caa3d853752e694a57e35029dd2bd08673e0" - [[package]] name = "time" version = "0.1.43" @@ -4757,6 +4565,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.3", +] + [[package]] name = "vec_map" version = "0.8.2" @@ -4781,7 +4598,7 @@ dependencies = [ name = "ven_pretty" version = "0.9.1-alpha.0" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "criterion", "difference", "tempfile", @@ -4807,7 +4624,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "utf8parse", "vte_generate_state_changes", ] @@ -5224,9 +5041,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" dependencies = [ "js-sys", "wasm-bindgen", @@ -5234,14 +5051,13 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd247f8b26fd3d42ef2f320d378025cd6e84d782ef749fab45cc3b981fbe3275" +checksum = "3d92a4fe73b1e7d7ef99938dacd49258cbf1ad87cdb5bf6efa20c27447442b45" dependencies = [ - "arrayvec", + "arrayvec 0.7.1", "js-sys", "log", - "naga", "parking_lot", "raw-window-handle", "smallvec", @@ -5249,29 +5065,21 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "wgpu-core", + "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-core" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958a8a5e418492723ab4e7933bf6dbdf06f5dc87274ba2ae0e4f9c891aac579c" +checksum = "5f1b4d918c970526cbc83b72ccb72dbefd38aec45f07b2310de4ffcd7f4bd8c5" dependencies = [ - "arrayvec", + "arrayvec 0.7.1", "bitflags", "cfg_aliases", "copyless", "fxhash", - "gfx-backend-dx11", - "gfx-backend-dx12", - "gfx-backend-empty", - "gfx-backend-gl", - "gfx-backend-metal", - "gfx-backend-vulkan", - "gfx-hal", - "gpu-alloc", - "gpu-descriptor", "log", "naga", "parking_lot", @@ -5279,23 +5087,58 @@ dependencies = [ "raw-window-handle", "smallvec", "thiserror", + "wgpu-hal", "wgpu-types", ] [[package]] -name = "wgpu-types" -version = "0.9.0" +name = "wgpu-hal" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5c9678cd533558e28b416d66947b099742df1939307478db54f867137f1b60" +checksum = "27cd894b17bff1958ee93da1cc991fd64bf99667746d4bd2a7403855f4d37fe2" +dependencies = [ + "arrayvec 0.7.1", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "khronos-egl", + "libloading 0.7.0", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wgpu-types", + "winapi 0.3.9", +] + +[[package]] +name = "wgpu-types" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25feb2fbf24ab3219a9f10890ceb8e1ef02b13314ed89d64a9ae99dcad883e18" dependencies = [ "bitflags", ] [[package]] name = "wgpu_glyph" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fee8c96eda18195a7ad9989737183e0a357f14b15e98838c76abbcf56a5f970" +checksum = "cbf11aebbcf20806535bee127367bcb393c83d77c60c4f7917184d839716cf41" dependencies = [ "bytemuck", "glyph_brush", @@ -5388,15 +5231,6 @@ dependencies = [ "x11-dl", ] -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Earthfile b/Earthfile index c6b754ce64..164f50af88 100644 --- a/Earthfile +++ b/Earthfile @@ -65,7 +65,7 @@ check-rustfmt: RUN cargo fmt --all -- --check check-typos: - RUN cargo install --version 1.0.11 typos-cli + RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ RUN typos @@ -97,11 +97,10 @@ test-all: BUILD +test-zig BUILD +check-rustfmt BUILD +check-clippy - BUILD +check-typos BUILD +test-rust BUILD +verify-no-git-changes -# compile everything needed for benchmarks and output a self-contained folder +# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run. prep-bench-folder: FROM +copy-dirs ARG BENCH_SUFFIX=branch diff --git a/cli/benches/time_bench.rs b/cli/benches/time_bench.rs index 56395acc75..f4c8f6e8be 100644 --- a/cli/benches/time_bench.rs +++ b/cli/benches/time_bench.rs @@ -25,12 +25,12 @@ fn bench_group_wall_time(c: &mut Criterion) { group.sample_size(nr_of_runs); let bench_funcs: Vec>) -> ()> = vec![ - bench_nqueens, // queens 11 - bench_cfold, // e = mkExpr 17 1 - bench_deriv, // nest deriv 8 f - bench_rbtree_ck, // ms = makeMap 5 80000 - bench_rbtree_delete, // m = makeMap 100000 - bench_quicksort, // list size 10000 + bench_nqueens, // queens 11 + bench_cfold, // e = mkExpr 17 1 + bench_deriv, // nest deriv 8 f + bench_rbtree_ck, // ms = makeMap 5 80000 + // bench_rbtree_delete, // m = makeMap 100000 + bench_quicksort, // list size 10000 ]; for bench_func in bench_funcs.iter() { diff --git a/cli/cli_utils/src/bench_utils.rs b/cli/cli_utils/src/bench_utils.rs index 146f500380..b01ec495fd 100644 --- a/cli/cli_utils/src/bench_utils.rs +++ b/cli/cli_utils/src/bench_utils.rs @@ -131,6 +131,7 @@ pub fn bench_rbtree_ck(bench_group_opt: Option<&mut BenchmarkGro ); } +#[allow(dead_code)] pub fn bench_rbtree_delete(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( &example_file("benchmarks", "RBTreeDel.roc"), diff --git a/cli/src/build.rs b/cli/src/build.rs index d0702ce3dd..e62ddb7043 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -3,6 +3,8 @@ use roc_build::{ link::{link, rebuild_host, LinkType}, program, }; +#[cfg(feature = "llvm")] +use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load::file::LoadingProblem; @@ -10,6 +12,7 @@ use roc_mono::ir::OptLevel; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; +#[cfg(feature = "llvm")] use tempfile::Builder; fn report_timing(buf: &mut String, label: &str, duration: Duration) { @@ -240,11 +243,19 @@ pub fn build_file<'a>( })?; BuildOutcome::NoProblems } else { + let mut inputs = vec![ + host_input_path.as_path().to_str().unwrap(), + app_o_file.to_str().unwrap(), + ]; + if matches!(opt_level, OptLevel::Development) { + inputs.push(bitcode::OBJ_PATH); + } + let (mut child, _) = // TODO use lld link( target, binary_path.clone(), - &[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], + &inputs, link_type ) .map_err(|_| { diff --git a/cli/src/main.rs b/cli/src/main.rs index 739b341907..a2e91147b1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -11,12 +11,13 @@ use std::path::{Path, PathBuf}; #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; -#[cfg(feature = "llvm")] -use roc_cli::build; use std::ffi::{OsStr, OsString}; +#[cfg(feature = "llvm")] +use roc_cli::build; + #[cfg(not(feature = "llvm"))] -fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result { +fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result { panic!("Building without LLVM is not currently supported."); } @@ -33,7 +34,7 @@ fn main() -> io::Result<()> { } None => { - launch_editor(&[])?; + launch_editor(None)?; Ok(0) } @@ -90,16 +91,13 @@ If you're building the compiler from source you'll want to do `cargo run [FILE]` .subcommand_matches(CMD_EDIT) .unwrap() .values_of_os(DIRECTORY_OR_FILES) + .map(|mut values| values.next()) { - None => { - launch_editor(&[])?; + Some(Some(os_str)) => { + launch_editor(Some(Path::new(os_str)))?; } - Some(values) => { - let paths = values - .map(|os_str| Path::new(os_str)) - .collect::>(); - - launch_editor(&paths)?; + _ => { + launch_editor(None)?; } } @@ -186,8 +184,8 @@ fn roc_files_recursive>( } #[cfg(feature = "editor")] -fn launch_editor(filepaths: &[&Path]) -> io::Result<()> { - roc_editor::launch(filepaths) +fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> { + roc_editor::launch(project_dir_path) } #[cfg(not(feature = "editor"))] diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index aa7020700f..925af2335d 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -435,77 +435,77 @@ mod cli_run { } benchmarks! { - nqueens => Example { - filename: "NQueens.roc", - executable_filename: "nqueens", - stdin: &["6"], - expected_ending: "4\n", - use_valgrind: true, - }, - cfold => Example { - filename: "CFold.roc", - executable_filename: "cfold", - stdin: &["3"], - expected_ending: "11 & 11\n", - use_valgrind: true, - }, - deriv => Example { - filename: "Deriv.roc", - executable_filename: "deriv", - stdin: &["2"], - expected_ending: "1 count: 6\n2 count: 22\n", - use_valgrind: true, - }, - rbtree_ck => Example { - filename: "RBTreeCk.roc", - executable_filename: "rbtree-ck", - stdin: &["100"], - expected_ending: "10\n", - use_valgrind: true, - }, - rbtree_insert => Example { - filename: "RBTreeInsert.roc", - executable_filename: "rbtree-insert", - stdin: &[], - expected_ending: "Node Black 0 {} Empty Empty\n", - use_valgrind: true, - }, - rbtree_del => Example { - filename: "RBTreeDel.roc", - executable_filename: "rbtree-del", - stdin: &["420"], - expected_ending: "30\n", - use_valgrind: true, - }, - astar => Example { - filename: "TestAStar.roc", - executable_filename: "test-astar", - stdin: &[], - expected_ending: "True\n", - use_valgrind: false, - }, - base64 => Example { - filename: "TestBase64.roc", - executable_filename: "test-base64", - stdin: &[], - expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", - use_valgrind: true, - }, - closure => Example { - filename: "Closure.roc", - executable_filename: "closure", - stdin: &[], - expected_ending: "", - use_valgrind: true, - }, - quicksort_app => Example { - filename: "QuicksortApp.roc", - executable_filename: "quicksortapp", - stdin: &[], - expected_ending: "todo put the correct quicksort answer here", - use_valgrind: true, - }, - } + nqueens => Example { + filename: "NQueens.roc", + executable_filename: "nqueens", + stdin: &["6"], + expected_ending: "4\n", + use_valgrind: true, + }, + cfold => Example { + filename: "CFold.roc", + executable_filename: "cfold", + stdin: &["3"], + expected_ending: "11 & 11\n", + use_valgrind: true, + }, + deriv => Example { + filename: "Deriv.roc", + executable_filename: "deriv", + stdin: &["2"], + expected_ending: "1 count: 6\n2 count: 22\n", + use_valgrind: true, + }, + rbtree_ck => Example { + filename: "RBTreeCk.roc", + executable_filename: "rbtree-ck", + stdin: &["100"], + expected_ending: "10\n", + use_valgrind: true, + }, + rbtree_insert => Example { + filename: "RBTreeInsert.roc", + executable_filename: "rbtree-insert", + stdin: &[], + expected_ending: "Node Black 0 {} Empty Empty\n", + use_valgrind: true, + }, + // rbtree_del => Example { + // filename: "RBTreeDel.roc", + // executable_filename: "rbtree-del", + // stdin: &["420"], + // expected_ending: "30\n", + // use_valgrind: true, + // }, + astar => Example { + filename: "TestAStar.roc", + executable_filename: "test-astar", + stdin: &[], + expected_ending: "True\n", + use_valgrind: false, + }, + base64 => Example { + filename: "TestBase64.roc", + executable_filename: "test-base64", + stdin: &[], + expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", + use_valgrind: true, + }, + closure => Example { + filename: "Closure.roc", + executable_filename: "closure", + stdin: &[], + expected_ending: "", + use_valgrind: true, + }, + quicksort_app => Example { + filename: "QuicksortApp.roc", + executable_filename: "quicksortapp", + stdin: &[], + expected_ending: "todo put the correct quicksort answer here", + use_valgrind: true, + }, + } #[cfg(not(debug_assertions))] fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { @@ -562,10 +562,10 @@ mod cli_run { file.read_exact(buf).unwrap(); // Only app modules in this directory are considered benchmarks. - if "app".as_bytes() == buf { + if "app".as_bytes() == buf && !benchmark_file_name.contains("RBTreeDel") { all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| { - panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); - }); + panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); + }); } } } diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 87d53837a8..bfb04ae0e0 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -502,6 +502,11 @@ mod repl_eval { expect_success("\\x -> x", " : a -> a"); } + #[test] + fn sum_lambda() { + expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); + } + #[test] fn stdlib_function() { expect_success("Num.abs", " : Num a -> Num a"); diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 40a8f84cbf..d7a894838c 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -1,7 +1,8 @@ use crate::target::arch_str; #[cfg(feature = "llvm")] use libloading::{Error, Library}; -#[cfg(feature = "llvm")] +use roc_builtins::bitcode; +// #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; use std::collections::HashMap; use std::env; @@ -93,7 +94,12 @@ pub fn build_zig_host_native( .env("PATH", env_path) .env("HOME", env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); + command.args(&[ + "build-exe", + "-fPIE", + shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, + ]); } else { command.args(&["build-obj", "-fPIC"]); } @@ -109,7 +115,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "--strip", // cross-compile? "-target", target, @@ -178,7 +183,12 @@ pub fn build_zig_host_native( .env("PATH", &env_path) .env("HOME", &env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); + command.args(&[ + "build-exe", + "-fPIE", + shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, + ]); } else { command.args(&["build-obj", "-fPIC"]); } @@ -197,7 +207,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "--strip", ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); @@ -274,6 +283,7 @@ pub fn build_c_host_native( if let Some(shared_lib_path) = shared_lib_path { command.args(&[ shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, "-fPIE", "-pie", "-lm", diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 226bfaf8be..7d9803e4be 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -2,11 +2,9 @@ use roc_gen_llvm::llvm::build::module_from_builtins; #[cfg(feature = "llvm")] pub use roc_gen_llvm::llvm::build::FunctionIterator; -#[cfg(feature = "llvm")] use roc_load::file::MonomorphizedModule; #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; -#[cfg(feature = "llvm")] use std::path::{Path, PathBuf}; use std::time::Duration; diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 22e76054ea..6e6351a6e7 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -1,6 +1,7 @@ const std = @import("std"); const utils = @import("utils.zig"); const RocResult = utils.RocResult; +const UpdateMode = utils.UpdateMode; const mem = std.mem; const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; @@ -52,6 +53,14 @@ pub const RocList = extern struct { }; } + pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) RocList { + if (update_mode == .InPlace) { + return self; + } else { + return self.makeUnique(alignment, element_width); + } + } + pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList { if (self.isEmpty()) { return self; @@ -132,14 +141,14 @@ const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; -pub fn listReverse(list: RocList, alignment: u32, element_width: usize) callconv(.C) RocList { +pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { if (list.bytes) |source_ptr| { const size = list.len(); var i: usize = 0; const end: usize = size - 1; - if (list.isUnique()) { + if (update_mode == .InPlace or list.isUnique()) { // Working from the front and back so // we only need to go ~(n / 2) iterations. @@ -720,10 +729,13 @@ pub fn listSingle(alignment: u32, element: Opaque, element_width: usize) callcon return output; } -pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { +pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { const old_length = list.len(); var output = list.reallocate(alignment, old_length + 1, element_width); + // we'd need capacity to use update_mode here + _ = update_mode; + if (output.bytes) |target| { if (element) |source| { @memcpy(target + old_length * element_width, source, element_width); @@ -763,18 +775,24 @@ pub fn listSwap( element_width: usize, index_1: usize, index_2: usize, + update_mode: UpdateMode, ) callconv(.C) RocList { const size = list.len(); - if (index_1 >= size or index_2 >= size) { + if (index_1 == index_2 or index_1 >= size or index_2 >= size) { // Either index out of bounds so we just return return list; } - const newList = list.makeUnique(alignment, element_width); + const newList = blk: { + if (update_mode == .InPlace) { + break :blk list; + } else { + break :blk list.makeUnique(alignment, element_width); + } + }; - if (newList.bytes) |source_ptr| { - swapElements(source_ptr, element_width, index_1, index_2); - } + const source_ptr = @ptrCast([*]u8, newList.bytes); + swapElements(source_ptr, element_width, index_1, index_2); return newList; } diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 69bf1ca5ac..0841a8e8f5 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -101,6 +101,7 @@ comptime { exportStrFn(str.strToUtf8C, "to_utf8"); exportStrFn(str.fromUtf8C, "from_utf8"); exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); + exportStrFn(str.repeat, "repeat"); } // Utils @@ -164,7 +165,12 @@ test "" { // https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig // // Thank you Zig Contributors! -export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { + +// Export it as weak incase it is alreadly linked in by something else. +comptime { + @export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak }); +} +fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { // @setRuntimeSafety(std.builtin.is_test); const min = @bitCast(i128, @as(u128, 1 << (128 - 1))); diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 2172e693d7..9ffb9a1375 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1,5 +1,6 @@ const utils = @import("utils.zig"); const RocList = @import("list.zig").RocList; +const UpdateMode = utils.UpdateMode; const std = @import("std"); const mem = std.mem; const always_inline = std.builtin.CallOptions.Modifier.always_inline; @@ -866,6 +867,22 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool { return true; } +// Str.repeat +pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr { + const bytes_len = string.len(); + const bytes_ptr = string.asU8ptr(); + + var ret_string = RocStr.allocate(.Clone, count * bytes_len); + var ret_string_ptr = ret_string.asU8ptr(); + + var i: usize = 0; + while (i < count) : (i += 1) { + @memcpy(ret_string_ptr + (i * bytes_len), bytes_ptr, bytes_len); + } + + return ret_string; +} + // Str.startsWithCodePt pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool { const bytes_ptr = string.asU8ptr(); @@ -1131,10 +1148,10 @@ test "RocStr.joinWith: result is big" { // Str.toUtf8 pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList { - return @call(.{ .modifier = always_inline }, strToBytes, .{arg}); + return strToBytes(arg); } -fn strToBytes(arg: RocStr) RocList { +inline fn strToBytes(arg: RocStr) RocList { if (arg.isEmpty()) { return RocList.empty(); } else if (arg.isSmallStr()) { @@ -1161,11 +1178,11 @@ const CountAndStart = extern struct { start: usize, }; -pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { - output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); +pub fn fromUtf8C(arg: RocList, update_mode: UpdateMode, output: *FromUtf8Result) callconv(.C) void { + output.* = fromUtf8(arg, update_mode); } -fn fromUtf8(arg: RocList) FromUtf8Result { +inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result { const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length]; if (unicode.utf8ValidateSlice(bytes)) { @@ -1178,13 +1195,23 @@ fn fromUtf8(arg: RocList) FromUtf8Result { const data_bytes = arg.len(); utils.decref(arg.bytes, data_bytes, RocStr.alignment); - return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + return FromUtf8Result{ + .is_ok = true, + .string = string, + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; } else { - const byte_list = arg.makeUnique(RocStr.alignment, @sizeOf(u8)); + const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode); const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length }; - return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + return FromUtf8Result{ + .is_ok = true, + .string = string, + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; } } else { const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); @@ -1193,7 +1220,12 @@ fn fromUtf8(arg: RocList) FromUtf8Result { const data_bytes = arg.len(); utils.decref(arg.bytes, data_bytes, RocStr.alignment); - return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem }; + return FromUtf8Result{ + .is_ok = false, + .string = RocStr.empty(), + .byte_index = temp.index, + .problem_code = temp.problem, + }; } } @@ -1276,11 +1308,11 @@ pub const Utf8ByteProblem = enum(u8) { }; fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result { - return fromUtf8(RocList{ .bytes = bytes, .length = length }); + return fromUtf8(RocList{ .bytes = bytes, .length = length }, .Immutable); } fn validateUtf8BytesX(str: RocList) FromUtf8Result { - return fromUtf8(str); + return fromUtf8(str, .Immutable); } fn expectOk(result: FromUtf8Result) !void { diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 67240bdb2d..1371afe674 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -256,3 +256,8 @@ pub const Ordering = enum(u8) { GT = 1, LT = 2, }; + +pub const UpdateMode = extern enum(u8) { + Immutable = 0, + InPlace = 1, +}; diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index b45d822c69..fd1ac7b17c 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -28,6 +28,7 @@ pub const STR_EQUAL: &str = "roc_builtins.str.equal"; pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range"; +pub const STR_REPEAT: &str = "roc_builtins.str.repeat"; pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 98762b568b..7615879227 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -618,6 +618,13 @@ pub fn types() -> MutMap { Box::new(str_type()) ); + // repeat : Str, Nat -> Str + add_top_level_function_type!( + Symbol::STR_REPEAT, + vec![str_type(), nat_type()], + Box::new(str_type()) + ); + // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* { let bad_utf8 = SolvedType::TagUnion( diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index fe45f1f673..d4fff2ef74 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -66,6 +66,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_FROM_UTF8_RANGE => str_from_utf8_range, STR_TO_UTF8 => str_to_utf8, STR_FROM_FLOAT=> str_from_float, + STR_REPEAT => str_repeat, LIST_LEN => list_len, LIST_GET => list_get, LIST_SET => list_set, @@ -1233,6 +1234,26 @@ fn str_split(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Str.repeat : Str, Nat -> Str +fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def { + let str_var = var_store.fresh(); + let nat_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::StrRepeat, + args: vec![(str_var, Var(Symbol::ARG_1)), (nat_var, Var(Symbol::ARG_2))], + ret_var: str_var, + }; + + defn( + symbol, + vec![(str_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)], + var_store, + body, + str_var, + ) +} + /// Str.concat : Str, Str -> Str fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { let str_var = var_store.fresh(); diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 6149bff14c..32273fbf91 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -226,6 +226,7 @@ impl CallConv for AArch64Call { #[inline(always)] fn load_args<'a>( + _buf: &mut Vec<'a, u8>, _symbol_map: &mut MutMap>, _args: &'a [(Layout<'a>, Symbol)], _ret_layout: &Layout<'a>, diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 9f98f512be..008633c202 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -9,7 +9,7 @@ use std::marker::PhantomData; pub mod aarch64; pub mod x86_64; -const PTR_SIZE: u32 = 64; +const PTR_SIZE: u32 = 8; pub trait CallConv { const GENERAL_PARAM_REGS: &'static [GeneralReg]; @@ -48,6 +48,7 @@ pub trait CallConv { // load_args updates the symbol map to know where every arg is stored. fn load_args<'a>( + buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. @@ -422,7 +423,12 @@ impl< args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, ) -> Result<(), String> { - CC::load_args(&mut self.symbol_storage_map, args, ret_layout)?; + CC::load_args( + &mut self.buf, + &mut self.symbol_storage_map, + args, + ret_layout, + )?; // Update used and free regs. for (sym, storage) in &self.symbol_storage_map { match storage { @@ -489,6 +495,25 @@ impl< ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); Ok(()) } + Layout::Builtin(Builtin::Str) => { + if CC::returns_via_arg_pointer(ret_layout)? { + // This will happen on windows, return via pointer here. + Err("FnCall: Returning strings via pointer not yet implemented".to_string()) + } else { + let offset = self.claim_stack_size(16)?; + self.symbol_storage_map.insert( + *dst, + SymbolStorage::Base { + offset, + size: 16, + owned: true, + }, + ); + ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); + ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); + Ok(()) + } + } x => Err(format!( "FnCall: receiving return type, {:?}, is not yet implemented", x @@ -893,6 +918,35 @@ impl< ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val); Ok(()) } + Literal::Str(x) if x.len() < 16 => { + // Load small string. + let reg = self.get_tmp_general_reg()?; + + let offset = self.claim_stack_size(16)?; + self.symbol_storage_map.insert( + *sym, + SymbolStorage::Base { + offset, + size: 16, + owned: true, + }, + ); + let mut bytes = [0; 16]; + bytes[..x.len()].copy_from_slice(x.as_bytes()); + bytes[15] = (x.len() as u8) | 0b1000_0000; + + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(&mut self.buf, reg, num); + ASM::mov_base32_reg64(&mut self.buf, offset, reg); + + num_bytes.copy_from_slice(&bytes[8..]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(&mut self.buf, reg, num); + ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); + Ok(()) + } x => Err(format!("loading literal, {:?}, is not yet implemented", x)), } } @@ -1012,6 +1066,19 @@ impl< Layout::Builtin(Builtin::Float64) => { ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); } + Layout::Builtin(Builtin::Str) => { + if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { + // This will happen on windows, return via pointer here. + return Err("Returning strings via pointer not yet implemented".to_string()); + } else { + ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); + ASM::mov_reg64_base32( + &mut self.buf, + CC::GENERAL_RETURN_REGS[1], + *offset + 8, + ); + } + } Layout::Struct(field_layouts) => { let (offset, size) = (*offset, *size); // Nothing to do for empty struct @@ -1446,8 +1513,6 @@ macro_rules! single_register_integers { #[macro_export] macro_rules! single_register_floats { () => { - // Float16 is explicitly ignored because it is not supported by must hardware and may require special exceptions. - // Builtin::Float16 | Builtin::Float32 | Builtin::Float64 }; } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index a39a828f8a..a30a1a4e9c 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -177,6 +177,7 @@ impl CallConv for X86_64SystemV { #[inline(always)] fn load_args<'a>( + buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, @@ -231,6 +232,29 @@ impl CallConv for X86_64SystemV { ); } } + Layout::Builtin(Builtin::Str) => { + if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { + // Load the value to the param reg. + let dst1 = Self::GENERAL_PARAM_REGS[general_i]; + let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1]; + base_offset += 16; + X86_64Assembler::mov_reg64_base32(buf, dst1, base_offset - 8); + X86_64Assembler::mov_reg64_base32(buf, dst2, base_offset); + symbol_map.insert( + *sym, + SymbolStorage::Base { + offset: base_offset, + size: 16, + owned: true, + }, + ); + general_i += 2; + } else { + return Err( + "loading strings args on the stack is not yet implemented".to_string() + ); + } + } Layout::Struct(&[]) => {} x => { return Err(format!( @@ -257,7 +281,7 @@ impl CallConv for X86_64SystemV { // For most return layouts we will do nothing. // In some cases, we need to put the return address as the first arg. match ret_layout { - Layout::Builtin(single_register_builtins!()) => {} + Layout::Builtin(single_register_builtins!() | Builtin::Str) => {} x => { return Err(format!( "receiving return type, {:?}, is not yet implemented", @@ -373,6 +397,32 @@ impl CallConv for X86_64SystemV { stack_offset += 8; } } + Layout::Builtin(Builtin::Str) => { + if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { + // Load the value to the param reg. + let dst1 = Self::GENERAL_PARAM_REGS[general_i]; + let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1]; + match symbol_map + .get(&args[i]) + .ok_or("function argument does not reference any symbol")? + { + SymbolStorage::Base { offset, .. } => { + X86_64Assembler::mov_reg64_base32(buf, dst1, *offset); + X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8); + } + _ => { + return Err("Strings only support being loaded from base offsets" + .to_string()); + } + } + general_i += 2; + } else { + return Err( + "calling functions with strings on the stack is not yet implemented" + .to_string(), + ); + } + } Layout::Struct(&[]) => {} x => { return Err(format!( @@ -516,6 +566,7 @@ impl CallConv for X86_64WindowsFastcall { #[inline(always)] fn load_args<'a>( + _buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, @@ -535,9 +586,18 @@ impl CallConv for X86_64WindowsFastcall { Layout::Builtin(single_register_integers!()) => { symbol_map .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); + i += 1; } Layout::Builtin(single_register_floats!()) => { symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); + i += 1; + } + Layout::Builtin(Builtin::Str) => { + // I think this just needs to be passed on the stack, so not a huge deal. + return Err( + "Passing str args with Windows fast call not yet implemented." + .to_string(), + ); } Layout::Struct(&[]) => {} x => { @@ -547,7 +607,6 @@ impl CallConv for X86_64WindowsFastcall { )); } } - i += 1; } else { base_offset += match layout { Layout::Builtin(single_register_builtins!()) => 8, @@ -580,7 +639,6 @@ impl CallConv for X86_64WindowsFastcall { ret_layout: &Layout<'a>, ) -> Result { let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; - let mut reg_i = 0; // For most return layouts we will do nothing. // In some cases, we need to put the return address as the first arg. match ret_layout { @@ -597,7 +655,7 @@ impl CallConv for X86_64WindowsFastcall { Layout::Builtin(single_register_integers!()) => { if i < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. - let dst = Self::GENERAL_PARAM_REGS[reg_i]; + let dst = Self::GENERAL_PARAM_REGS[i]; match symbol_map .get(&args[i]) .ok_or("function argument does not reference any symbol")? @@ -615,7 +673,6 @@ impl CallConv for X86_64WindowsFastcall { ) } } - reg_i += 1; } else { // Load the value to the stack. match symbol_map @@ -651,7 +708,7 @@ impl CallConv for X86_64WindowsFastcall { Layout::Builtin(single_register_floats!()) => { if i < Self::FLOAT_PARAM_REGS.len() { // Load the value to the param reg. - let dst = Self::FLOAT_PARAM_REGS[reg_i]; + let dst = Self::FLOAT_PARAM_REGS[i]; match symbol_map .get(&args[i]) .ok_or("function argument does not reference any symbol")? @@ -668,7 +725,6 @@ impl CallConv for X86_64WindowsFastcall { return Err("Cannot load general symbol into FloatReg".to_string()) } } - reg_i += 1; } else { // Load the value to the stack. match symbol_map @@ -700,6 +756,12 @@ impl CallConv for X86_64WindowsFastcall { stack_offset += 8; } } + Layout::Builtin(Builtin::Str) => { + // I think this just needs to be passed on the stack, so not a huge deal. + return Err( + "Passing str args with Windows fast call not yet implemented.".to_string(), + ); + } Layout::Struct(&[]) => {} x => { return Err(format!( diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 010fa066d3..5ef03a3a42 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -93,12 +93,8 @@ where for (layout, sym) in proc.args { self.set_layout_map(*sym, layout)?; } - // let start = std::time::Instant::now(); self.scan_ast(&proc.body); self.create_free_map(); - // let duration = start.elapsed(); - // println!("Time to calculate lifetimes: {:?}", duration); - // println!("{:?}", self.last_seen_map()); self.build_stmt(&proc.body, &proc.ret_layout)?; self.finalize() } @@ -119,6 +115,11 @@ where self.free_symbols(stmt)?; Ok(()) } + Stmt::Refcounting(_modify, following) => { + // TODO: actually deal with refcounting. For hello world, we just skipped it. + self.build_stmt(following, ret_layout)?; + Ok(()) + } Stmt::Switch { cond_symbol, cond_layout, @@ -298,6 +299,13 @@ where arg_layouts, ret_layout, ), + Symbol::STR_CONCAT => self.build_run_low_level( + sym, + &LowLevel::StrConcat, + arguments, + arg_layouts, + ret_layout, + ), x if x .module_string(&self.env().interns) .starts_with(ModuleName::APP) => @@ -470,6 +478,13 @@ where arg_layouts, ret_layout, ), + LowLevel::StrConcat => self.build_fn_call( + sym, + bitcode::STR_CONCAT.to_string(), + args, + arg_layouts, + ret_layout, + ), x => Err(format!("low level, {:?}. is not yet implemented", x)), } } diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index c223acbcdf..c82d2ca23f 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -1,12 +1,6 @@ -#[macro_use] -extern crate pretty_assertions; - #[macro_use] extern crate indoc; -extern crate bumpalo; -extern crate libc; - #[macro_use] mod helpers; diff --git a/compiler/gen_dev/tests/dev_str.rs b/compiler/gen_dev/tests/dev_str.rs new file mode 100644 index 0000000000..19da650a89 --- /dev/null +++ b/compiler/gen_dev/tests/dev_str.rs @@ -0,0 +1,954 @@ +// #[macro_use] +// extern crate indoc; + +#[macro_use] +mod helpers; + +#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] +mod dev_str { + // use roc_std::{RocList, RocStr}; + // #[test] + // fn str_split_bigger_delimiter_small_str() { + // assert_evals_to!( + // indoc!( + // r#" + // List.len (Str.split "hello" "JJJJ there") + // "# + // ), + // 1, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // when List.first (Str.split "JJJ" "JJJJ there") is + // Ok str -> + // Str.countGraphemes str + + // _ -> + // -1 + + // "# + // ), + // 3, + // i64 + // ); + // } + + // #[test] + // fn str_split_str_concat_repeated() { + // assert_evals_to!( + // indoc!( + // r#" + // when List.first (Str.split "JJJJJ" "JJJJ there") is + // Ok str -> + // str + // |> Str.concat str + // |> Str.concat str + // |> Str.concat str + // |> Str.concat str + + // _ -> + // "Not Str!" + + // "# + // ), + // RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"), + // RocStr + // ); + // } + + // #[test] + // fn str_split_small_str_bigger_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // when + // List.first + // (Str.split "JJJ" "0123456789abcdefghi") + // is + // Ok str -> str + // _ -> "" + // "# + // ), + // RocStr::from_slice(b"JJJ"), + // RocStr + // ); + // } + + // #[test] + // fn str_split_big_str_small_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "01234567789abcdefghi?01234567789abcdefghi" "?" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"01234567789abcdefghi"), + // RocStr::from_slice(b"01234567789abcdefghi") + // ]), + // RocList + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"01234567789abcdefghi "), + // RocStr::from_slice(b" 01234567789abcdefghi") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_small_str_small_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "J!J!J" "!" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"J"), + // RocStr::from_slice(b"J"), + // RocStr::from_slice(b"J") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_bigger_delimiter_big_strs() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "string to split is shorter" + // "than the delimiter which happens to be very very long" + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]), + // RocList + // ); + // } + + // #[test] + // fn str_split_empty_strs() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "" "" + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"")]), + // RocList + // ); + // } + + // #[test] + // fn str_split_minimal_example() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "a," "," + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]), + // RocList + // ) + // } + + // #[test] + // fn str_split_small_str_big_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + // "---- ---- ---- ---- ----" + // |> List.len + // "# + // ), + // 3, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + // "---- ---- ---- ---- ----" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"1"), + // RocStr::from_slice(b"2"), + // RocStr::from_slice(b"") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_small_str_20_char_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |" + // "|-- -- -- -- -- -- |" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"3"), + // RocStr::from_slice(b"4"), + // RocStr::from_slice(b"") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_concat_big_to_big() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.concat + // "First string that is fairly long. Longer strings make for different errors. " + // "Second string that is also fairly long. Two long strings test things that might not appear with short strings." + // "# + // ), + // RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."), + // RocStr + // ); + // } + + #[test] + fn small_str_literal() { + assert_evals_to!( + "\"JJJJJJJJJJJJJJJ\"", + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } + + // #[test] + // fn small_str_zeroed_literal() { + // // Verifies that we zero out unused bytes in the string. + // // This is important so that string equality tests don't randomly + // // fail due to unused memory being there! + // assert_evals_to!( + // "\"J\"", + // [ + // 0x4a, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0b1000_0001 + // ], + // [u8; 16] + // ); + // } + + #[test] + fn small_str_concat_empty_first_arg() { + assert_evals_to!( + r#"Str.concat "" "JJJJJJJJJJJJJJJ""#, + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } + + #[test] + fn small_str_concat_empty_second_arg() { + assert_evals_to!( + r#"Str.concat "JJJJJJJJJJJJJJJ" """#, + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } + + // #[test] + // fn small_str_concat_small_to_big() { + // assert_evals_to!( + // r#"Str.concat "abc" " this is longer than 15 chars""#, + // RocStr::from_slice(b"abc this is longer than 15 chars"), + // RocStr + // ); + // } + + #[test] + fn small_str_concat_small_to_small_staying_small() { + assert_evals_to!( + r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } + + // #[test] + // fn small_str_concat_small_to_small_overflow_to_big() { + // assert_evals_to!( + // r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#, + // RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"), + // RocStr + // ); + // } + + // #[test] + // fn str_concat_empty() { + // assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr); + // } + + // #[test] + // fn small_str_is_empty() { + // assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool); + // } + + // #[test] + // fn big_str_is_empty() { + // assert_evals_to!( + // r#"Str.isEmpty "this is more than 15 chars long""#, + // false, + // bool + // ); + // } + + // #[test] + // fn empty_str_is_empty() { + // assert_evals_to!(r#"Str.isEmpty """#, true, bool); + // } + + // #[test] + // fn str_starts_with() { + // assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool); + // assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool); + // assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool); + // } + + // #[test] + // fn str_starts_with_code_point() { + // assert_evals_to!( + // &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32), + // true, + // bool + // ); + // assert_evals_to!( + // &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32), + // false, + // bool + // ); + // } + + // #[test] + // fn str_ends_with() { + // assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool); + // assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool); + // } + + // #[test] + // fn str_count_graphemes_small_str() { + // assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize); + // } + + // #[test] + // fn str_count_graphemes_three_js() { + // assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize); + // } + + // #[test] + // fn str_count_graphemes_big_str() { + // assert_evals_to!( + // r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#, + // 45, + // usize + // ); + // } + + // #[test] + // fn str_starts_with_same_big_str() { + // assert_evals_to!( + // r#"Str.startsWith "123456789123456789" "123456789123456789""#, + // true, + // bool + // ); + // } + + // #[test] + // fn str_starts_with_different_big_str() { + // assert_evals_to!( + // r#"Str.startsWith "12345678912345678910" "123456789123456789""#, + // true, + // bool + // ); + // } + + // #[test] + // fn str_starts_with_same_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); + // } + + // #[test] + // fn str_starts_with_different_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); + // } + // #[test] + // fn str_starts_with_false_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); + // } + + // #[test] + // fn str_from_int() { + // assert_evals_to!( + // r#"Str.fromInt 1234"#, + // roc_std::RocStr::from_slice("1234".as_bytes()), + // roc_std::RocStr + // ); + // assert_evals_to!( + // r#"Str.fromInt 0"#, + // roc_std::RocStr::from_slice("0".as_bytes()), + // roc_std::RocStr + // ); + // assert_evals_to!( + // r#"Str.fromInt -1"#, + // roc_std::RocStr::from_slice("-1".as_bytes()), + // roc_std::RocStr + // ); + + // let max = format!("{}", i64::MAX); + // assert_evals_to!( + // r#"Str.fromInt Num.maxInt"#, + // RocStr::from_slice(max.as_bytes()), + // RocStr + // ); + + // let min = format!("{}", i64::MIN); + // assert_evals_to!( + // r#"Str.fromInt Num.minInt"#, + // RocStr::from_slice(min.as_bytes()), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_ascii() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_ascii() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("abc~".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_unicode() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("∆".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_unicode() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("∆œ¬".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_grapheme() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_grapheme() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_all() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖b∆".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_invalid_start_byte() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is + // Err (BadUtf8 InvalidStartByte byteIndex) -> + // if byteIndex == 2 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_unexpected_end_of_sequence() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is + // Err (BadUtf8 UnexpectedEndOfSequence byteIndex) -> + // if byteIndex == 3 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_expected_continuation() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is + // Err (BadUtf8 ExpectedContinuation byteIndex) -> + // if byteIndex == 3 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_overlong_encoding() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is + // Err (BadUtf8 OverlongEncoding byteIndex) -> + // if byteIndex == 1 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_codepoint_too_large() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is + // Err (BadUtf8 CodepointTooLarge byteIndex) -> + // if byteIndex == 1 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_surrogate_half() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is + // Err (BadUtf8 EncodesSurrogateHalf byteIndex) -> + // if byteIndex == 2 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_equality() { + // assert_evals_to!(r#""a" == "a""#, true, bool); + // assert_evals_to!( + // r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#, + // true, + // bool + // ); + // assert_evals_to!(r#""a" != "b""#, true, bool); + // assert_evals_to!(r#""a" == "b""#, false, bool); + // } + + // #[test] + // fn str_clone() { + // use roc_std::RocStr; + // let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes()); + // let short = RocStr::from_slice("x".as_bytes()); + // let empty = RocStr::from_slice("".as_bytes()); + + // debug_assert_eq!(long.clone(), long); + // debug_assert_eq!(short.clone(), short); + // debug_assert_eq!(empty.clone(), empty); + // } + + // #[test] + // fn nested_recursive_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // Expr : [ Add Expr Expr, Val I64, Var I64 ] + + // expr : Expr + // expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1)) + + // printExpr : Expr -> Str + // printExpr = \e -> + // when e is + // Add a b -> + // "Add (" + // |> Str.concat (printExpr a) + // |> Str.concat ") (" + // |> Str.concat (printExpr b) + // |> Str.concat ")" + // Val v -> "Val " |> Str.concat (Str.fromInt v) + // Var v -> "Var " |> Str.concat (Str.fromInt v) + + // printExpr expr + // "# + // ), + // RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_small() { + // assert_evals_to!( + // r#"Str.joinWith ["1", "2"] ", " "#, + // RocStr::from("1, 2"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_big() { + // assert_evals_to!( + // r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#, + // RocStr::from("10000000, 2000000, 30000000"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_single() { + // assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); + // } + + // #[test] + // fn str_from_float() { + // assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr); + // } + + // #[test] + // fn str_to_utf8() { + // assert_evals_to!( + // r#"Str.toUtf8 "hello""#, + // RocList::from_slice(&[104, 101, 108, 108, 111]), + // RocList + // ); + // assert_evals_to!( + // r#"Str.toUtf8 "this is a long string""#, + // RocList::from_slice(&[ + // 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116, + // 114, 105, 110, 103 + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_from_utf8_range() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 5, start: 0 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("hello"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_slice() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 4, start: 1 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ello"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_slice_not_end() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 3, start: 1 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ell"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_order_does_not_matter() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 1, count: 3 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ell"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_out_of_bounds_start_value() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 7, count: 3 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_count_too_high() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 0, count: 6 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_count_too_high_for_start() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 4, count: 3 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } +} diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 4864b04fcb..310fd2cd51 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -15,7 +15,7 @@ use crate::llvm::build_list::{ }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, - str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_split, + str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, str_starts_with, str_starts_with_code_point, str_to_utf8, }; use crate::llvm::compare::{generic_eq, generic_neq}; @@ -708,7 +708,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>( env, main_fn_name, roc_main_fn, - &[], + top_level.arguments, top_level.result, main_fn_name, ); @@ -934,7 +934,9 @@ pub fn build_exp_call<'a, 'ctx, 'env>( CallType::LowLevel { op, update_mode } => { let bytes = update_mode.to_bytes(); let update_var = UpdateModeVar(&bytes); - let update_mode = func_spec_solutions.update_mode(update_var).ok(); + let update_mode = func_spec_solutions + .update_mode(update_var) + .unwrap_or(UpdateMode::Immutable); run_low_level( env, @@ -2185,7 +2187,10 @@ fn list_literal<'a, 'ctx, 'env>( let list_length = elems.len(); let list_length_intval = env.ptr_int().const_int(list_length as _, false); - if element_type.is_int_type() { + // TODO re-enable, currently causes morphic segfaults because it tries to update + // constants in-place... + // if element_type.is_int_type() { + if false { let element_type = element_type.into_int_type(); let element_width = elem_layout.stack_size(env.ptr_bytes); let size = list_length * element_width as usize; @@ -2226,17 +2231,18 @@ fn list_literal<'a, 'ctx, 'env>( } ListLiteralElement::Symbol(symbol) => { let val = load_symbol(scope, symbol); - let intval = val.into_int_value(); - if intval.is_const() { - global_elements.push(intval); - } else { - is_all_constant = false; + // here we'd like to furthermore check for intval.is_const(). + // if all elements are const for LLVM, we could make the array a constant. + // BUT morphic does not know about this, and could allow us to modify that + // array in-place. That would cause a segfault. So, we'll have to find + // constants ourselves and cannot lean on LLVM here. - runtime_evaluated_elements.push((index, val)); + is_all_constant = false; - global_elements.push(element_type.get_undef()); - } + runtime_evaluated_elements.push((index, val)); + + global_elements.push(element_type.get_undef()); } }; } @@ -3210,6 +3216,141 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( c_function } +fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + ident_string: &str, + roc_function: FunctionValue<'ctx>, + arguments: &[Layout<'a>], + c_function_name: &str, +) -> FunctionValue<'ctx> { + let context = env.context; + + // a tagged union to indicate to the test loader that a panic occurred. + // especially when running 32-bit binaries on a 64-bit machine, there + // does not seem to be a smarter solution + let wrapper_return_type = context.struct_type( + &[ + context.i64_type().into(), + roc_function.get_type().get_return_type().unwrap(), + ], + false, + ); + + let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); + for layout in arguments { + cc_argument_types.push(to_cc_type(env, layout)); + } + + // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it + let mut argument_types = cc_argument_types; + let return_type = wrapper_return_type; + + let c_function_type = { + let output_type = return_type.ptr_type(AddressSpace::Generic); + argument_types.push(output_type.into()); + env.context.void_type().fn_type(&argument_types, false) + }; + + let c_function = add_func( + env.module, + c_function_name, + c_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(c_function_name); + c_function.set_subprogram(subprogram); + + // STEP 2: build the exposed function's body + let builder = env.builder; + let context = env.context; + + let entry = context.append_basic_block(c_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, c_function); + + // drop the final argument, which is the pointer we write the result into + let args_vector = c_function.get_params(); + let mut args = args_vector.as_slice(); + let args_length = args.len(); + + args = &args[..args.len() - 1]; + + let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); + + let it = args.iter().zip(roc_function.get_type().get_param_types()); + for (arg, fastcc_type) in it { + let arg_type = arg.get_type(); + if arg_type == fastcc_type { + // the C and Fast calling conventions agree + arguments_for_call.push(*arg); + } else { + let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type"); + arguments_for_call.push(cast); + } + } + + let arguments_for_call = &arguments_for_call.into_bump_slice(); + + let call_result = { + let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!( + arguments_for_call.len(), + roc_wrapper_function.get_params().len() + ); + + builder.position_at_end(entry); + + let call_wrapped = builder.build_call( + roc_wrapper_function, + arguments_for_call, + "call_wrapped_function", + ); + call_wrapped.set_call_convention(FAST_CALL_CONV); + + call_wrapped.try_as_basic_value().left().unwrap() + }; + + let output_arg_index = args_length - 1; + + let output_arg = c_function + .get_nth_param(output_arg_index as u32) + .unwrap() + .into_pointer_value(); + + builder.build_store(output_arg, call_result); + builder.build_return(None); + + // STEP 3: build a {} -> u64 function that gives the size of the return type + let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_name: String = format!("roc__{}_size", ident_string); + + let size_function = add_func( + env.module, + size_function_name.as_str(), + size_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(&size_function_name); + size_function.set_subprogram(subprogram); + + let entry = context.append_basic_block(size_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, size_function); + + let size: BasicValueEnum = return_type.size_of().unwrap().into(); + builder.build_return(Some(&size)); + + c_function +} + fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, ident_string: &str, @@ -3220,16 +3361,24 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ) -> FunctionValue<'ctx> { let context = env.context; - // a generic version that writes the result into a passed *u8 pointer - if !env.is_gen_test { - expose_function_to_host_help_c_abi_generic( + if env.is_gen_test { + return expose_function_to_host_help_c_abi_gen_test( env, + ident_string, roc_function, arguments, - &format!("{}_generic", c_function_name), + c_function_name, ); } + // a generic version that writes the result into a passed *u8 pointer + expose_function_to_host_help_c_abi_generic( + env, + roc_function, + arguments, + &format!("{}_generic", c_function_name), + ); + let wrapper_return_type = if env.is_gen_test { context .struct_type( @@ -3256,11 +3405,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let cc_return = to_cc_return(env, &return_layout); let c_function_type = match cc_return { - CCReturn::Void if !env.is_gen_test => { - env.context.void_type().fn_type(&argument_types, false) - } - CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false), - _ => { + CCReturn::Void => env.context.void_type().fn_type(&argument_types, false), + CCReturn::Return => return_type.fn_type(&argument_types, false), + CCReturn::ByPointer => { let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); env.context.void_type().fn_type(&argument_types, false) @@ -3294,13 +3441,13 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let args_length = args.len(); match cc_return { - CCReturn::Return if !env.is_gen_test => { + CCReturn::Return => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - CCReturn::Void if !env.is_gen_test => { + CCReturn::Void => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - _ => { + CCReturn::ByPointer => { args = &args[..args.len() - 1]; debug_assert_eq!(args.len(), roc_function.get_params().len()); } @@ -3323,44 +3470,25 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = { - if env.is_gen_test { - let roc_wrapper_function = make_exception_catcher(env, roc_function); - debug_assert_eq!( - arguments_for_call.len(), - roc_wrapper_function.get_params().len() - ); + let call_unwrapped = + builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function"); + call_unwrapped.set_call_convention(FAST_CALL_CONV); - builder.position_at_end(entry); + let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); - let call_wrapped = builder.build_call( - roc_wrapper_function, - arguments_for_call, - "call_wrapped_function", - ); - call_wrapped.set_call_convention(FAST_CALL_CONV); - - call_wrapped.try_as_basic_value().left().unwrap() - } else { - let call_unwrapped = - builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function"); - call_unwrapped.set_call_convention(FAST_CALL_CONV); - - let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); - - // make_good_roc_result(env, call_unwrapped_result) - call_unwrapped_result - } + // make_good_roc_result(env, call_unwrapped_result) + call_unwrapped_result }; match cc_return { - CCReturn::Void if !env.is_gen_test => { + CCReturn::Void => { // TODO return empty struct here? builder.build_return(None); } - CCReturn::Return if !env.is_gen_test => { + CCReturn::Return => { builder.build_return(Some(&call_result)); } - _ => { + CCReturn::ByPointer => { let output_arg_index = args_length - 1; let output_arg = c_function @@ -4105,19 +4233,28 @@ pub fn build_proc<'a, 'ctx, 'env>( let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); - let func_spec = it.next().unwrap(); - debug_assert!( - it.next().is_none(), - "we expect only one specialization of this symbol" - ); + let evaluator = match it.next() { + Some(func_spec) => { + debug_assert!( + it.next().is_none(), + "we expect only one specialization of this symbol" + ); - let evaluator = function_value_by_func_spec( - env, - *func_spec, - symbol, - top_level.arguments, - &top_level.result, - ); + function_value_by_func_spec( + env, + *func_spec, + symbol, + top_level.arguments, + &top_level.result, + ) + } + None => { + // morphic did not generate a specialization for this function, + // therefore it must actually be unused. + // An example is our closure callers + panic!("morphic did not specialize {:?}", symbol); + } + }; let ident_string = proc.name.as_str(&env.interns); let fn_name: String = format!("{}_1", ident_string); @@ -4777,7 +4914,7 @@ fn run_low_level<'a, 'ctx, 'env>( layout: &Layout<'a>, op: LowLevel, args: &[Symbol], - update_mode: Option, + update_mode: UpdateMode, // expect_failed: *const (), ) -> BasicValueEnum<'ctx> { use LowLevel::*; @@ -4833,7 +4970,7 @@ fn run_low_level<'a, 'ctx, 'env>( let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); - str_from_utf8(env, parent, original_wrapper) + str_from_utf8(env, parent, original_wrapper, update_mode) } StrFromUtf8Range => { debug_assert_eq!(args.len(), 2); @@ -4853,6 +4990,12 @@ fn run_low_level<'a, 'ctx, 'env>( str_to_utf8(env, string.into_struct_value()) } + StrRepeat => { + // Str.repeat : Str, Nat -> Str + debug_assert_eq!(args.len(), 2); + + str_repeat(env, scope, args[0], args[1]) + } StrSplit => { // Str.split : Str, Str -> List Str debug_assert_eq!(args.len(), 2); @@ -4909,7 +5052,7 @@ fn run_low_level<'a, 'ctx, 'env>( let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - list_reverse(env, list, list_layout) + list_reverse(env, list, list_layout, update_mode) } ListConcat => { debug_assert_eq!(args.len(), 2); @@ -4951,7 +5094,7 @@ fn run_low_level<'a, 'ctx, 'env>( let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]); - list_append(env, original_wrapper, elem, elem_layout) + list_append(env, original_wrapper, elem, elem_layout, update_mode) } ListSwap => { // List.swap : List elem, Nat, Nat -> List elem @@ -4971,6 +5114,7 @@ fn run_low_level<'a, 'ctx, 'env>( index_1.into_int_value(), index_2.into_int_value(), element_layout, + update_mode, ), _ => unreachable!("Invalid layout {:?} in List.swap", list_layout), } @@ -5298,7 +5442,7 @@ fn run_low_level<'a, 'ctx, 'env>( index.into_int_value(), element, element_layout, - update_mode.unwrap(), + update_mode, ), _ => unreachable!("invalid dict layout"), } @@ -5615,89 +5759,115 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( let builder = env.builder; let context = env.context; - // Here we build two functions: - // - // - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine` - // This is just a type signature that we make available to the linker, - // and can use in the wrapper - // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` + let fastcc_function_name = format!("{}_fastcc_wrapper", foreign.as_str()); - let return_type = basic_type_from_layout(env, ret_layout); - let cc_return = to_cc_return(env, ret_layout); + let (fastcc_function, arguments) = match env.module.get_function(fastcc_function_name.as_str()) + { + Some(function_value) => { + let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); - let mut cc_argument_types = Vec::with_capacity_in(argument_symbols.len() + 1, env.arena); - let mut fastcc_argument_types = Vec::with_capacity_in(argument_symbols.len(), env.arena); - let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); + for symbol in argument_symbols { + let (value, _) = load_symbol_and_layout(scope, symbol); - for symbol in argument_symbols { - let (value, layout) = load_symbol_and_layout(scope, symbol); + arguments.push(value); + } - cc_argument_types.push(to_cc_type(env, layout)); - - let basic_type = basic_type_from_layout(env, layout); - fastcc_argument_types.push(basic_type); - - arguments.push(value); - } - - let cc_type = match cc_return { - CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false), - CCReturn::ByPointer => { - cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); - env.context.void_type().fn_type(&cc_argument_types, false) + (function_value, arguments) + } + None => { + // Here we build two functions: + // + // - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine` + // This is just a type signature that we make available to the linker, + // and can use in the wrapper + // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` + + let return_type = basic_type_from_layout(env, ret_layout); + let cc_return = to_cc_return(env, ret_layout); + + let mut cc_argument_types = + Vec::with_capacity_in(argument_symbols.len() + 1, env.arena); + let mut fastcc_argument_types = + Vec::with_capacity_in(argument_symbols.len(), env.arena); + let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); + + for symbol in argument_symbols { + let (value, layout) = load_symbol_and_layout(scope, symbol); + + cc_argument_types.push(to_cc_type(env, layout)); + + let basic_type = basic_type_from_layout(env, layout); + fastcc_argument_types.push(basic_type); + + arguments.push(value); + } + + let cc_type = match cc_return { + CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false), + CCReturn::ByPointer => { + cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + env.context.void_type().fn_type(&cc_argument_types, false) + } + CCReturn::Return => return_type.fn_type(&cc_argument_types, false), + }; + + let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); + + let fastcc_type = return_type.fn_type(&fastcc_argument_types, false); + + let fastcc_function = add_func( + env.module, + &fastcc_function_name, + fastcc_type, + Linkage::Private, + FAST_CALL_CONV, + ); + + let old = builder.get_insert_block().unwrap(); + + let entry = context.append_basic_block(fastcc_function, "entry"); + { + builder.position_at_end(entry); + let return_pointer = env.builder.build_alloca(return_type, "return_value"); + + let fastcc_parameters = fastcc_function.get_params(); + let mut cc_arguments = + Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); + + for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) + { + if param.get_type() == *cc_type { + cc_arguments.push(param); + } else { + let as_cc_type = + complex_bitcast(env.builder, param, *cc_type, "to_cc_type"); + cc_arguments.push(as_cc_type); + } + } + + if let CCReturn::ByPointer = cc_return { + cc_arguments.push(return_pointer.into()); + } + + let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); + call.set_call_convention(C_CALL_CONV); + + let return_value = match cc_return { + CCReturn::Return => call.try_as_basic_value().left().unwrap(), + + CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), + CCReturn::Void => return_type.const_zero(), + }; + + builder.build_return(Some(&return_value)); + } + + builder.position_at_end(old); + + (fastcc_function, arguments) } - CCReturn::Return => return_type.fn_type(&cc_argument_types, false), }; - let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); - - let fastcc_type = return_type.fn_type(&fastcc_argument_types, false); - - let fastcc_function = add_func( - env.module, - &format!("{}_fastcc_wrapper", foreign.as_str()), - fastcc_type, - Linkage::Private, - FAST_CALL_CONV, - ); - - let old = builder.get_insert_block().unwrap(); - - let entry = context.append_basic_block(fastcc_function, "entry"); - { - builder.position_at_end(entry); - let return_pointer = env.builder.build_alloca(return_type, "return_value"); - - let fastcc_parameters = fastcc_function.get_params(); - let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); - - for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) { - if param.get_type() == *cc_type { - cc_arguments.push(param); - } else { - let as_cc_type = complex_bitcast(env.builder, param, *cc_type, "to_cc_type"); - cc_arguments.push(as_cc_type); - } - } - - if let CCReturn::ByPointer = cc_return { - cc_arguments.push(return_pointer.into()); - } - - let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); - call.set_call_convention(C_CALL_CONV); - - let return_value = match cc_return { - CCReturn::Return => call.try_as_basic_value().left().unwrap(), - - CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), - CCReturn::Void => return_type.const_zero(), - }; - - builder.build_return(Some(&return_value)); - } - - builder.position_at_end(old); let call = env.builder.build_call(fastcc_function, &arguments, "tmp"); call.set_call_convention(FAST_CALL_CONV); return call.try_as_basic_value().left().unwrap(); diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 2fe94d4cb5..d46d454458 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -17,6 +17,16 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_mono::layout::{Builtin, Layout, LayoutIds}; +pub fn pass_update_mode<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + update_mode: UpdateMode, +) -> BasicValueEnum<'ctx> { + match update_mode { + UpdateMode::Immutable => env.context.i8_type().const_zero().into(), + UpdateMode::InPlace => env.context.i8_type().const_int(1, false).into(), + } +} + fn list_returned_from_zig<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, output: BasicValueEnum<'ctx>, @@ -162,6 +172,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { let element_layout = match *list_layout { Layout::Builtin(Builtin::EmptyList) => { @@ -180,6 +191,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( pass_list_cc(env, list), env.alignment_intvalue(&element_layout), layout_width(env, &element_layout), + pass_update_mode(env, update_mode), ], bitcode::LIST_REVERSE, ) @@ -228,6 +240,7 @@ pub fn list_append<'a, 'ctx, 'env>( original_wrapper: StructValue<'ctx>, element: BasicValueEnum<'ctx>, element_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { call_bitcode_fn_returns_list( env, @@ -236,6 +249,7 @@ pub fn list_append<'a, 'ctx, 'env>( env.alignment_intvalue(element_layout), pass_element_as_opaque(env, element), layout_width(env, element_layout), + pass_update_mode(env, update_mode), ], bitcode::LIST_APPEND, ) @@ -267,6 +281,7 @@ pub fn list_swap<'a, 'ctx, 'env>( index_1: IntValue<'ctx>, index_2: IntValue<'ctx>, element_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { call_bitcode_fn_returns_list( env, @@ -276,6 +291,7 @@ pub fn list_swap<'a, 'ctx, 'env>( layout_width(env, element_layout), index_1.into(), index_2.into(), + pass_update_mode(env, update_mode), ], bitcode::LIST_SWAP, ) diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 80bfa0fa3a..564f35625e 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -1,9 +1,12 @@ use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; use crate::llvm::build::{complex_bitcast, Env, Scope}; -use crate::llvm::build_list::{allocate_list, call_bitcode_fn_returns_list, store_list}; +use crate::llvm::build_list::{ + allocate_list, call_bitcode_fn_returns_list, pass_update_mode, store_list, +}; use inkwell::builder::Builder; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::AddressSpace; +use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; @@ -12,6 +15,18 @@ use super::build::load_symbol; pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8); +/// Str.repeat : Str, Nat -> Str +pub fn str_repeat<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + scope: &Scope<'a, 'ctx>, + str_symbol: Symbol, + count_symbol: Symbol, +) -> BasicValueEnum<'ctx> { + let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol); + let count = load_symbol(scope, &count_symbol); + call_bitcode_fn(env, &[str_c_abi.into(), count], bitcode::STR_REPEAT) +} + /// Str.split : Str, Str -> List Str pub fn str_split<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -338,6 +353,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, _parent: FunctionValue<'ctx>, original_wrapper: StructValue<'ctx>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { let builder = env.builder; @@ -353,6 +369,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( env.str_list_c_abi().into(), "to_i128", ), + pass_update_mode(env, update_mode), result_ptr.into(), ], bitcode::STR_FROM_UTF8, diff --git a/compiler/gen_wasm/README.md b/compiler/gen_wasm/README.md index 50018f8758..9681e350c3 100644 --- a/compiler/gen_wasm/README.md +++ b/compiler/gen_wasm/README.md @@ -3,48 +3,57 @@ ## Plan - Initial bringup - - Get a wasm backend working for some of the number tests. - - Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time. -- Improve the fundamentals + - [x] Get a wasm backend working for some of the number tests. + - [x] Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time. +- Get the fundamentals working + - [x] Come up with a way to do control flow - [x] Flesh out the details of value representations between local variables and stack memory - - [ ] Set up a way to write tests with any return value rather than just i64 and f64 - - [ ] Figure out relocations for linking object files - - [ ] Think about the Wasm module builder library we're using, are we happy with it? + - [x] Set up a way to write tests with any return value rather than just i64 and f64 + - [x] Implement stack memory + - [x] Push and pop stack frames + - [x] Deal with returning structs + - [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc. + - [ ] Ensure early Return statements don't skip stack cleanup + - [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec` + - [ ] Implement relocations + - Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec` rather than a `Vec`. It may be worth serialising each instruction as it is inserted. + +- Refactor for code sharing with CPU backends + + - [ ] Implement a `scan_ast` pre-pass like `Backend` does, but for reusing Wasm locals rather than CPU registers + - [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing + - [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible + - Integration - Move wasm files to `gen_dev/src/wasm` - Share tests between wasm and x64, with some way of saying which tests work on which backends, and dispatching to different eval helpers based on that. - Get `build_module` in object_builder.rs to dispatch to the wasm generator (adding some Wasm options to the `Triple` struct) - Get `build_module` to write to a file, or maybe return `Vec`, instead of returning an Object structure -- Code sharing - - Try to ensure that both Wasm and x64 use the same `Backend` trait so that we can share code. - - We need to work towards this after we've progressed a bit more with Wasm and gained more understanding and experience of the differences. - - We will have to think about how to deal with the `Backend` code that doesn't apply to Wasm. Perhaps we will end up with more traits like `RegisterBackend` / `StackBackend` or `NativeBackend` / `WasmBackend`, and perhaps even some traits to do with backends that support jumps and those that don't. ## Structured control flow -🚨 **This is an area that could be tricky** 🚨 - One of the security features of WebAssembly is that it does not allow unrestricted "jumps" to anywhere you like. It does not have an instruction for that. All of the [control instructions][control-inst] can only implement "structured" control flow, and have names like `if`, `loop`, `block` that you'd normally associate with high-level languages. There are branch (`br`) instructions that can jump to labelled blocks within the same function, but the blocks have to be nested in sensible ways. [control-inst]: https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions -Implications: +This way of representing control flow is similar to parts of the Roc AST like `When`, `If` and `LetRec`. But Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. We need to map back from graph to a tree again in the Wasm backend. -Roc, like most modern languages, is already enforcing structured control flow in the source program. Constructs from the Roc AST like `When`, `If` and `LetRec` can all be converted straightforwardly to Wasm constructs. +Our solution is to wrap all joinpoint/jump graphs in an outer `loop`, with nested `block`s inside it. -However the Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. That doesn't map so directly to the Wasm structures. This is such a common issue for compiler back-ends that the WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. We should probably investigate this area sooner rather than later. If relooping turns out to be necessary or difficult, we might need to switch from parity_wasm to binaryen. +### Possible future optimisations -> By the way, it's not obvious how to pronounce "binaryen" but apparently it rhymes with "Targaryen", the family name from the "Game of Thrones" TV series +There are other algorithms available that may result in more optimised control flow. We are not focusing on that for our development backend, but here are some notes for future reference. + +The WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf). + +> By the way, apparently "binaryen" rhymes with "Targaryen", the family name from the "Game of Thrones" TV series + +There is also an improvement on Relooper called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2). It can reorder the joinpoints and jumps to make code more efficient. (It is also has things Roc wouldn't need but C++ does, like support for "irreducible" graphs that include `goto`). [cfg-api]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api [binaryen-rs]: https://crates.io/crates/binaryen -Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf). - -There is an alternative algorithm that is supposed to be an improvement on Relooper, called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2). - - ## Stack machine vs register machine Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack. @@ -90,29 +99,30 @@ The Mono IR contains two functions, `Num.add` and `main`, so we generate two cor (func (;1;) (result i64) ; declare function index 1 (main) with no parameters and an i64 result (local i64 i64 i64 i64) ; declare 4 local variables, all with type i64, one for each symbol in the Mono IR - i64.const 1 ; load constant of type i64 and value 1 stack=[1] - local.set 0 ; store top of stack to local0 stack=[] local0=1 - i64.const 2 ; load constant of type i64 and value 2 stack=[2] local0=1 - local.set 1 ; store top of stack to local1 stack=[] local0=1 local1=2 - local.get 0 ; load local0 to top of stack stack=[1] local0=1 local1=2 - local.get 1 ; load local1 to top of stack stack=[1,2] local0=1 local1=2 - call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[3] local0=1 local1=2 - local.set 2 ; store top of stack to local2 stack=[] local0=1 local1=2 local2=3 - i64.const 4 ; load constant of type i64 and value 4 stack=[4] local0=1 local1=2 local2=3 - local.set 3 ; store top of stack to local3 stack=[] local0=1 local1=2 local2=3 local3=4 - local.get 2 ; load local2 to top of stack stack=[3] local0=1 local1=2 local2=3 local3=4 - local.get 3 ; load local3 to top of stack stack=[3,4] local0=1 local1=2 local2=3 local3=4 - call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[7] local0=1 local1=2 local2=3 local3=4 + i64.const 1 ; stack=[1] + local.set 0 ; stack=[] local0=1 + i64.const 2 ; stack=[2] local0=1 + local.set 1 ; stack=[] local0=1 local1=2 + local.get 0 ; stack=[1] local0=1 local1=2 + local.get 1 ; stack=[1,2] local0=1 local1=2 + call 0 ; stack=[3] local0=1 local1=2 + local.set 2 ; stack=[] local0=1 local1=2 local2=3 + i64.const 4 ; stack=[4] local0=1 local1=2 local2=3 + local.set 3 ; stack=[] local0=1 local1=2 local2=3 local3=4 + local.get 2 ; stack=[3] local0=1 local1=2 local2=3 local3=4 + local.get 3 ; stack=[3,4] local0=1 local1=2 local2=3 local3=4 + call 0 ; stack=[7] local0=1 local1=2 local2=3 local3=4 return) ; return the value at the top of the stack ``` -If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away. The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). +If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away (which is all of them in this example!). The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). ``` $ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm ``` -The optimised functions have no local variables, and the code shrinks to about 60% of its original size. +The optimised functions have no local variables at all for this example. (Of course, this is an oversimplified toy example! It might not be so extreme in a real program.) + ``` (func (;0;) (param i64 i64) (result i64) local.get 0 @@ -122,9 +132,20 @@ The optimised functions have no local variables, and the code shrinks to about 6 i64.const 1 i64.const 2 call 0 - i64.const 4) + i64.const 4 + call 0) ``` +### Reducing sets and gets + +It would be nice to find some cheap optimisation to reduce the number of `local.set` and `local.get` instructions. + +We don't need a `local` if the value we want is already at the top of the VM stack. In fact, for our example above, it just so happens that if we simply skip generating the `local.set` instructions, everything _does_ appear on the VM stack in the right order, which means we can skip the `local.get` too. It ends up being very close to the fully optimised version! I assume this is because the Mono IR within the function is in dependency order, but I'm not sure... + +Of course the trick is to do this reliably for more complex dependency graphs. I am investigating whether we can do it by optimistically assuming it's OK not to create a local, and then keeping track of which symbols are at which positions in the VM stack after every instruction. Then when we need to use a symbol we can first check if it's on the VM stack and only create a local if it's not. In cases where we _do_ need to create a local, we need to go back and insert a `local.set` instruction at an earlier point in the program. We can make this fast by waiting to do all of the insertions in one batch when we're finalising the procedure. + +For a while we thought it would be very helpful to reuse the same local for multiple symbols at different points in the program. And we already have similar code in the CPU backends for register allocation. But on further examination, it doesn't actually buy us much! In our example above, we would still have the same number of `local.set` and `local.get` instructions - they'd just be operating on two locals instead of four! That doesn't shrink much code. Only the declaration at the top of the function would shrink from `(local i64 i64 i64 i64)` to `(local i64 i64)`... and in fact that's only smaller in the text format, it's the same size in the binary format! So the `scan_ast` pass doesn't seem worthwhile for Wasm. + ## Memory WebAssembly programs have a "linear memory" for storing data, which is a block of memory assigned to it by the host. You can assign a min and max size to the memory, and the WebAssembly program can request 64kB pages from the host, just like a "normal" program would request pages from the OS. Addresses start at zero and go up to whatever the current size is. Zero is a perfectly normal address like any other, and dereferencing it is not a segfault. But addresses beyond the current memory size are out of bounds and dereferencing them will cause a panic. @@ -143,7 +164,7 @@ When we are talking about how we store values in _memory_, I'll use the term _st Of course our program can use another area of memory as a heap as well. WebAssembly doesn't mind how you divide up your memory. It just gives you some memory and some instructions for loading and storing. -## Function calls +## Calling conventions & stack memory In WebAssembly you call a function by pushing arguments to the stack and then issuing a `call` instruction, which specifies a function index. The VM knows how many values to pop off the stack by examining the _type_ of the function. In our example earlier, `Num.add` had the type `[i64 i64] → [i64]` so it expects to find two i64's on the stack and pushes one i64 back as the result. Remember, the runtime engine will validate the module before running it, and if your generated code is trying to call a function at a point in the program where the wrong value types are on the stack, it will fail validation. @@ -151,11 +172,17 @@ Function arguments are restricted to the four value types, `i32`, `i64`, `f32` a That's all great for primitive values but what happens when we want to pass more complex data structures between functions? -Well, remember, "stack memory" is not a special kind of memory in WebAssembly, it's just an area of our memory where we _decide_ that we want to implement a stack data structure. So we can implement it however we want. A good choice would be to make our stack frame look the same as it would when we're targeting a CPU, except without the return address (since there's no need for one). We can also decide to pass numbers through the machine stack rather than in stack memory, since that takes fewer instructions. +Well, remember, "stack memory" is not a special kind of memory in WebAssembly, and is separate from the VM stack. It's just an area of our memory where we implement a stack data structure. But there are some conventions that it makes sense to follow so that we can easily link to Wasm code generated from Zig or other languages. -The only other thing we need is a stack pointer. On CPU targets, there's often have a specific "stack pointer" register. WebAssembly has no equivalent to that, but we can use a `global` variable. +### Observations from compiled C code -The system I've outlined above is based on my experience of compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals). +- `global 0` is used as the stack pointer, and its value is normally copied to a `local` as well (presumably because locals tend to be assigned to CPU registers) +- Stack memory grows downwards +- If a C function returns a struct, the compiled WebAssembly function has no return value, but instead has an extra _argument_. The argument is an `i32` pointer to space allocated in the caller's stack, that the called function can write to. +- There is no maximum number of arguments for a WebAssembly function, and arguments are not passed via _stack memory_. This makes sense because the _VM stack_ has no size limit. It's like having a CPU with an unlimited number of registers. +- Stack memory is only used for allocating local variables, not for passing arguments. And it's only used for values that cannot be stored in one of WebAssembly's primitive values (`i32`, `i64`, `f32`, `f64`). + +These observations are based on experiments compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals). ## Modules vs Instances diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 0b03f4aea8..303fe38349 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,5 +1,5 @@ use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, ModuleBuilder}; +use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder}; use parity_wasm::elements::{ BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, }; @@ -8,147 +8,28 @@ use roc_collections::all::MutMap; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; -use roc_mono::layout::{Builtin, Layout, UnionLayout}; +use roc_mono::layout::{Builtin, Layout}; -use crate::*; +use crate::layout::WasmLayout; +use crate::storage::SymbolStorage; +use crate::{ + copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + PTR_TYPE, +}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) const UNUSED_DATA_SECTION_BYTES: u32 = 1024; -#[derive(Clone, Copy, Debug)] -struct LocalId(u32); - #[derive(Clone, Copy, Debug)] struct LabelId(u32); -#[derive(Debug)] -struct SymbolStorage(LocalId, WasmLayout); - -// See README for background information on Wasm locals, memory and function calls -#[derive(Debug)] -pub enum WasmLayout { - // Most number types can fit in a Wasm local without any stack memory. - // Roc i8 is represented as an i32 local. Store the type and the original size. - LocalOnly(ValueType, u32), - - // A `local` pointing to stack memory - StackMemory(u32), - - // A `local` pointing to heap memory - HeapMemory, -} - -impl WasmLayout { - fn new(layout: &Layout) -> Self { - use ValueType::*; - let size = layout.stack_size(PTR_SIZE); - match layout { - Layout::Builtin(Builtin::Int128) => Self::StackMemory(size), - Layout::Builtin(Builtin::Int64) => Self::LocalOnly(I64, size), - Layout::Builtin(Builtin::Int32) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int16) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int8) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int1) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Usize) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Decimal) => Self::StackMemory(size), - Layout::Builtin(Builtin::Float128) => Self::StackMemory(size), - Layout::Builtin(Builtin::Float64) => Self::LocalOnly(F64, size), - Layout::Builtin(Builtin::Float32) => Self::LocalOnly(F32, size), - Layout::Builtin(Builtin::Str) => Self::StackMemory(size), - Layout::Builtin(Builtin::Dict(_, _)) => Self::StackMemory(size), - Layout::Builtin(Builtin::Set(_)) => Self::StackMemory(size), - Layout::Builtin(Builtin::List(_)) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyStr) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyList) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyDict) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptySet) => Self::StackMemory(size), - Layout::LambdaSet(lambda_set) => WasmLayout::new(&lambda_set.runtime_representation()), - Layout::Struct(_) => Self::StackMemory(size), - Layout::Union(UnionLayout::NonRecursive(_)) => Self::StackMemory(size), - Layout::Union(UnionLayout::Recursive(_)) => Self::HeapMemory, - Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => Self::HeapMemory, - Layout::Union(UnionLayout::NullableWrapped { .. }) => Self::HeapMemory, - Layout::Union(UnionLayout::NullableUnwrapped { .. }) => Self::HeapMemory, - Layout::RecursivePointer => Self::HeapMemory, - } - } - - fn value_type(&self) -> ValueType { - match self { - Self::LocalOnly(type_, _) => *type_, - _ => PTR_TYPE, - } - } - - fn stack_memory(&self) -> u32 { - match self { - Self::StackMemory(size) => *size, - _ => 0, - } - } - - #[allow(dead_code)] - fn load(&self, offset: u32) -> Result { - use crate::backend::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), - - // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Load(ALIGN_8, offset)) - } else { - Ok(I32Load(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate load instruction for WasmLayout {:?}", - self - )), - } - } - - #[allow(dead_code)] - fn store(&self, offset: u32) -> Result { - use crate::backend::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)), - - // LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Store(ALIGN_8, offset)) - } else { - Ok(I32Store(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate store instruction for WasmLayout {:?}", - self - )), - } - } +enum LocalKind { + Parameter, + Variable, } +// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) pub struct WasmBackend<'a> { // Module: Wasm AST pub builder: ModuleBuilder, @@ -160,12 +41,12 @@ pub struct WasmBackend<'a> { // Functions: Wasm AST instructions: std::vec::Vec, - ret_type: ValueType, arg_types: std::vec::Vec, locals: std::vec::Vec, // Functions: internal state & IR mappings - stack_memory: u32, + stack_memory: i32, + stack_frame_pointer: Option, symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) block_depth: u32, @@ -185,12 +66,12 @@ impl<'a> WasmBackend<'a> { // Functions: Wasm AST instructions: std::vec::Vec::with_capacity(256), - ret_type: ValueType::I32, arg_types: std::vec::Vec::with_capacity(8), locals: std::vec::Vec::with_capacity(32), // Functions: internal state & IR mappings stack_memory: 0, + stack_frame_pointer: None, symbol_storage_map: MutMap::default(), block_depth: 0, joinpoint_label_map: MutMap::default(), @@ -205,48 +86,18 @@ impl<'a> WasmBackend<'a> { // Functions: internal state & IR mappings self.stack_memory = 0; + self.stack_frame_pointer = None; self.symbol_storage_map.clear(); - // joinpoint_label_map.clear(); + self.joinpoint_label_map.clear(); + assert_eq!(self.block_depth, 0); } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { - let ret_layout = WasmLayout::new(&proc.ret_layout); - - if let WasmLayout::StackMemory { .. } = ret_layout { - return Err(format!( - "Not yet implemented: Returning values to callee stack memory {:?} {:?}", - proc.name, sym - )); - } - - self.ret_type = ret_layout.value_type(); - self.arg_types.reserve(proc.args.len()); - - for (layout, symbol) in proc.args { - let wasm_layout = WasmLayout::new(layout); - self.arg_types.push(wasm_layout.value_type()); - self.insert_local(wasm_layout, *symbol); - } + let signature_builder = self.start_proc(&proc); self.build_stmt(&proc.body, &proc.ret_layout)?; - let signature = builder::signature() - .with_params(self.arg_types.clone()) // requires std::Vec, not Bumpalo - .with_result(self.ret_type) - .build_sig(); - - // functions must end with an End instruction/opcode - let mut instructions = self.instructions.clone(); - instructions.push(Instruction::End); - - let function_def = builder::function() - .with_signature(signature) - .body() - .with_locals(self.locals.clone()) - .with_instructions(Instructions::new(instructions)) - .build() // body - .build(); // function - + let function_def = self.finalize_proc(signature_builder); let location = self.builder.push_function(function_def); let function_index = location.body; self.proc_symbol_map.insert(sym, location); @@ -255,32 +106,169 @@ impl<'a> WasmBackend<'a> { Ok(function_index) } - fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId { - self.stack_memory += layout.stack_memory(); - let index = self.symbol_storage_map.len(); - if index >= self.arg_types.len() { - self.locals.push(Local::new(1, layout.value_type())); + fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder { + let ret_layout = WasmLayout::new(&proc.ret_layout); + + let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + self.arg_types.push(PTR_TYPE); + self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any) + builder::signature() + } else { + let ret_type = ret_layout.value_type(); + self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any) + builder::signature().with_result(ret_type) + }; + + for (layout, symbol) in proc.args { + self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); } - let local_id = LocalId(index as u32); - let storage = SymbolStorage(local_id, layout); - self.symbol_storage_map.insert(symbol, storage); - local_id + + signature_builder.with_params(self.arg_types.clone()) } - fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { - self.symbol_storage_map.get(sym).ok_or_else(|| { - format!( + fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { + self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) + + let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); + + if self.stack_memory > 0 { + push_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer.unwrap(), + ); + } + + final_instructions.extend(self.instructions.drain(0..)); + + if self.stack_memory > 0 { + pop_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer.unwrap(), + ); + } + final_instructions.push(End); + + builder::function() + .with_signature(signature_builder.build_sig()) + .body() + .with_locals(self.locals.clone()) + .with_instructions(Instructions::new(final_instructions)) + .build() // body + .build() // function + } + + fn insert_local( + &mut self, + wasm_layout: WasmLayout, + symbol: Symbol, + kind: LocalKind, + ) -> SymbolStorage { + let local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); + + let storage = match kind { + LocalKind::Parameter => { + // Already stack-allocated by the caller if needed. + self.arg_types.push(wasm_layout.value_type()); + match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive { + local_id, + value_type, + size, + }, + + WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive { + local_id, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, + + WasmLayout::StackMemory { + size, + alignment_bytes, + } => SymbolStorage::ParamStackMemory { + local_id, + size, + alignment_bytes, + }, + } + } + LocalKind::Variable => { + self.locals.push(Local::new(1, wasm_layout.value_type())); + + match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive { + local_id, + value_type, + size, + }, + + WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { local_id }, + + WasmLayout::StackMemory { + size, + alignment_bytes, + } => { + let offset = + round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + self.stack_memory = offset + size as i32; + + match self.stack_frame_pointer { + None => { + // This is the first stack-memory variable in the function + // That means we can reuse it as the stack frame pointer, + // and it will get initialised at the start of the function + self.stack_frame_pointer = Some(local_id); + } + + Some(frame_ptr_id) => { + // This local points to the base of a struct, at an offset from the stack frame pointer + // Having one local per variable means params and locals work the same way in code gen. + // (alternatively we could use one frame pointer + offset for all struct variables) + self.instructions.extend([ + GetLocal(frame_ptr_id.0), + I32Const(offset), + I32Add, + SetLocal(local_id.0), + ]); + } + }; + + SymbolStorage::VarStackMemory { + local_id, + size, + offset: offset as u32, + alignment_bytes, + } + } + } + } + }; + + self.symbol_storage_map.insert(symbol, storage.clone()); + + storage + } + + fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage { + self.symbol_storage_map.get(sym).unwrap_or_else(|| { + panic!( "Symbol {:?} not found in function scope:\n{:?}", sym, self.symbol_storage_map ) }) } - fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> { - let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?; - let id: u32 = *local_id; - self.instructions.push(GetLocal(id)); - Ok(()) + fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { + let storage = self.get_symbol_storage(sym); + storage.local_id() + } + + fn load_symbol(&mut self, sym: &Symbol) { + let storage = self.get_symbol_storage(sym); + let index: u32 = storage.local_id().0; + self.instructions.push(GetLocal(index)); } /// start a loop that leaves a value on the stack @@ -291,12 +279,9 @@ impl<'a> WasmBackend<'a> { self.instructions.push(Loop(BlockType::Value(value_type))); } - fn start_block(&mut self) { + fn start_block(&mut self, block_type: BlockType) { self.block_depth += 1; - - // Our blocks always end with a `return` or `br`, - // so they never leave extra values on the stack - self.instructions.push(Block(BlockType::NoResult)); + self.instructions.push(Block(block_type)); } fn end_block(&mut self) { @@ -306,36 +291,77 @@ impl<'a> WasmBackend<'a> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { match stmt { - // This pattern is a simple optimisation to get rid of one local and two instructions per proc. - // If we are just returning the expression result, then don't SetLocal and immediately GetLocal + // Simple optimisation: if we are just returning the expression, we don't need a local Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { + let wasm_layout = WasmLayout::new(layout); + if let WasmLayout::StackMemory { + size, + alignment_bytes, + } = wasm_layout + { + // Map this symbol to the first argument (pointer into caller's stack) + // Saves us from having to copy it later + let storage = SymbolStorage::ParamStackMemory { + local_id: LocalId(0), + size, + alignment_bytes, + }; + self.symbol_storage_map.insert(*let_sym, storage); + } self.build_expr(let_sym, expr, layout)?; - self.instructions.push(Return); + self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop) Ok(()) } Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let local_id = self.insert_local(wasm_layout, *sym); + let local_id = self + .insert_local(wasm_layout, *sym, LocalKind::Variable) + .local_id(); self.build_expr(sym, expr, layout)?; - self.instructions.push(SetLocal(local_id.0)); + + // If this local is shared with the stack frame pointer, it's already assigned + match self.stack_frame_pointer { + Some(sfp) if sfp == local_id => {} + _ => self.instructions.push(SetLocal(local_id.0)), + } self.build_stmt(following, ret_layout)?; Ok(()) } Stmt::Ret(sym) => { - if let Some(SymbolStorage(local_id, _)) = self.symbol_storage_map.get(sym) { - self.instructions.push(GetLocal(local_id.0)); - self.instructions.push(Return); - Ok(()) - } else { - Err(format!( - "Not yet implemented: returning values with layout {:?}", - ret_layout - )) + use crate::storage::SymbolStorage::*; + + let storage = self.symbol_storage_map.get(sym).unwrap(); + + match storage { + VarStackMemory { + local_id, + size, + alignment_bytes, + .. + } + | ParamStackMemory { + local_id, + size, + alignment_bytes, + } => { + let from = *local_id; + let to = LocalId(0); + copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0); + } + + ParamPrimitive { local_id, .. } + | VarPrimitive { local_id, .. } + | VarHeapMemory { local_id, .. } => { + self.instructions.push(GetLocal(local_id.0)); + self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) + } } + + Ok(()) } Stmt::Switch { @@ -351,19 +377,16 @@ impl<'a> WasmBackend<'a> { // create (number_of_branches - 1) new blocks. for _ in 0..branches.len() { - self.start_block() + self.start_block(BlockType::NoResult) } // the LocalId of the symbol that we match on - let matched_on = match self.symbol_storage_map.get(cond_symbol) { - Some(SymbolStorage(local_id, _)) => local_id.0, - None => unreachable!("symbol not defined: {:?}", cond_symbol), - }; + let matched_on = self.local_id_from_symbol(cond_symbol); // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { // put the cond_symbol on the top of the stack - self.instructions.push(GetLocal(matched_on)); + self.instructions.push(GetLocal(matched_on.0)); self.instructions.push(I32Const(*value as i32)); @@ -398,12 +421,14 @@ impl<'a> WasmBackend<'a> { let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let local_id = self.insert_local(wasm_layout, parameter.symbol); + let local_id = self + .insert_local(wasm_layout, parameter.symbol, LocalKind::Variable) + .local_id(); jp_parameter_local_ids.push(local_id); } - self.start_block(); + self.start_block(BlockType::NoResult); self.joinpoint_label_map .insert(*id, (self.block_depth, jp_parameter_local_ids)); @@ -429,12 +454,8 @@ impl<'a> WasmBackend<'a> { // put the arguments on the stack for (symbol, local_id) in arguments.iter().zip(locals.iter()) { - let argument = match self.symbol_storage_map.get(symbol) { - Some(SymbolStorage(local_id, _)) => local_id.0, - None => unreachable!("symbol not defined: {:?}", symbol), - }; - - self.instructions.push(GetLocal(argument)); + let argument = self.local_id_from_symbol(symbol); + self.instructions.push(GetLocal(argument.0)); self.instructions.push(SetLocal(local_id.0)); } @@ -463,7 +484,7 @@ impl<'a> WasmBackend<'a> { }) => match call_type { CallType::ByName { name: func_sym, .. } => { for arg in *arguments { - self.load_from_symbol(arg)?; + self.load_symbol(arg); } let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", @@ -479,45 +500,112 @@ impl<'a> WasmBackend<'a> { x => Err(format!("the call type, {:?}, is not yet implemented", x)), }, + Expr::Struct(fields) => self.create_struct(sym, layout, fields), + x => Err(format!("Expression is not yet implemented {:?}", x)), } } fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { - match lit { - Literal::Bool(x) => { - self.instructions.push(I32Const(*x as i32)); - Ok(()) + let instruction = match lit { + Literal::Bool(x) => I32Const(*x as i32), + Literal::Byte(x) => I32Const(*x as i32), + Literal::Int(x) => match layout { + Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), + Layout::Builtin( + Builtin::Int32 + | Builtin::Int16 + | Builtin::Int8 + | Builtin::Int1 + | Builtin::Usize, + ) => I32Const(*x as i32), + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); + } + }, + Literal::Float(x) => match layout { + Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), + Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); + } + }, + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); } - Literal::Byte(x) => { - self.instructions.push(I32Const(*x as i32)); - Ok(()) + }; + self.instructions.push(instruction); + Ok(()) + } + + fn create_struct( + &mut self, + sym: &Symbol, + layout: &Layout<'a>, + fields: &'a [Symbol], + ) -> Result<(), String> { + let storage = self.get_symbol_storage(sym).to_owned(); + + if let Layout::Struct(field_layouts) = layout { + match storage { + SymbolStorage::VarStackMemory { local_id, size, .. } + | SymbolStorage::ParamStackMemory { local_id, size, .. } => { + if size > 0 { + let mut relative_offset = 0; + for (field, _) in fields.iter().zip(field_layouts.iter()) { + relative_offset += self.copy_symbol_to_pointer_at_offset( + local_id, + relative_offset, + field, + ); + } + } else { + return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); + } + } + _ => { + return Err(format!( + "Cannot create struct {:?} with storage {:?}", + sym, storage + )); + } } - Literal::Int(x) => { - let instruction = match layout { - Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), - Layout::Builtin( - Builtin::Int32 - | Builtin::Int16 - | Builtin::Int8 - | Builtin::Int1 - | Builtin::Usize, - ) => I32Const(*x as i32), - x => panic!("loading literal, {:?}, is not yet implemented", x), - }; - self.instructions.push(instruction); - Ok(()) - } - Literal::Float(x) => { - let instruction = match layout { - Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), - Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), - x => panic!("loading literal, {:?}, is not yet implemented", x), - }; - self.instructions.push(instruction); - Ok(()) - } - x => Err(format!("loading literal, {:?}, is not yet implemented", x)), + } else { + // Struct expression but not Struct layout => single element. Copy it. + let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); + self.copy_storage(&storage, &field_storage); + } + Ok(()) + } + + fn copy_symbol_to_pointer_at_offset( + &mut self, + to_ptr: LocalId, + to_offset: u32, + from_symbol: &Symbol, + ) -> u32 { + let from_storage = self.get_symbol_storage(from_symbol).to_owned(); + from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset) + } + + fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) { + let has_stack_memory = to.has_stack_memory(); + debug_assert!(from.has_stack_memory() == has_stack_memory); + + if !has_stack_memory { + debug_assert!(from.value_type() == to.value_type()); + self.instructions.push(GetLocal(from.local_id().0)); + self.instructions.push(SetLocal(to.local_id().0)); + } else { + let (size, alignment_bytes) = from.stack_size_and_alignment(); + copy_memory( + &mut self.instructions, + from.local_id(), + to.local_id(), + size, + alignment_bytes, + 0, + ); } } @@ -528,7 +616,7 @@ impl<'a> WasmBackend<'a> { return_layout: &Layout<'a>, ) -> Result<(), String> { for arg in args { - self.load_from_symbol(arg)?; + self.load_symbol(arg); } let wasm_layout = WasmLayout::new(return_layout); self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; @@ -546,7 +634,7 @@ impl<'a> WasmBackend<'a> { // For those, we'll need to pre-process each argument before the main op, // so simple arrays of instructions won't work. But there are common patterns. let instructions: &[Instruction] = match lowlevel { - // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_from_symbol? + // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_symbol? LowLevel::NumAdd => match return_value_type { ValueType::I32 => &[I32Add], ValueType::I64 => &[I64Add], diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs new file mode 100644 index 0000000000..df59b80eb1 --- /dev/null +++ b/compiler/gen_wasm/src/layout.rs @@ -0,0 +1,82 @@ +use parity_wasm::elements::ValueType; +use roc_mono::layout::{Layout, UnionLayout}; + +use crate::{PTR_SIZE, PTR_TYPE}; + +// See README for background information on Wasm locals, memory and function calls +#[derive(Debug, Clone)] +pub enum WasmLayout { + // Primitive number value. Just a Wasm local, without any stack memory. + // For example, Roc i8 is represented as Wasm i32. Store the type and the original size. + LocalOnly(ValueType, u32), + + // Local pointer to stack memory + StackMemory { size: u32, alignment_bytes: u32 }, + + // Local pointer to heap memory + HeapMemory, +} + +impl WasmLayout { + pub fn new(layout: &Layout) -> Self { + use roc_mono::layout::Builtin::*; + use UnionLayout::*; + use ValueType::*; + + let size = layout.stack_size(PTR_SIZE); + let alignment_bytes = layout.alignment_bytes(PTR_SIZE); + + match layout { + Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), + + Layout::Builtin(Int64) => Self::LocalOnly(I64, size), + + Layout::Builtin(Float32) => Self::LocalOnly(F32, size), + + Layout::Builtin(Float64) => Self::LocalOnly(F64, size), + + Layout::Builtin( + Int128 + | Decimal + | Float128 + | Str + | Dict(_, _) + | Set(_) + | List(_) + | EmptyStr + | EmptyList + | EmptyDict + | EmptySet, + ) + | Layout::Struct(_) + | Layout::LambdaSet(_) + | Layout::Union(NonRecursive(_)) => Self::StackMemory { + size, + alignment_bytes, + }, + + Layout::Union( + Recursive(_) + | NonNullableUnwrapped(_) + | NullableWrapped { .. } + | NullableUnwrapped { .. }, + ) + | Layout::RecursivePointer => Self::HeapMemory, + } + } + + pub fn value_type(&self) -> ValueType { + match self { + Self::LocalOnly(type_, _) => *type_, + _ => PTR_TYPE, + } + } + + #[allow(dead_code)] + pub fn stack_memory(&self) -> u32 { + match self { + Self::StackMemory { size, .. } => *size, + _ => 0, + } + } +} diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 37eb3c1d5b..32ff3e83ad 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,9 +1,11 @@ mod backend; pub mod from_wasm32_memory; +mod layout; +mod storage; use bumpalo::Bump; use parity_wasm::builder; -use parity_wasm::elements::{Instruction, Internal, ValueType}; +use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType}; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::{Interns, Symbol}; @@ -22,6 +24,10 @@ pub const ALIGN_4: u32 = 2; pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; +pub const STACK_ALIGNMENT_BYTES: i32 = 16; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LocalId(pub u32); pub struct Env<'a> { pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot @@ -104,3 +110,83 @@ pub fn build_module_help<'a>( Ok((backend.builder, main_function_index)) } + +fn encode_alignment(bytes: u32) -> u32 { + match bytes { + 1 => ALIGN_1, + 2 => ALIGN_2, + 4 => ALIGN_4, + 8 => ALIGN_8, + _ => panic!("{:?}-byte alignment is not supported", bytes), + } +} + +fn copy_memory( + instructions: &mut Vec, + from_ptr: LocalId, + to_ptr: LocalId, + size: u32, + alignment_bytes: u32, + offset: u32, +) { + let alignment_flag = encode_alignment(alignment_bytes); + let mut current_offset = offset; + while size - current_offset >= 8 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I64Load(alignment_flag, current_offset)); + instructions.push(I64Store(alignment_flag, current_offset)); + current_offset += 8; + } + if size - current_offset >= 4 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I32Load(alignment_flag, current_offset)); + instructions.push(I32Store(alignment_flag, current_offset)); + current_offset += 4; + } + while size - current_offset > 0 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I32Load8U(alignment_flag, current_offset)); + instructions.push(I32Store8(alignment_flag, current_offset)); + current_offset += 1; + } +} + +/// Round up to alignment_bytes (assumed to be a power of 2) +pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { + let mut aligned = unaligned; + aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary + aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 + aligned +} + +pub fn push_stack_frame( + instructions: &mut Vec, + size: i32, + local_frame_pointer: LocalId, +) { + let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); + instructions.extend([ + GetGlobal(STACK_POINTER_GLOBAL_ID), + I32Const(aligned_size), + I32Sub, + TeeLocal(local_frame_pointer.0), + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]); +} + +pub fn pop_stack_frame( + instructions: &mut Vec, + size: i32, + local_frame_pointer: LocalId, +) { + let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); + instructions.extend([ + GetLocal(local_frame_pointer.0), + I32Const(aligned_size), + I32Add, + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]); +} diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs new file mode 100644 index 0000000000..51c14203b1 --- /dev/null +++ b/compiler/gen_wasm/src/storage.rs @@ -0,0 +1,146 @@ +use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; + +#[derive(Debug, Clone)] +pub enum SymbolStorage { + VarPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + ParamPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + VarStackMemory { + local_id: LocalId, + size: u32, + offset: u32, + alignment_bytes: u32, + }, + ParamStackMemory { + local_id: LocalId, + size: u32, + alignment_bytes: u32, + }, + VarHeapMemory { + local_id: LocalId, + }, +} + +impl SymbolStorage { + pub fn local_id(&self) -> LocalId { + match self { + Self::ParamPrimitive { local_id, .. } => *local_id, + Self::ParamStackMemory { local_id, .. } => *local_id, + Self::VarPrimitive { local_id, .. } => *local_id, + Self::VarStackMemory { local_id, .. } => *local_id, + Self::VarHeapMemory { local_id, .. } => *local_id, + } + } + + pub fn value_type(&self) -> ValueType { + match self { + Self::ParamPrimitive { value_type, .. } => *value_type, + Self::VarPrimitive { value_type, .. } => *value_type, + Self::ParamStackMemory { .. } => ValueType::I32, + Self::VarStackMemory { .. } => ValueType::I32, + Self::VarHeapMemory { .. } => ValueType::I32, + } + } + + pub fn has_stack_memory(&self) -> bool { + match self { + Self::ParamStackMemory { .. } => true, + Self::VarStackMemory { .. } => true, + Self::ParamPrimitive { .. } => false, + Self::VarPrimitive { .. } => false, + Self::VarHeapMemory { .. } => false, + } + } + + pub fn stack_size_and_alignment(&self) -> (u32, u32) { + match self { + Self::VarStackMemory { + size, + alignment_bytes, + .. + } + | Self::ParamStackMemory { + size, + alignment_bytes, + .. + } => (*size, *alignment_bytes), + + _ => (0, 0), + } + } + + pub fn copy_to_memory( + &self, + instructions: &mut Vec, + to_pointer: LocalId, + to_offset: u32, + ) -> u32 { + match self { + Self::ParamPrimitive { + local_id, + value_type, + size, + .. + } + | Self::VarPrimitive { + local_id, + value_type, + size, + .. + } => { + let store_instruction = match (value_type, size) { + (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), + (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), + (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), + (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), + (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), + (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), + _ => { + panic!("Cannot store {:?} with alignment of {:?}", value_type, size); + } + }; + instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(local_id.0)); + instructions.push(store_instruction); + *size + } + + Self::ParamStackMemory { + local_id, + size, + alignment_bytes, + } + | Self::VarStackMemory { + local_id, + size, + alignment_bytes, + .. + } => { + copy_memory( + instructions, + *local_id, + to_pointer, + *size, + *alignment_bytes, + to_offset, + ); + *size + } + + Self::VarHeapMemory { local_id, .. } => { + instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(local_id.0)); + instructions.push(I32Store(ALIGN_4, to_offset)); + 4 + } + } + } +} diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 184edf43f7..5cf709a597 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -1,11 +1,15 @@ use parity_wasm::builder; use parity_wasm::builder::ModuleBuilder; -use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Internal, ValueType}; +use parity_wasm::elements::{ + Instruction, Instruction::*, Instructions, Internal, Local, ValueType, +}; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_gen_wasm::*; use roc_std::{RocDec, RocList, RocOrder, RocStr}; +const STACK_POINTER_LOCAL_ID: u32 = 0; + pub trait Wasm32TestResult { fn insert_test_wrapper( module_builder: &mut ModuleBuilder, @@ -16,9 +20,11 @@ pub trait Wasm32TestResult { let signature = builder::signature().with_result(ValueType::I32).build_sig(); + let stack_frame_pointer = Local::new(1, ValueType::I32); let function_def = builder::function() .with_signature(signature) .body() + .with_locals(vec![stack_frame_pointer]) .with_instructions(Instructions::new(instructions)) .build() // body .build(); // function @@ -35,22 +41,15 @@ pub trait Wasm32TestResult { fn build_wrapper_body(main_function_index: u32) -> Vec; } -fn build_wrapper_body_prelude(stack_memory_size: usize) -> Vec { - vec![ - GetGlobal(STACK_POINTER_GLOBAL_ID), - I32Const(stack_memory_size as i32), - I32Sub, - SetGlobal(STACK_POINTER_GLOBAL_ID), - ] -} - macro_rules! build_wrapper_body_primitive { ($store_instruction: expr, $align: expr) => { fn build_wrapper_body(main_function_index: u32) -> Vec { - const MAX_ALIGNED_SIZE: usize = 16; - let mut instructions = build_wrapper_body_prelude(MAX_ALIGNED_SIZE); + let size: i32 = 8; + let mut instructions = Vec::with_capacity(16); + push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.extend([ - GetGlobal(STACK_POINTER_GLOBAL_ID), + // load result address to prepare for the store instruction later + GetLocal(STACK_POINTER_LOCAL_ID), // // Call the main function with no arguments. Get primitive back. Call(main_function_index), @@ -59,9 +58,10 @@ macro_rules! build_wrapper_body_primitive { $store_instruction($align, 0), // // Return the result pointer - GetGlobal(STACK_POINTER_GLOBAL_ID), - End, + GetLocal(STACK_POINTER_LOCAL_ID), ]); + pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); + instructions.push(End); instructions } }; @@ -76,18 +76,28 @@ macro_rules! wasm_test_result_primitive { } fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { - let mut instructions = build_wrapper_body_prelude(size); + let mut instructions = Vec::with_capacity(16); + push_stack_frame( + &mut instructions, + size as i32, + LocalId(STACK_POINTER_LOCAL_ID), + ); instructions.extend([ // // Call the main function with the allocated address to write the result. // No value is returned to the VM stack. This is the same as in compiled C. - GetGlobal(STACK_POINTER_GLOBAL_ID), + GetLocal(STACK_POINTER_LOCAL_ID), Call(main_function_index), // // Return the result address - GetGlobal(STACK_POINTER_GLOBAL_ID), - End, + GetLocal(STACK_POINTER_LOCAL_ID), ]); + pop_stack_frame( + &mut instructions, + size as i32, + LocalId(STACK_POINTER_LOCAL_ID), + ); + instructions.push(End); instructions } @@ -163,3 +173,106 @@ where ) } } + +impl Wasm32TestResult for (T, U, V, W) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z, A) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, + A: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH + + A::ACTUAL_WIDTH, + ) + } +} diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 9c776ecfaa..884e92c7db 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -307,94 +307,13 @@ mod wasm_records { // () // ); // } - // - // #[test] - // fn i64_record1_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3 } - // "# - // ), - // 3, - // i64 - // ); - // } - - // #[test] - // fn i64_record2_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5 } - // "# - // ), - // (3, 5), - // (i64, i64) - // ); - // } - - // // #[test] - // // fn i64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3, y: 5, z: 17 } - // // "# - // // ), - // // (3, 5, 17), - // // (i64, i64, i64) - // // ); - // // } - - // #[test] - // fn f64_record2_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3.1, y: 5.1 } - // "# - // ), - // (3.1, 5.1), - // (f64, f64) - // ); - // } - - // // #[test] - // // fn f64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3.1, y: 5.1, z: 17.1 } - // // "# - // // ), - // // (3.1, 5.1, 17.1), - // // (f64, f64, f64) - // // ); - // // } - - // // #[test] - // // fn bool_record4_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // record : { a : Bool, b : Bool, c : Bool, d : Bool } - // // record = { a: True, b: True, c : True, d : Bool } - - // // record - // // "# - // // ), - // // (true, false, false, true), - // // (bool, bool, bool, bool) - // // ); - // // } #[test] fn i64_record1_literal() { assert_evals_to!( indoc!( r#" - { a: 3 } + { x: 3 } "# ), 3, @@ -402,31 +321,86 @@ mod wasm_records { ); } - // // #[test] - // // fn i64_record9_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } - // // "# - // // ), - // // (3, 5, 17, 1, 9, 12, 13, 14, 15), - // // (i64, i64, i64, i64, i64, i64, i64, i64, i64) - // // ); - // // } + #[test] + fn i64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5 } + "# + ), + (3, 5), + (i64, i64) + ); + } - // // #[test] - // // fn f64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3.1, y: 5.1, z: 17.1 } - // // "# - // // ), - // // (3.1, 5.1, 17.1), - // // (f64, f64, f64) - // // ); - // // } + #[test] + fn i64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 17 } + "# + ), + (3, 5, 17), + (i64, i64, i64) + ); + } + + #[test] + fn f64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1 } + "# + ), + (3.1, 5.1), + (f64, f64) + ); + } + + #[test] + fn f64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1, z: 17.1 } + "# + ), + (3.1, 5.1, 17.1), + (f64, f64, f64) + ); + } + + #[test] + fn bool_record4_literal() { + assert_evals_to!( + indoc!( + r#" + record : { a : Bool, b : Bool, c : Bool, d : Bool } + record = { a: True, b: False, c : False, d : True } + + record + "# + ), + [true, false, false, true], + [bool; 4] + ); + } + + #[test] + fn i64_record9_literal() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } + "# + ), + [3, 5, 17, 1, 9, 12, 13, 14, 15], + [i64; 9] + ); + } #[test] fn bool_literal() { @@ -667,135 +641,135 @@ mod wasm_records { // ); // } - // #[test] - // fn return_record_2() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5 } - // "# - // ), - // [3, 5], - // [i64; 2] - // ); - // } + #[test] + fn return_record_2() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5 } + "# + ), + [3, 5], + [i64; 2] + ); + } - // #[test] - // fn return_record_3() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5, z: 4 } - // "# - // ), - // (3, 5, 4), - // (i64, i64, i64) - // ); - // } + #[test] + fn return_record_3() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 4 } + "# + ), + (3, 5, 4), + (i64, i64, i64) + ); + } - // #[test] - // fn return_record_4() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2 } - // "# - // ), - // [3, 5, 4, 2], - // [i64; 4] - // ); - // } + #[test] + fn return_record_4() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2 } + "# + ), + [3, 5, 4, 2], + [i64; 4] + ); + } - // #[test] - // fn return_record_5() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1 } - // "# - // ), - // [3, 5, 4, 2, 1], - // [i64; 5] - // ); - // } + #[test] + fn return_record_5() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1 } + "# + ), + [3, 5, 4, 2, 1], + [i64; 5] + ); + } - // #[test] - // fn return_record_6() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } - // "# - // ), - // [3, 5, 4, 2, 1, 7], - // [i64; 6] - // ); - // } + #[test] + fn return_record_6() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } + "# + ), + [3, 5, 4, 2, 1, 7], + [i64; 6] + ); + } - // #[test] - // fn return_record_7() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } - // "# - // ), - // [3, 5, 4, 2, 1, 7, 8], - // [i64; 7] - // ); - // } + #[test] + fn return_record_7() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } + "# + ), + [3, 5, 4, 2, 1, 7, 8], + [i64; 7] + ); + } - // #[test] - // fn return_record_float_int() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3.14, b: 0x1 } - // "# - // ), - // (3.14, 0x1), - // (f64, i64) - // ); - // } + #[test] + fn return_record_float_int() { + assert_evals_to!( + indoc!( + r#" + { a: 3.14, b: 0x1 } + "# + ), + (3.14, 0x1), + (f64, i64) + ); + } - // #[test] - // fn return_record_int_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 0x1, b: 3.14 } - // "# - // ), - // (0x1, 3.14), - // (i64, f64) - // ); - // } + #[test] + fn return_record_int_float() { + assert_evals_to!( + indoc!( + r#" + { a: 0x1, b: 3.14 } + "# + ), + (0x1, 3.14), + (i64, f64) + ); + } - // #[test] - // fn return_record_float_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 6.28, b: 3.14 } - // "# - // ), - // (6.28, 3.14), - // (f64, f64) - // ); - // } + #[test] + fn return_record_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14 } + "# + ), + (6.28, 3.14), + (f64, f64) + ); + } - // #[test] - // fn return_record_float_float_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 6.28, b: 3.14, c: 0.1 } - // "# - // ), - // (6.28, 3.14, 0.1), - // (f64, f64, f64) - // ); - // } + #[test] + fn return_record_float_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14, c: 0.1 } + "# + ), + (6.28, 3.14, 0.1), + (f64, f64, f64) + ); + } // #[test] // fn return_nested_record() { @@ -851,20 +825,20 @@ mod wasm_records { // ); // } - #[test] - fn update_single_element_record() { - assert_evals_to!( - indoc!( - r#" - rec = { foo: 42} + // #[test] + // fn update_single_element_record() { + // assert_evals_to!( + // indoc!( + // r#" + // rec = { foo: 42} - { rec & foo: rec.foo + 1 } - "# - ), - 43, - i64 - ); - } + // { rec & foo: rec.foo + 1 } + // "# + // ), + // 43, + // i64 + // ); + // } // #[test] // fn booleans_in_record() { @@ -899,6 +873,24 @@ mod wasm_records { // ); // } + #[test] + fn stack_memory_return_from_branch() { + // stack memory pointer should end up in the right place after returning from a branch + assert_evals_to!( + indoc!( + r#" + stackMemoryJunk = { x: 999, y: 111 } + if True then + { x: 123, y: 321 } + else + stackMemoryJunk + "# + ), + (123, 321), + (i64, i64) + ); + } + // #[test] // fn blue_and_present() { // assert_evals_to!( diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 5aaefa463b..66481d3db8 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -37,13 +37,13 @@ use roc_types::subs::{Subs, VarStore, Variable}; use roc_types::types::{Alias, Type}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; -use std::fs; use std::io; use std::iter; use std::path::{Path, PathBuf}; use std::str::from_utf8_unchecked; use std::sync::Arc; use std::time::{Duration, SystemTime}; +use std::{env, fs}; /// Default name for the binary generated for an app, if an invalid one was specified. const DEFAULT_APP_OUTPUT_PATH: &str = "app"; @@ -1351,7 +1351,12 @@ where // doing .max(1) on the entire expression guards against // num_cpus returning 0, while also avoiding wrapping // unsigned subtraction overflow. - let num_workers = num_cpus::get().max(2) - 1; + let default_num_workers = num_cpus::get().max(2) - 1; + + let num_workers = match env::var("ROC_NUM_WORKERS") { + Ok(env_str) => env_str.parse::().unwrap_or(default_num_workers), + Err(_) => default_num_workers, + }; let worker_arenas = arena.alloc(bumpalo::collections::Vec::with_capacity_in( num_workers, @@ -1962,7 +1967,7 @@ fn update<'a>( ); } - if module_id == state.root_id && state.goal_phase == Phase::SolveTypes { + if is_host_exposed && state.goal_phase == Phase::SolveTypes { debug_assert!(work.is_empty()); debug_assert!(state.dependencies.solved_all()); diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 583e6017c1..7dec1f4ccf 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -15,6 +15,7 @@ pub enum LowLevel { StrFromUtf8, StrFromUtf8Range, StrToUtf8, + StrRepeat, StrFromFloat, ListLen, ListGetUnsafe, @@ -114,19 +115,19 @@ impl LowLevel { match self { StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 - | StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet - | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains - | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty - | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues - | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap - | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap - | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked - | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos - | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling - | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd - | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 - | NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not - | Hash | ExpectTrue => false, + | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe + | ListSet | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat + | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap + | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe + | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference + | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap + | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt + | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf + | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound + | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan + | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy + | NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast + | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false, ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index aaa9ce321d..57d747d246 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -551,7 +551,53 @@ impl IdentIds { } } - /// Generates a unique, new name that's just a stringified integer + // necessary when the name of a value is changed in the editor + pub fn update_key( + &mut self, + old_ident_name: &str, + new_ident_name: &str, + ) -> Result { + let old_ident: Ident = old_ident_name.into(); + + let ident_id_ref_opt = self.by_ident.get(&old_ident); + + match ident_id_ref_opt { + Some(ident_id_ref) => { + let ident_id = *ident_id_ref; + + self.by_ident.remove(&old_ident); + self.by_ident.insert(new_ident_name.into(), ident_id); + + let by_id = &mut self.by_id; + let key_index_opt = by_id.iter().position(|x| *x == old_ident); + + if let Some(key_index) = key_index_opt { + if let Some(vec_elt) = by_id.get_mut(key_index) { + *vec_elt = new_ident_name.into(); + } else { + // we get the index from by_id + unreachable!() + } + + Ok(ident_id) + } else { + Err( + format!( + "Tried to find position of key {:?} in IdentIds.by_id but I could not find the key. IdentIds.by_id: {:?}", + old_ident_name, + self.by_id + ) + ) + } + } + None => Err(format!( + "Tried to update key in IdentIds ({:?}) but I could not find the key ({}).", + self.by_ident, old_ident_name + )), + } + } + + /// Generates a unique, new name that's just a strigified integer /// (e.g. "1" or "5"), using an internal counter. Since valid Roc variable /// names cannot begin with a number, this has no chance of colliding /// with actual user-defined variables. @@ -927,6 +973,7 @@ define_builtins! { 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime 18 STR_FROM_UTF8_RANGE: "fromUtf8Range" + 19 STR_REPEAT: "repeat" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index f8e5dddb71..b218875425 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -9,13 +9,16 @@ use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use std::convert::TryFrom; -use crate::ir::{Call, CallType, Expr, ListLiteralElement, Literal, ModifyRc, Proc, Stmt}; -use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; +use crate::ir::{ + Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, Proc, Stmt, +}; +use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout}; // just using one module for now pub const MOD_APP: ModName = ModName(b"UserApp"); pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes()); +pub const STATIC_LIST_NAME: ConstName = ConstName(b"THIS IS A STATIC LIST"); const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; @@ -128,25 +131,49 @@ where }; m.add_const(STATIC_STR_NAME, static_str_def)?; - // the entry point wrapper - let roc_main_bytes = func_name_bytes_help( - entry_point.symbol, - entry_point.layout.arguments.iter().copied(), - entry_point.layout.result, - ); - let roc_main = FuncName(&roc_main_bytes); + // a const that models all static lists + let static_list_def = { + let mut cbuilder = ConstDefBuilder::new(); + let block = cbuilder.add_block(); + let cell = cbuilder.add_new_heap_cell(block)?; - let entry_point_function = build_entry_point(entry_point.layout, roc_main)?; - let entry_point_name = FuncName(ENTRY_POINT_NAME); - m.add_func(entry_point_name, entry_point_function)?; + let unit_type = cbuilder.add_tuple_type(&[])?; + let bag = cbuilder.add_empty_bag(block, unit_type)?; + let value_id = cbuilder.add_make_tuple(block, &[cell, bag])?; + let root = BlockExpr(block, value_id); + let list_type_id = static_list_type(&mut cbuilder)?; + + cbuilder.build(list_type_id, root)? + }; + m.add_const(STATIC_LIST_NAME, static_list_def)?; let mut type_definitions = MutSet::default(); + let mut host_exposed_functions = Vec::new(); // all other functions for proc in procs { let bytes = func_name_bytes(proc); let func_name = FuncName(&bytes); + if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts { + for (_, (symbol, top_level, layout)) in aliases { + match layout { + RawFunctionLayout::Function(_, _, _) => { + let it = top_level.arguments.iter().copied(); + let bytes = func_name_bytes_help(*symbol, it, top_level.result); + + host_exposed_functions.push((bytes, top_level.arguments)); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + let it = std::iter::once(Layout::Struct(&[])); + let bytes = func_name_bytes_help(*symbol, it, top_level.result); + + host_exposed_functions.push((bytes, top_level.arguments)); + } + } + } + } + if DEBUG { eprintln!( "{:?}: {:?} with {:?} args", @@ -163,6 +190,19 @@ where m.add_func(func_name, spec)?; } + // the entry point wrapper + let roc_main_bytes = func_name_bytes_help( + entry_point.symbol, + entry_point.layout.arguments.iter().copied(), + entry_point.layout.result, + ); + let roc_main = FuncName(&roc_main_bytes); + + let entry_point_function = + build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?; + let entry_point_name = FuncName(ENTRY_POINT_NAME); + m.add_func(entry_point_name, entry_point_function)?; + for union_layout in type_definitions { let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name = TypeName(&type_name_bytes); @@ -202,23 +242,83 @@ where morphic_lib::solve(program) } -fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Result { +/// if you want an "escape hatch" which allows you construct "best-case scenario" values +/// of an arbitrary type in much the same way that 'unknown_with' allows you to construct +/// "worst-case scenario" values of an arbitrary type, you can use the following terrible hack: +/// use 'add_make_union' to construct an instance of variant 0 of a union type 'union {(), your_type}', +/// and then use 'add_unwrap_union' to extract variant 1 from the value you just constructed. +/// In the current implementation (but not necessarily in future versions), +/// I can promise this will effectively give you a value of type 'your_type' +/// all of whose heap cells are considered unique and mutable. +fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId) -> Result { + let variant_types = vec![builder.add_tuple_type(&[])?, type_id]; + let unit = builder.add_make_tuple(block, &[])?; + let value = builder.add_make_union(block, &variant_types, 0, unit)?; + + builder.add_unwrap_union(block, value, 1) +} + +fn build_entry_point( + layout: crate::ir::ProcLayout, + func_name: FuncName, + host_exposed_functions: &[([u8; SIZE], &[Layout])], +) -> Result { let mut builder = FuncDefBuilder::new(); - let block = builder.add_block(); + let outer_block = builder.add_block(); - // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type(&mut builder, layout.arguments)?; - let argument = builder.add_unknown_with(block, &[], argument_type)?; + let mut cases = Vec::new(); - let name_bytes = [0; 16]; - let spec_var = CalleeSpecVar(&name_bytes); - let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?; + { + let block = builder.add_block(); + + // to the modelling language, the arguments appear out of thin air + let argument_type = build_tuple_type(&mut builder, layout.arguments)?; + + // does not make any assumptions about the input + // let argument = builder.add_unknown_with(block, &[], argument_type)?; + + // assumes the input can be updated in-place + let argument = terrible_hack(&mut builder, block, argument_type)?; + + let name_bytes = [0; 16]; + let spec_var = CalleeSpecVar(&name_bytes); + let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?; + + // to the modelling language, the result disappears into the void + let unit_type = builder.add_tuple_type(&[])?; + let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + + cases.push(BlockExpr(block, unit_value)); + } + + // add fake calls to host-exposed functions so they are specialized + for (name_bytes, layouts) in host_exposed_functions { + let host_exposed_func_name = FuncName(name_bytes); + + if host_exposed_func_name == func_name { + continue; + } + + let block = builder.add_block(); + + let type_id = layout_spec(&mut builder, &Layout::Struct(layouts))?; + + let argument = builder.add_unknown_with(block, &[], type_id)?; + + let spec_var = CalleeSpecVar(name_bytes); + let result = + builder.add_call(block, spec_var, MOD_APP, host_exposed_func_name, argument)?; + + let unit_type = builder.add_tuple_type(&[])?; + let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + + cases.push(BlockExpr(block, unit_value)); + } - // to the modelling language, the result disappears into the void let unit_type = builder.add_tuple_type(&[])?; - let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + let unit_value = builder.add_choice(outer_block, &cases)?; - let root = BlockExpr(block, unit_value); + let root = BlockExpr(outer_block, unit_value); let spec = builder.build(unit_type, unit_type, root)?; Ok(spec) @@ -818,6 +918,17 @@ fn lowlevel_spec( let new_cell = builder.add_new_heap_cell(block)?; builder.add_make_tuple(block, &[new_cell, bag]) } + ListReverse => { + let list = env.symbols[&arguments[0]]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let _unit = builder.add_update(block, update_mode_var, cell)?; + + let new_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, bag]) + } ListAppend => { let list = env.symbols[&arguments[0]]; let to_insert = env.symbols[&arguments[1]]; @@ -833,6 +944,27 @@ fn lowlevel_spec( let new_cell = builder.add_new_heap_cell(block)?; builder.add_make_tuple(block, &[new_cell, bag]) } + StrToUtf8 => { + let string = env.symbols[&arguments[0]]; + + let u8_type = builder.add_tuple_type(&[])?; + let bag = builder.add_empty_bag(block, u8_type)?; + let cell = builder.add_get_tuple_field(block, string, LIST_CELL_INDEX)?; + + builder.add_make_tuple(block, &[cell, bag]) + } + StrFromUtf8 => { + let list = env.symbols[&arguments[0]]; + + let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let string = builder.add_make_tuple(block, &[cell])?; + + let byte_index = builder.add_make_tuple(block, &[])?; + let is_ok = builder.add_make_tuple(block, &[])?; + let problem_code = builder.add_make_tuple(block, &[])?; + + builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code]) + } DictEmpty => { match layout { Layout::Builtin(Builtin::EmptyDict) => { @@ -1117,9 +1249,11 @@ fn expr_spec<'a>( let list = new_list(builder, block, type_id)?; let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let mut all_constants = true; for element in elems.iter() { let value_id = if let ListLiteralElement::Symbol(symbol) = element { + all_constants = false; env.symbols[symbol] } else { builder.add_make_tuple(block, &[]).unwrap() @@ -1128,9 +1262,13 @@ fn expr_spec<'a>( bag = builder.add_bag_insert(block, bag, value_id)?; } - let cell = builder.add_new_heap_cell(block)?; + if all_constants { + new_static_list(builder, block) + } else { + let cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[cell, bag]) + builder.add_make_tuple(block, &[cell, bag]) + } } EmptyArray => { @@ -1296,6 +1434,14 @@ fn str_type(builder: &mut TC) -> Result { builder.add_tuple_type(&[cell_id]) } +fn static_list_type(builder: &mut TC) -> Result { + let unit_type = builder.add_tuple_type(&[])?; + let cell = builder.add_heap_cell_type(); + let bag = builder.add_bag_type(unit_type)?; + + builder.add_tuple_type(&[cell, bag]) +} + // const OK_TAG_ID: u8 = 1u8; // const ERR_TAG_ID: u8 = 0u8; @@ -1329,6 +1475,12 @@ fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result Result { + let module = MOD_APP; + + builder.add_const_ref(block, module, STATIC_LIST_NAME) +} + fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result { // we model all our numbers as unit values builder.add_make_tuple(block, &[]) diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 89d6ae2446..45f5eb9c0d 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -1013,6 +1013,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrToUtf8 => arena.alloc_slice_copy(&[owned]), + StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), DictSize => arena.alloc_slice_copy(&[borrowed]), diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index bc620bdf74..c611ef99a2 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -94,6 +94,12 @@ impl<'a> CapturedSymbols<'a> { } } +impl<'a> Default for CapturedSymbols<'a> { + fn default() -> Self { + CapturedSymbols::None + } +} + #[derive(Clone, Debug, PartialEq)] pub struct PendingSpecialization<'a> { solved_type: SolvedType, @@ -6208,6 +6214,8 @@ fn reuse_function_symbol<'a>( layout_cache, ); + // even though this function may not itself capture, + // unification may still cause it to have an extra argument construct_closure_data( env, lambda_set, @@ -6437,8 +6445,6 @@ fn call_by_name<'a>( assign_to_symbols(env, procs, layout_cache, iter, result) } } else { - let argument_layouts = lambda_set.extend_argument_list(env.arena, arg_layouts); - call_by_name_help( env, procs, @@ -6446,7 +6452,7 @@ fn call_by_name<'a>( proc_name, loc_args, lambda_set, - argument_layouts, + arg_layouts, ret_layout, layout_cache, assigned, @@ -6494,10 +6500,6 @@ fn call_by_name_help<'a>( let original_fn_var = fn_var; let arena = env.arena; - // debug_assert!(!procs.module_thunks.contains(&proc_name), "{:?}", proc_name); - - let top_level_layout = ProcLayout::new(env.arena, argument_layouts, *ret_layout); - // the arguments given to the function, stored in symbols let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena); field_symbols.extend( @@ -6506,7 +6508,13 @@ fn call_by_name_help<'a>( .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), ); - let field_symbols = field_symbols.into_bump_slice(); + // If required, add an extra argument to the layout that is the captured environment + // afterwards, we MUST make sure the number of arguments in the layout matches the + // number of arguments actually passed. + let top_level_layout = { + let argument_layouts = lambda_set.extend_argument_list(env.arena, argument_layouts); + ProcLayout::new(env.arena, argument_layouts, *ret_layout) + }; // the variables of the given arguments let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); @@ -6535,6 +6543,8 @@ fn call_by_name_help<'a>( proc_name, ); + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6573,6 +6583,9 @@ fn call_by_name_help<'a>( "see call_by_name for background (scroll down a bit), function is {:?}", proc_name, ); + + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6625,6 +6638,8 @@ fn call_by_name_help<'a>( proc_name, ); + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6643,6 +6658,19 @@ fn call_by_name_help<'a>( None => { let opt_partial_proc = procs.partial_procs.get(&proc_name); + /* + debug_assert_eq!( + argument_layouts.len(), + field_symbols.len(), + "Function {:?} is called with {} arguments, but the layout expects {}", + proc_name, + field_symbols.len(), + argument_layouts.len(), + ); + */ + + let field_symbols = field_symbols.into_bump_slice(); + match opt_partial_proc { Some(partial_proc) => { // TODO should pending_procs hold a Rc to avoid this .clone()? @@ -6657,18 +6685,22 @@ fn call_by_name_help<'a>( match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) { - Ok((proc, layout)) => call_specialized_proc( - env, - procs, - proc_name, - proc, - layout, - field_symbols, - loc_args, - layout_cache, - assigned, - hole, - ), + Ok((proc, layout)) => { + // now we just call our freshly-specialized function + call_specialized_proc( + env, + procs, + proc_name, + proc, + lambda_set, + layout, + field_symbols, + loc_args, + layout_cache, + assigned, + hole, + ) + } Err(SpecializeFailure { attempted_layout, problem: _, @@ -6684,6 +6716,7 @@ fn call_by_name_help<'a>( procs, proc_name, proc, + lambda_set, attempted_layout, field_symbols, loc_args, @@ -6833,6 +6866,7 @@ fn call_specialized_proc<'a>( procs: &mut Procs<'a>, proc_name: Symbol, proc: Proc<'a>, + lambda_set: LambdaSet<'a>, layout: RawFunctionLayout<'a>, field_symbols: &'a [Symbol], loc_args: std::vec::Vec<(Variable, Located)>, @@ -6871,6 +6905,8 @@ fn call_specialized_proc<'a>( arguments: field_symbols, }; + // the closure argument is already added here (to get the right specialization) + // but now we need to remove it because the `match_on_lambda_set` will add it again build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole) } RawFunctionLayout::ZeroArgumentThunk(_) => { @@ -6878,30 +6914,75 @@ fn call_specialized_proc<'a>( } } } else { - debug_assert_eq!( - function_layout.arguments.len(), - field_symbols.len(), - "function {:?} with layout {:?} expects {:?} arguments, but is applied to {:?}", - proc_name, - function_layout, - function_layout.arguments.len(), - field_symbols.len(), - ); - let call = self::Call { - call_type: CallType::ByName { - name: proc_name, - ret_layout: function_layout.result, - arg_layouts: function_layout.arguments, - specialization_id: env.next_call_specialization_id(), - }, - arguments: field_symbols, - }; - let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); - let result = build_call(env, call, assigned, function_layout.result, hole); + match procs + .partial_procs + .get(&proc_name) + .map(|pp| &pp.captured_symbols) + { + Some(&CapturedSymbols::Captured(captured_symbols)) => { + let symbols = Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) + .into_bump_slice(); - assign_to_symbols(env, procs, layout_cache, iter, result) + let closure_data_symbol = env.unique_symbol(); + + // the closure argument is already added here (to get the right specialization) + // but now we need to remove it because the `match_on_lambda_set` will add it again + let mut argument_layouts = + Vec::from_iter_in(function_layout.arguments.iter().copied(), env.arena); + argument_layouts.pop().unwrap(); + + debug_assert_eq!(argument_layouts.len(), field_symbols.len(),); + + let new_hole = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + field_symbols, + argument_layouts.into_bump_slice(), + function_layout.result, + assigned, + hole, + ); + + let result = construct_closure_data( + env, + lambda_set, + proc_name, + symbols, + closure_data_symbol, + env.arena.alloc(new_hole), + ); + + assign_to_symbols(env, procs, layout_cache, iter, result) + } + _ => { + debug_assert_eq!( + function_layout.arguments.len(), + field_symbols.len(), + "function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}", + proc_name, + function_layout, + function_layout.arguments.len(), + field_symbols.len(), + ); + + let call = self::Call { + call_type: CallType::ByName { + name: proc_name, + ret_layout: function_layout.result, + arg_layouts: function_layout.arguments, + specialization_id: env.next_call_specialization_id(), + }, + arguments: field_symbols, + }; + + let result = build_call(env, call, assigned, function_layout.result, hole); + + assign_to_symbols(env, procs, layout_cache, iter, result) + } + } } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 959d679cb3..507bcfaf7e 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -599,7 +599,7 @@ impl<'a> LambdaSet<'a> { // this can happen when there is a type error somewhere Ok(LambdaSet { set: &[], - representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))), + representation: arena.alloc(Layout::Struct(&[])), }) } _ => panic!("called LambdaSet.from_var on invalid input"), diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 460294c9b3..72298d6209 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -32,7 +32,7 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { #[inline(always)] pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located>>, SyntaxError<'a>> { - // force that we pare until the end of the input + // force that we parse until the end of the input let min_indent = 0; skip_second!( specialize( diff --git a/compiler/parse/src/test_helpers.rs b/compiler/parse/src/test_helpers.rs index 8f7682dc54..148b43fd54 100644 --- a/compiler/parse/src/test_helpers.rs +++ b/compiler/parse/src/test_helpers.rs @@ -1,6 +1,9 @@ use crate::ast; +use crate::module::module_defs; // use crate::module::module_defs; +use crate::parser::Parser; use crate::parser::{State, SyntaxError}; +use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use roc_region::all::Located; @@ -23,3 +26,15 @@ pub fn parse_loc_with<'a>( Err(fail) => Err(SyntaxError::Expr(fail)), } } + +pub fn parse_defs_with<'a>( + arena: &'a Bump, + input: &'a str, +) -> Result>>, SyntaxError<'a>> { + let state = State::new(input.trim().as_bytes()); + + match module_defs().parse(arena, state) { + Ok(tuple) => Ok(tuple.1), + Err(tuple) => Err(tuple.1), + } +} diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 582b9acceb..758603f2c1 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2031,3 +2031,31 @@ fn map_with_index_multi_record() { RocList<((), ())> ); } + +#[test] +fn empty_list_of_function_type() { + // see https://github.com/rtfeldman/roc/issues/1732 + assert_evals_to!( + indoc!( + r#" + myList : List (Str -> Str) + myList = [] + + myClosure : Str -> Str + myClosure = \_ -> "bar" + + choose = + if False then + myList + else + [ myClosure ] + + when List.get choose 0 is + Ok f -> f "foo" + Err _ -> "bad!" + "# + ), + RocStr::from_slice(b"bar"), + RocStr + ); +} diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 1de82fa5f7..d6a3a38049 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2980,3 +2980,30 @@ fn nested_rigid_tag_union() { RocStr ); } + +#[test] +fn call_that_needs_closure_parameter() { + // here both p2 is lifted to the top-level, which means that `list` must be + // passed to it from `manyAux`. + assert_evals_to!( + indoc!( + r#" + Step state a : [ Loop state, Done a ] + + manyAux : List a -> [ Pair (Step (List a) (List a))] + manyAux = \list -> + p2 = \_ -> Pair (Done list) + + p2 "foo" + + manyAuxTest = (manyAux [ ]) == Pair (Loop [97]) + + runTest = \t -> if t then "PASS" else "FAIL" + + runTest manyAuxTest + "# + ), + RocStr::from_slice(b"FAIL"), + RocStr + ); +} diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 2b5bdb45ad..292946403e 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -949,3 +949,31 @@ fn str_from_utf8_range_count_too_high_for_start() { RocStr ); } + +#[test] +fn str_repeat_small() { + assert_evals_to!( + indoc!(r#"Str.repeat "Roc" 3"#), + RocStr::from("RocRocRoc"), + RocStr + ); +} + +#[test] +fn str_repeat_big() { + assert_evals_to!( + indoc!(r#"Str.repeat "more than 16 characters" 2"#), + RocStr::from("more than 16 charactersmore than 16 characters"), + RocStr + ); +} + +#[test] +fn str_repeat_empty_string() { + assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr); +} + +#[test] +fn str_repeat_zero_times() { + assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); +} diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt new file mode 100644 index 0000000000..89aa3d309b --- /dev/null +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -0,0 +1,43 @@ +procedure List.3 (#Attr.2, #Attr.3): + let Test.20 = lowlevel ListLen #Attr.2; + let Test.17 = lowlevel NumLt #Attr.3 Test.20; + if Test.17 then + let Test.19 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 = Ok Test.19; + ret Test.18; + else + let Test.16 = Struct {}; + let Test.15 = Err Test.16; + ret Test.15; + +procedure Test.2 (Test.6): + let Test.24 = "bar"; + ret Test.24; + +procedure Test.0 (): + let Test.1 = Array []; + joinpoint Test.22 Test.3: + let Test.14 = 0i64; + let Test.7 = CallByName List.3 Test.3 Test.14; + dec Test.3; + let Test.11 = 1i64; + let Test.12 = GetTagId Test.7; + let Test.13 = lowlevel Eq Test.11 Test.12; + if Test.13 then + let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.9 = "foo"; + let Test.8 = CallByName Test.2 Test.9; + dec Test.9; + ret Test.8; + else + let Test.10 = "bad!"; + ret Test.10; + in + let Test.25 = false; + if Test.25 then + jump Test.22 Test.1; + else + dec Test.1; + let Test.23 = Struct {}; + let Test.21 = Array [Test.23]; + jump Test.22 Test.21; diff --git a/compiler/test_mono/src/lib.rs b/compiler/test_mono/src/lib.rs index 2d05f9cd16..cbcdbed066 100644 --- a/compiler/test_mono/src/lib.rs +++ b/compiler/test_mono/src/lib.rs @@ -1084,6 +1084,33 @@ fn specialize_lowlevel() { ) } +#[mono_test] +fn empty_list_of_function_type() { + // see https://github.com/rtfeldman/roc/issues/1732 + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + myList : List (Str -> Str) + myList = [] + + myClosure : Str -> Str + myClosure = \_ -> "bar" + + choose = + if False then + myList + else + [ myClosure ] + + when List.get choose 0 is + Ok f -> f "foo" + Err _ -> "bad!" + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 5c4f71525f..603498ce87 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -9,6 +9,8 @@ exclude = ["src/shaders/*.spv"] [dependencies] roc_collections = { path = "../compiler/collections" } +roc_load = { path = "../compiler/load" } +roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } @@ -27,13 +29,13 @@ arraystring = "0.3.0" libc = "0.2" page_size = "0.4" winit = "0.24" -wgpu = "0.9" +wgpu = "0.10" glyph_brush = "0.7" log = "0.4" zerocopy = "0.3" env_logger = "0.8" futures = "0.3" -wgpu_glyph = "0.13" +wgpu_glyph = "0.14" cgmath = "0.18.0" snafu = { version = "0.6", features = ["backtraces"] } colored = "2" @@ -49,6 +51,9 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [ ], default-features = false } serde = { version = "1.0.123", features = ["derive"] } nonempty = "0.6.0" +tempfile = "3.2.0" +uuid = { version = "0.8", features = ["v4"] } +fs_extra = "1.2.0" [dependencies.bytemuck] version = "1.4" diff --git a/editor/src/editor/code_lines.rs b/editor/src/editor/code_lines.rs index 63f7b52472..4298c877f4 100644 --- a/editor/src/editor/code_lines.rs +++ b/editor/src/editor/code_lines.rs @@ -1,10 +1,10 @@ use crate::ui::text::lines::Lines; use crate::ui::text::selection::Selection; -use crate::ui::ui_error::UIResult; +use crate::ui::text::text_pos::TextPos; +use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::slice_get; use crate::ui::util::slice_get_mut; -use bumpalo::collections::String as BumpString; -use bumpalo::Bump; +use std::cmp::Ordering; use std::fmt; #[derive(Debug)] @@ -14,31 +14,102 @@ pub struct CodeLines { } impl CodeLines { - pub fn from_str(code_str: &str) -> CodeLines { - CodeLines { - lines: code_str - .split_inclusive('\n') - .map(|s| s.to_owned()) - .collect(), - nr_of_chars: code_str.len(), - } - } - pub fn insert_between_line( &mut self, line_nr: usize, index: usize, new_str: &str, ) -> UIResult<()> { - let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + let nr_of_lines = self.lines.len(); - line_ref.insert_str(index, new_str); + if line_nr < nr_of_lines { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.insert_str(index, new_str); + } else if line_nr >= self.lines.len() { + for _ in 0..((line_nr - nr_of_lines) + 1) { + self.push_empty_line(); + } + + self.insert_between_line(line_nr, index, new_str)?; + } else { + LineInsertionFailed { + line_nr, + nr_of_lines, + } + .fail()?; + } self.nr_of_chars += new_str.len(); Ok(()) } + pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> { + if line_nr <= self.lines.len() { + self.lines.insert(line_nr, String::new()); + + Ok(()) + } else { + OutOfBounds { + index: line_nr, + collection_name: "code_lines.lines".to_owned(), + len: self.lines.len(), + } + .fail() + } + } + + pub fn push_empty_line(&mut self) { + self.lines.push(String::new()) + } + + pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> { + // clippy prefers this over if-else + match line_nr.cmp(&self.lines.len()) { + Ordering::Less => { + self.insert_empty_line(line_nr + 1)?; + + let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr + + if col_nr < line_ref.len() { + let next_line_str: String = line_ref.drain(col_nr..).collect(); + + let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line + + *next_line_ref = next_line_str; + } + + Ok(()) + } + Ordering::Equal => self.insert_empty_line(line_nr + 1), + Ordering::Greater => OutOfBounds { + index: line_nr, + collection_name: "code_lines.lines".to_owned(), + len: self.lines.len(), + } + .fail(), + } + } + + pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + *line_ref = String::new(); + + Ok(()) + } + + pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> { + let line_len = self.line_len(line_nr)?; + + self.lines.remove(line_nr); + + self.nr_of_chars -= line_len; + + Ok(()) + } + pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { let line_ref = slice_get_mut(line_nr, &mut self.lines)?; @@ -49,6 +120,18 @@ impl CodeLines { Ok(()) } + pub fn del_range_at_line( + &mut self, + line_nr: usize, + col_range: std::ops::Range, + ) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.drain(col_range); + + Ok(()) + } + pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> { if selection.is_on_same_line() { let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?; @@ -60,17 +143,36 @@ impl CodeLines { Ok(()) } + + // last column of last line + pub fn end_txt_pos(&self) -> TextPos { + let last_line_nr = self.nr_of_lines() - 1; + + TextPos { + line: last_line_nr, + column: self.line_len(last_line_nr).unwrap(), // safe because we just calculated last_line + } + } +} + +impl Default for CodeLines { + fn default() -> Self { + CodeLines { + lines: Vec::new(), + nr_of_chars: 0, + } + } } impl Lines for CodeLines { - fn get_line(&self, line_nr: usize) -> UIResult<&str> { + fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> { let line_string = slice_get(line_nr, &self.lines)?; Ok(line_string) } fn line_len(&self, line_nr: usize) -> UIResult { - self.get_line(line_nr).map(|line| line.len()) + self.get_line_ref(line_nr).map(|line| line.len()) } fn nr_of_lines(&self) -> usize { @@ -81,14 +183,8 @@ impl Lines for CodeLines { self.nr_of_chars } - fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { - let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena); - - for line in &self.lines { - lines.push_str(line); - } - - lines + fn all_lines_as_string(&self) -> String { + self.lines.join("\n") } fn is_last_line(&self, line_nr: usize) -> bool { @@ -96,7 +192,7 @@ impl Lines for CodeLines { } fn last_char(&self, line_nr: usize) -> UIResult> { - Ok(self.get_line(line_nr)?.chars().last()) + Ok(self.get_line_ref(line_nr)?.chars().last()) } } @@ -105,14 +201,16 @@ impl fmt::Display for CodeLines { for row in &self.lines { let row_str = row .chars() - .map(|code_char| format!("'{}'", code_char)) + .map(|code_char| format!("{}", code_char)) .collect::>() - .join(", "); + .join(" "); - write!(f, "\n{}", row_str)?; + let escaped_row_str = row_str.replace("\n", "\\n"); + + write!(f, "\n{}", escaped_row_str)?; } - write!(f, " (code_lines)")?; + writeln!(f, " (code_lines, {:?} lines)", self.lines.len())?; Ok(()) } diff --git a/editor/src/editor/config.rs b/editor/src/editor/config.rs index aa44ca1b07..432bba0f41 100644 --- a/editor/src/editor/config.rs +++ b/editor/src/editor/config.rs @@ -5,6 +5,7 @@ use crate::editor::theme::EdTheme; #[derive(Serialize, Deserialize)] pub struct Config { pub code_font_size: f32, + pub debug_font_size: f32, pub ed_theme: EdTheme, } @@ -12,6 +13,7 @@ impl Default for Config { fn default() -> Self { Self { code_font_size: 30.0, + debug_font_size: 20.0, ed_theme: EdTheme::default(), } } diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index 2cbd9f35c7..4ed692eefa 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,4 +1,4 @@ -use crate::ui::ui_error::UIResult; +use crate::lang::parse::ASTNodeId; use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; use colored::*; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; @@ -11,6 +11,24 @@ use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; #[derive(Debug, Snafu)] #[snafu(visibility(pub))] pub enum EdError { + #[snafu(display( + "ASTNodeIdWithoutDefId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(DefId)` .", + ast_node_id + ))] + ASTNodeIdWithoutDefId { + ast_node_id: ASTNodeId, + backtrace: Backtrace, + }, + + #[snafu(display( + "ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .", + ast_node_id + ))] + ASTNodeIdWithoutExprId { + ast_node_id: ASTNodeId, + backtrace: Backtrace, + }, + #[snafu(display( "CaretNotFound: No carets were found in the expected node with id {}", node_id @@ -43,6 +61,17 @@ pub enum EdError { backtrace: Backtrace, }, + #[snafu(display( + "EmptyCodeString: I need to have a code string (code_str) that contains either an app, interface or Package-Config header. The code string was empty.", + ))] + EmptyCodeString { backtrace: Backtrace }, + + #[snafu(display("FailedToUpdateIdentIdName: {}", err_str))] + FailedToUpdateIdentIdName { + err_str: String, + backtrace: Backtrace, + }, + #[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))] GetContentOnNestedNode { backtrace: Backtrace }, @@ -99,6 +128,12 @@ pub enum EdError { backtrace: Backtrace, }, + #[snafu(display("NoDefMarkNodeBeforeLineNr: I could not find a MarkupNode whose root parent points to a DefId located before the given line number: {}.", line_nr))] + NoDefMarkNodeBeforeLineNr { + line_nr: usize, + backtrace: Backtrace, + }, + #[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))] NodeWithoutAttributes { backtrace: Backtrace }, @@ -131,6 +166,17 @@ pub enum EdError { backtrace: Backtrace, }, + #[snafu(display( + "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", + required_pattern2, + encountered_pattern2, + ))] + UnexpectedPattern2Variant { + required_pattern2: String, + encountered_pattern2: String, + backtrace: Backtrace, + }, + #[snafu(display( "UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.", descriptive_vec_name @@ -154,7 +200,10 @@ pub enum EdError { }, #[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))] - ParseError { syntax_err: String }, + SrcParseError { + syntax_err: String, + backtrace: Backtrace, + }, #[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))] RecordWithoutFields { backtrace: Backtrace }, @@ -176,14 +225,6 @@ pub fn print_err(err: &EdError) { } } -pub fn print_ui_err(err: &UIError) { - eprintln!("{}", format!("{}", err).truecolor(255, 0, 0)); - - if let Some(backtrace) = ErrorCompat::backtrace(err) { - eprintln!("{}", color_backtrace(backtrace)); - } -} - fn color_backtrace(backtrace: &snafu::Backtrace) -> String { let backtrace_str = format!("{}", backtrace); let backtrace_split = backtrace_str.split('\n'); @@ -242,10 +283,3 @@ impl From for EdError { dummy_res.context(UIErrorBacktrace { msg }).unwrap_err() } } - -pub fn from_ui_res(ui_res: UIResult) -> EdResult { - match ui_res { - Ok(t) => Ok(t), - Err(ui_err) => Err(EdError::from(ui_err)), - } -} diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index f0abf111fe..2cece54f43 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -1,40 +1,28 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::NestedNodeWithoutChildren; -use crate::editor::ed_error::NodeIdNotInGridNodeMap; +use crate::editor::ed_error::{NoDefMarkNodeBeforeLineNr, NodeIdNotInGridNodeMap}; use crate::editor::mvc::ed_model::EdModel; use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::util::first_last_index_of; use crate::editor::util::index_of; -use crate::lang::ast::ExprId; +use crate::lang::parse::ASTNodeId; use crate::ui::text::selection::Selection; use crate::ui::text::text_pos::TextPos; -use crate::ui::ui_error::UIResult; +use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::{slice_get, slice_get_mut}; use snafu::OptionExt; +use std::cmp::Ordering; use std::fmt; +use super::markup::nodes::get_root_mark_node_id; + #[derive(Debug)] pub struct GridNodeMap { pub lines: Vec>, } impl GridNodeMap { - pub fn new() -> GridNodeMap { - GridNodeMap { - lines: vec![vec![]], - } - } - - pub fn add_to_line(&mut self, line_nr: usize, len: usize, node_id: MarkNodeId) -> UIResult<()> { - let line_ref = slice_get_mut(line_nr, &mut self.lines)?; - let mut new_cols_vec: Vec = std::iter::repeat(node_id).take(len).collect(); - - line_ref.append(&mut new_cols_vec); - - Ok(()) - } - pub fn insert_between_line( &mut self, line_nr: usize, @@ -42,18 +30,105 @@ impl GridNodeMap { len: usize, node_id: MarkNodeId, ) -> UIResult<()> { - let line_ref = slice_get_mut(line_nr, &mut self.lines)?; - let new_cols_vec: Vec = std::iter::repeat(node_id).take(len).collect(); + let nr_of_lines = self.lines.len(); - line_ref.splice(index..index, new_cols_vec); + if line_nr < nr_of_lines { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + let new_cols_vec: Vec = std::iter::repeat(node_id).take(len).collect(); + + line_ref.splice(index..index, new_cols_vec); + } else if line_nr >= nr_of_lines { + for _ in 0..((line_nr - nr_of_lines) + 1) { + self.push_empty_line(); + } + + self.insert_between_line(line_nr, index, len, node_id)?; + } else { + LineInsertionFailed { + line_nr, + nr_of_lines, + } + .fail()?; + } Ok(()) } - pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { + pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> { + if line_nr <= self.lines.len() { + self.lines.insert(line_nr, Vec::new()); + + Ok(()) + } else { + OutOfBounds { + index: line_nr, + collection_name: "code_lines.lines".to_owned(), + len: self.lines.len(), + } + .fail() + } + } + + pub fn push_empty_line(&mut self) { + self.lines.push(vec![]); + } + + pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> { + // clippy prefers this over if-else + match line_nr.cmp(&self.lines.len()) { + Ordering::Less => { + self.insert_empty_line(line_nr + 1)?; + + let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr + + if col_nr < line_ref.len() { + let next_line_str: Vec = line_ref.drain(col_nr..).collect(); + + let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line + + *next_line_ref = next_line_str; + } + + Ok(()) + } + Ordering::Equal => self.insert_empty_line(line_nr + 1), + Ordering::Greater => OutOfBounds { + index: line_nr, + collection_name: "grid_node_map.lines".to_owned(), + len: self.lines.len(), + } + .fail(), + } + } + + pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> { let line_ref = slice_get_mut(line_nr, &mut self.lines)?; - line_ref.remove(index); + *line_ref = vec![]; + + Ok(()) + } + + pub fn del_line(&mut self, line_nr: usize) { + self.lines.remove(line_nr); + } + + pub fn del_at_line(&mut self, line_nr: usize, column: usize) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.remove(column); + + Ok(()) + } + + pub fn del_range_at_line( + &mut self, + line_nr: usize, + col_range: std::ops::Range, + ) -> UIResult<()> { + let line_ref = slice_get_mut(line_nr, &mut self.lines)?; + + line_ref.drain(col_range); Ok(()) } @@ -64,16 +139,12 @@ impl GridNodeMap { line_ref.drain(selection.start_pos.column..selection.end_pos.column); } else { - // TODO support multiline + unimplemented!("TODO support deleting multiline selection") } Ok(()) } - /*pub fn new_line(&mut self) { - self.lines.push(vec![]) - }*/ - pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult { let line = slice_get(caret_pos.line, &self.lines)?; let node_id = slice_get(caret_pos.column, line)?; @@ -133,15 +204,15 @@ impl GridNodeMap { } } - // returns start and end pos of Expr2, relevant AST node and MarkNodeId of the corresponding MarkupNode - pub fn get_expr_start_end_pos( + // returns start and end pos of Expr2/Def2, relevant AST node and MarkNodeId of the corresponding MarkupNode + pub fn get_block_start_end_pos( &self, caret_pos: TextPos, ed_model: &EdModel, - ) -> EdResult<(TextPos, TextPos, ExprId, MarkNodeId)> { + ) -> EdResult<(TextPos, TextPos, ASTNodeId, MarkNodeId)> { let line = slice_get(caret_pos.line, &self.lines)?; let node_id = slice_get(caret_pos.column, line)?; - let node = ed_model.markup_node_pool.get(*node_id); + let node = ed_model.mark_node_pool.get(*node_id); if node.is_nested() { let (start_pos, end_pos) = self.get_nested_start_end_pos(*node_id, ed_model)?; @@ -151,10 +222,7 @@ impl GridNodeMap { let (first_node_index, last_node_index) = first_last_index_of(*node_id, line)?; let curr_node_id = slice_get(first_node_index, line)?; - let curr_ast_node_id = ed_model - .markup_node_pool - .get(*curr_node_id) - .get_ast_node_id(); + let curr_ast_node_id = ed_model.mark_node_pool.get(*curr_node_id).get_ast_node_id(); let mut expr_start_index = first_node_index; let mut expr_end_index = last_node_index; @@ -165,7 +233,7 @@ impl GridNodeMap { for i in (0..first_node_index).rev() { let prev_pos_node_id = slice_get(i, line)?; let prev_ast_node_id = ed_model - .markup_node_pool + .mark_node_pool .get(*prev_pos_node_id) .get_ast_node_id(); @@ -187,7 +255,7 @@ impl GridNodeMap { for i in last_node_index..line.len() { let next_pos_node_id = slice_get(i, line)?; let next_ast_node_id = ed_model - .markup_node_pool + .mark_node_pool .get(*next_pos_node_id) .get_ast_node_id(); @@ -204,7 +272,7 @@ impl GridNodeMap { } let correct_mark_node_id = - GridNodeMap::get_top_node_with_expr_id(*curr_node_id, &ed_model.markup_node_pool); + GridNodeMap::get_top_node_with_expr_id(*curr_node_id, &ed_model.mark_node_pool); Ok(( TextPos { @@ -225,12 +293,12 @@ impl GridNodeMap { // `{` is not the entire Expr2 fn get_top_node_with_expr_id( curr_node_id: MarkNodeId, - markup_node_pool: &SlowPool, + mark_node_pool: &SlowPool, ) -> MarkNodeId { - let curr_node = markup_node_pool.get(curr_node_id); + let curr_node = mark_node_pool.get(curr_node_id); if let Some(parent_id) = curr_node.get_parent_id_opt() { - let parent = markup_node_pool.get(parent_id); + let parent = mark_node_pool.get(parent_id); if parent.get_ast_node_id() == curr_node.get_ast_node_id() { parent_id @@ -247,30 +315,109 @@ impl GridNodeMap { nested_node_id: MarkNodeId, ed_model: &EdModel, ) -> EdResult<(TextPos, TextPos)> { - let parent_mark_node = ed_model.markup_node_pool.get(nested_node_id); + let left_most_leaf = self.get_leftmost_leaf(nested_node_id, ed_model)?; - let all_child_ids = parent_mark_node.get_children_ids(); - let first_child_id = all_child_ids - .first() - .with_context(|| NestedNodeWithoutChildren { - node_id: nested_node_id, - })?; - let last_child_id = all_child_ids - .last() - .with_context(|| NestedNodeWithoutChildren { - node_id: nested_node_id, - })?; + let right_most_leaf = self.get_rightmost_leaf(nested_node_id, ed_model)?; let expr_start_pos = ed_model .grid_node_map - .get_node_position(*first_child_id, true)?; + .get_node_position(left_most_leaf, true)?; let expr_end_pos = ed_model .grid_node_map - .get_node_position(*last_child_id, false)? + .get_node_position(right_most_leaf, false)? .increment_col(); Ok((expr_start_pos, expr_end_pos)) } + + fn get_leftmost_leaf( + &self, + nested_node_id: MarkNodeId, + ed_model: &EdModel, + ) -> EdResult { + let mut children_ids = ed_model + .mark_node_pool + .get(nested_node_id) + .get_children_ids(); + let mut first_child_id = 0; + + while !children_ids.is_empty() { + first_child_id = *children_ids + .first() + .with_context(|| NestedNodeWithoutChildren { + node_id: nested_node_id, + })?; + + children_ids = ed_model + .mark_node_pool + .get(first_child_id) + .get_children_ids(); + } + + Ok(first_child_id) + } + + fn get_rightmost_leaf( + &self, + nested_node_id: MarkNodeId, + ed_model: &EdModel, + ) -> EdResult { + let mut children_ids = ed_model + .mark_node_pool + .get(nested_node_id) + .get_children_ids(); + let mut last_child_id = 0; + + while !children_ids.is_empty() { + last_child_id = *children_ids + .last() + .with_context(|| NestedNodeWithoutChildren { + node_id: nested_node_id, + })?; + + children_ids = ed_model + .mark_node_pool + .get(last_child_id) + .get_children_ids(); + } + + Ok(last_child_id) + } + + // get id of root mark_node whose ast_node_id points to a DefId + pub fn get_def_mark_node_id_before_line( + &self, + line_nr: usize, + mark_node_pool: &SlowPool, + ) -> EdResult { + for curr_line_nr in (0..line_nr).rev() { + let first_col_pos = TextPos { + line: curr_line_nr, + column: 0, + }; + + if self.node_exists_at_pos(first_col_pos) { + let mark_node_id = self.get_id_at_row_col(first_col_pos)?; + let root_mark_node_id = get_root_mark_node_id(mark_node_id, mark_node_pool); + + let ast_node_id = mark_node_pool.get(root_mark_node_id).get_ast_node_id(); + + if let ASTNodeId::ADefId(_) = ast_node_id { + return Ok(root_mark_node_id); + } + } + } + + NoDefMarkNodeBeforeLineNr { line_nr }.fail() + } +} + +impl Default for GridNodeMap { + fn default() -> Self { + GridNodeMap { + lines: vec![Vec::new()], + } + } } impl fmt::Display for GridNodeMap { @@ -282,10 +429,10 @@ impl fmt::Display for GridNodeMap { .collect::>() .join(", "); - write!(f, "{}", row_str)?; + writeln!(f, "{}", row_str)?; } - write!(f, " (grid_node_map)")?; + writeln!(f, "(grid_node_map, {:?} lines)", self.lines.len())?; Ok(()) } diff --git a/editor/src/editor/keyboard_input.rs b/editor/src/editor/keyboard_input.rs index a76ff9d24e..95538347a3 100644 --- a/editor/src/editor/keyboard_input.rs +++ b/editor/src/editor/keyboard_input.rs @@ -43,7 +43,7 @@ pub fn handle_keydown( } } - A | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, + A | S | R | Home | End => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, F11 => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?, diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index ad8294a9a6..ba31393054 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -1,9 +1,8 @@ use super::keyboard_input; use super::style::CODE_TXT_XY; -use crate::editor::ed_error::print_ui_err; use crate::editor::mvc::ed_view; use crate::editor::mvc::ed_view::RenderedWgpu; -use crate::editor::resources::strings::NOTHING_OPENED; +use crate::editor::resources::strings::{HELLO_WORLD, NOTHING_OPENED}; use crate::editor::{ config::Config, ed_error::print_err, @@ -20,17 +19,23 @@ use crate::graphics::{ }; use crate::lang::expr::Env; use crate::lang::pool::Pool; -use crate::ui::ui_error::UIError::FileOpenFailed; -use crate::ui::util::slice_get; -use bumpalo::collections::String as BumpString; +use crate::ui::text::caret_w_select::CaretPos; +use crate::ui::util::path_to_string; use bumpalo::Bump; use cgmath::Vector2; +use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use pipelines::RectResources; -use roc_module::symbol::Interns; -use roc_module::symbol::{IdentIds, ModuleIds}; +use roc_can::builtins::builtin_defs_map; +use roc_collections::all::MutMap; +use roc_load; +use roc_load::file::LoadedModule; +use roc_module::symbol::IdentIds; use roc_types::subs::VarStore; +use std::collections::HashSet; +use std::fs::{self, File}; +use std::io::Write; use std::{error::Error, io, path::Path}; -use wgpu::{CommandEncoder, RenderPass, TextureView}; +use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView}; use wgpu_glyph::GlyphBrush; use winit::{ dpi::PhysicalSize, @@ -48,26 +53,13 @@ use winit::{ /// The editor is actually launched from the CLI if you pass it zero arguments, /// or if you provide it 1 or more files or directories to open on launch. -pub fn launch(filepaths: &[&Path]) -> io::Result<()> { - //TODO support using multiple filepaths - let first_path_opt = if !filepaths.is_empty() { - match slice_get(0, filepaths) { - Ok(path_ref_ref) => Some(*path_ref_ref), - Err(e) => { - eprintln!("{}", e); - None - } - } - } else { - None - }; - - run_event_loop(first_path_opt).expect("Error running event loop"); +pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> { + run_event_loop(project_dir_path_opt).expect("Error running event loop"); Ok(()) } -fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { +fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box> { env_logger::init(); // Open window and create a surface @@ -78,7 +70,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { .build(&event_loop) .unwrap(); - let instance = wgpu::Instance::new(wgpu::BackendBit::all()); + let instance = wgpu::Instance::new(wgpu::Backends::all()); let surface = unsafe { instance.create_surface(&window) }; @@ -114,17 +106,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let render_format = wgpu::TextureFormat::Bgra8Unorm; let mut size = window.inner_size(); - let swap_chain_descr = wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: render_format, width: size.width, height: size.height, present_mode: wgpu::PresentMode::Mailbox, }; - let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr); + surface.configure(&gpu_device, &surface_config); - let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr); + let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config); let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; @@ -134,51 +126,37 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let env_arena = Bump::new(); let code_arena = Bump::new(); + let (file_path_str, code_str) = read_main_roc_file(project_dir_path_opt); + println!("Loading file {}...", file_path_str); + + let file_path = Path::new(&file_path_str); + + let loaded_module = load_module(file_path); + let mut var_store = VarStore::default(); let dep_idents = IdentIds::exposed_builtins(8); - let exposed_ident_ids = IdentIds::default(); - let mut module_ids = ModuleIds::default(); - let mod_id = module_ids.get_or_insert(&"ModId123".into()); - - let interns = Interns { - module_ids, - all_ident_ids: IdentIds::exposed_builtins(8), - }; + let module_ids = loaded_module.interns.module_ids.clone(); let env = Env::new( - mod_id, + loaded_module.module_id, &env_arena, &mut env_pool, &mut var_store, dep_idents, - &interns.module_ids, + &module_ids, exposed_ident_ids, ); - let mut code_str = BumpString::from_str_in("", &code_arena); - - let file_path = if let Some(file_path) = file_path_opt { - match std::fs::read_to_string(file_path) { - Ok(file_as_str) => { - code_str = BumpString::from_str_in(&file_as_str, &code_arena); - file_path - } - - Err(e) => { - print_ui_err(&FileOpenFailed { - path_str: file_path.to_string_lossy().to_string(), - err_msg: e.to_string(), - }); - Path::new("") - } - } - } else { - Path::new("") - }; - let ed_model_opt = { - let ed_model_res = ed_model::init_model(&code_str, file_path, env, &interns, &code_arena); + let ed_model_res = ed_model::init_model( + &code_str, + file_path, + env, + loaded_module, + &code_arena, + CaretPos::End, + ); match ed_model_res { Ok(mut ed_model) => { @@ -227,10 +205,10 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { } => { size = new_size; - swap_chain = gpu_device.create_swap_chain( - &surface, - &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + surface.configure( + &gpu_device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: render_format, width: size.width, height: size.height, @@ -251,7 +229,8 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { event: event::WindowEvent::ReceivedCharacter(ch), .. } => { - let input_outcome_res = app_update::handle_new_char(&ch, &mut app_model); + let input_outcome_res = + app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers); if let Err(e) = input_outcome_res { print_err(&e) } else if let Ok(InputOutcome::Ignored) = input_outcome_res { @@ -295,11 +274,15 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { label: Some("Redraw"), }); - let frame = swap_chain + let frame = surface .get_current_frame() .expect("Failed to acquire next SwapChainFrame") .output; + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + if let Some(ref mut ed_model) = app_model.ed_model_opt { if rendered_wgpu_opt.is_none() || ed_model.dirty { let rendered_wgpu_res = @@ -314,23 +297,55 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { } if let Some(ref rendered_wgpu) = rendered_wgpu_opt { - for text_section in &rendered_wgpu.text_sections { + draw_rects( + &rendered_wgpu.rects_behind, + &mut encoder, + &view, + &gpu_device, + &rect_resources, + wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)), + ); + + for text_section in &rendered_wgpu.text_sections_behind { let borrowed_text = text_section.to_borrowed(); glyph_brush.queue(borrowed_text); } - draw_all_rects( - &rendered_wgpu.rects, + // draw first layer of text + glyph_brush + .draw_queued( + &gpu_device, + &mut staging_belt, + &mut encoder, + &view, + size.width, + size.height, + ) + .expect("Failed to draw first layer of text."); + + // draw rects on top of first text layer + draw_rects( + &rendered_wgpu.rects_front, &mut encoder, - &frame.view, + &view, &gpu_device, &rect_resources, - &ed_theme, - ) + wgpu::LoadOp::Load, + ); + + for text_section in &rendered_wgpu.text_sections_front { + let borrowed_text = text_section.to_borrowed(); + + glyph_brush.queue(borrowed_text); + } } } else { - begin_render_pass(&mut encoder, &frame.view, &ed_theme); + begin_render_pass( + &mut encoder, + &view, + wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)), + ); queue_no_file_text( &size, @@ -341,17 +356,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { ); } - // draw all text + // draw text glyph_brush .draw_queued( &gpu_device, &mut staging_belt, &mut encoder, - &frame.view, + &view, size.width, size.height, ) - .expect("Draw queued"); + .expect("Failed to draw queued text."); staging_belt.finish(); cmd_queue.submit(Some(encoder.finish())); @@ -374,17 +389,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { Ok(()) } -fn draw_all_rects( +fn draw_rects( all_rects: &[Rect], encoder: &mut CommandEncoder, texture_view: &TextureView, gpu_device: &wgpu::Device, rect_resources: &RectResources, - ed_theme: &EdTheme, + load_op: LoadOp, ) { let rect_buffers = create_rect_buffers(gpu_device, encoder, all_rects); - let mut render_pass = begin_render_pass(encoder, texture_view, ed_theme); + let mut render_pass = begin_render_pass(encoder, texture_view, load_op); render_pass.set_pipeline(&rect_resources.pipeline); render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); @@ -399,16 +414,14 @@ fn draw_all_rects( fn begin_render_pass<'a>( encoder: &'a mut CommandEncoder, texture_view: &'a TextureView, - ed_theme: &EdTheme, + load_op: LoadOp, ) -> RenderPass<'a> { - let bg_color = to_wgpu_color(ed_theme.background); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[wgpu::RenderPassColorAttachment { view: texture_view, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(bg_color), + load: load_op, store: true, }, }], @@ -417,6 +430,166 @@ fn begin_render_pass<'a>( }) } +type PathStr = String; + +fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathStr, String) { + if let Some(project_dir_path) = project_dir_path_opt { + let mut ls_config = HashSet::new(); + ls_config.insert(DirEntryAttr::FullName); + + let dir_items = ls(project_dir_path, &ls_config) + .unwrap_or_else(|err| panic!("Failed to list items in project directory: {:?}", err)) + .items; + + let file_names = dir_items + .iter() + .map(|info_hash_map| { + info_hash_map + .values() + .map(|dir_entry_value| { + if let DirEntryValue::String(file_name) = dir_entry_value { + Some(file_name) + } else { + None + } + }) + .flatten() // remove None + .collect::>() + }) + .flatten(); + + let roc_file_names: Vec<&String> = file_names + .filter(|file_name| file_name.contains(".roc")) + .collect(); + + let project_dir_path_str = path_to_string(project_dir_path); + + if let Some(&roc_file_name) = roc_file_names.first() { + let full_roc_file_path_str = vec![ + project_dir_path_str.clone(), + "/".to_owned(), + roc_file_name.clone(), + ] + .join(""); + let file_as_str = std::fs::read_to_string(&Path::new(&full_roc_file_path_str)) + .unwrap_or_else(|err| panic!("In the provided project {:?}, I found the roc file {}, but I failed to read it: {}", &project_dir_path_str, &full_roc_file_path_str, err)); + + (full_roc_file_path_str, file_as_str) + } else { + init_new_roc_project(&project_dir_path_str) + } + } else { + init_new_roc_project("new-roc-project") + } +} + +// returns path and content of app file +fn init_new_roc_project(project_dir_path_str: &str) -> (PathStr, String) { + let orig_platform_path = Path::new("./examples/hello-world/platform"); + + let project_dir_path = Path::new(project_dir_path_str); + + let roc_file_path_str = vec![project_dir_path_str, "/UntitledApp.roc"].join(""); + let roc_file_path = Path::new("./new-roc-project/UntitledApp.roc"); + + let project_platform_path_str = vec![project_dir_path_str, "/platform"].join(""); + let project_platform_path = Path::new(&project_platform_path_str); + + if !project_dir_path.exists() { + fs::create_dir(project_dir_path).expect("Failed to create dir for roc project."); + } + + copy_roc_platform_if_not_exists(orig_platform_path, project_platform_path, project_dir_path); + + let code_str = create_roc_file_if_not_exists(project_dir_path, roc_file_path); + + (roc_file_path_str, code_str) +} + +// returns contents of file +fn create_roc_file_if_not_exists(project_dir_path: &Path, roc_file_path: &Path) -> String { + if !roc_file_path.exists() { + let mut roc_file = File::create(roc_file_path).unwrap_or_else(|err| { + panic!("No roc file path was passed to the editor, so I wanted to create a new roc project with the file {:?}, but it failed: {}", roc_file_path, err) + }); + + write!(roc_file, "{}", HELLO_WORLD).unwrap_or_else(|err| { + panic!( + r#"No roc file path was passed to the editor, so I created a new roc project with the file {:?} + I wanted to write roc hello world to that file, but it failed: {:?}"#, + roc_file_path, + err + ) + }); + + HELLO_WORLD.to_string() + } else { + std::fs::read_to_string(roc_file_path).unwrap_or_else(|err| { + panic!( + "I detected an existing {:?} inside {:?}, but I failed to read from it: {}", + roc_file_path, project_dir_path, err + ) + }) + } +} + +fn copy_roc_platform_if_not_exists( + orig_platform_path: &Path, + project_platform_path: &Path, + project_dir_path: &Path, +) { + if !orig_platform_path.exists() && !project_platform_path.exists() { + panic!( + r#"No roc file path was passed to the editor, I wanted to create a new roc project but I could not find the platform at {:?}. + Are you at the root of the roc repository?"#, + orig_platform_path + ); + } else if !project_platform_path.exists() { + copy(orig_platform_path, project_dir_path, &CopyOptions::new()).unwrap_or_else(|err|{ + panic!(r#"No roc file path was passed to the editor, so I wanted to create a new roc project and roc projects require a platform, + I tried to copy the platform at {:?} to {:?} but it failed: {}"#, + orig_platform_path, + project_platform_path, + err + ) + }); + } +} + +pub fn load_module(src_file: &Path) -> LoadedModule { + let subs_by_module = MutMap::default(); + + let arena = Bump::new(); + let loaded = roc_load::file::load_and_typecheck( + &arena, + src_file.to_path_buf(), + arena.alloc(roc_builtins::std::standard_stdlib()), + src_file.parent().unwrap_or_else(|| { + panic!( + "src_file {:?} did not have a parent directory but I need to have one.", + src_file + ) + }), + subs_by_module, + 8, + builtin_defs_map, + ); + + match loaded { + Ok(x) => x, + Err(roc_load::file::LoadingProblem::FormattedReport(report)) => { + panic!( + "Failed to load module from src_file {:?}. Report: {:?}", + src_file, report + ); + } + Err(e) => panic!( + "Failed to load module from src_file {:?}: {:?}", + src_file, e + ), + } +} + fn queue_no_file_text( size: &PhysicalSize, text: &str, diff --git a/editor/src/editor/markup/common_nodes.rs b/editor/src/editor/markup/common_nodes.rs new file mode 100644 index 0000000000..f74648f3fa --- /dev/null +++ b/editor/src/editor/markup/common_nodes.rs @@ -0,0 +1,107 @@ +use crate::{ + editor::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}, + lang::{ast::ExprId, parse::ASTNodeId}, +}; + +use super::{attribute::Attributes, nodes, nodes::MarkupNode}; + +pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::EQUALS.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COMMA.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn_w_nls( + ast_node_id: ASTNodeId, + parent_id_opt: Option, + nr_of_newlines: usize, +) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: nr_of_newlines, + } +} + +pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COLON.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} diff --git a/editor/src/editor/markup/mod.rs b/editor/src/editor/markup/mod.rs index ecae51c6d2..de015ebd80 100644 --- a/editor/src/editor/markup/mod.rs +++ b/editor/src/editor/markup/mod.rs @@ -1,2 +1,3 @@ pub mod attribute; +pub mod common_nodes; pub mod nodes; diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs index 26047fa230..00b5358e88 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/editor/src/editor/markup/nodes.rs @@ -1,42 +1,62 @@ use super::attribute::Attributes; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::ExpectedTextNode; -use crate::editor::ed_error::GetContentOnNestedNode; use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; +use crate::editor::markup::common_nodes::new_blank_mn; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::markup::common_nodes::new_colon_mn; +use crate::editor::markup::common_nodes::new_comma_mn; +use crate::editor::markup::common_nodes::new_equals_mn; +use crate::editor::markup::common_nodes::new_left_accolade_mn; +use crate::editor::markup::common_nodes::new_left_square_mn; +use crate::editor::markup::common_nodes::new_right_accolade_mn; +use crate::editor::markup::common_nodes::new_right_square_mn; +use crate::editor::mvc::tld_value_update::tld_mark_node; use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; -use crate::lang::ast::{Expr2, ExprId, RecordField}; -use crate::lang::{expr::Env, pool::PoolStr}; +use crate::lang::ast::Def2; +use crate::lang::ast::DefId; +use crate::lang::ast::ExprId; +use crate::lang::ast::RecordField; +use crate::lang::ast::ValueDef; +use crate::lang::parse::ASTNodeId; +use crate::lang::parse::{AppHeader, AST}; +use crate::lang::pattern::get_identifier_string; +use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; use crate::ui::util::slice_get; use bumpalo::Bump; +use roc_module::symbol::Interns; use std::fmt; #[derive(Debug)] pub enum MarkupNode { Nested { - ast_node_id: ExprId, + ast_node_id: ASTNodeId, children_ids: Vec, parent_id_opt: Option, + newlines_at_end: usize, }, Text { content: String, - ast_node_id: ExprId, + ast_node_id: ASTNodeId, syn_high_style: HighlightStyle, attributes: Attributes, parent_id_opt: Option, + newlines_at_end: usize, }, Blank { - ast_node_id: ExprId, + ast_node_id: ASTNodeId, attributes: Attributes, syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank parent_id_opt: Option, + newlines_at_end: usize, }, } impl MarkupNode { - pub fn get_ast_node_id(&self) -> ExprId { + pub fn get_ast_node_id(&self) -> ASTNodeId { match self { MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, MarkupNode::Text { ast_node_id, .. } => *ast_node_id, @@ -60,9 +80,9 @@ impl MarkupNode { } } - pub fn get_sibling_ids(&self, markup_node_pool: &SlowPool) -> Vec { + pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec { if let Some(parent_id) = self.get_parent_id_opt() { - let parent_node = markup_node_pool.get(parent_id); + let parent_node = mark_node_pool.get(parent_id); parent_node.get_children_ids() } else { @@ -74,7 +94,7 @@ impl MarkupNode { pub fn get_child_indices( &self, child_id: MarkNodeId, - markup_node_pool: &SlowPool, + mark_node_pool: &SlowPool, ) -> EdResult<(usize, usize)> { match self { MarkupNode::Nested { children_ids, .. } => { @@ -87,7 +107,7 @@ impl MarkupNode { mark_child_index_opt = Some(indx); } - let child_mark_node = markup_node_pool.get(mark_child_id); + let child_mark_node = mark_node_pool.get(mark_child_id); // a node that points to the same ast_node as the parent is a ',', '[', ']' // those are not "real" ast children if child_mark_node.get_ast_node_id() != self_ast_id { @@ -147,15 +167,25 @@ impl MarkupNode { } } - // can't be &str, this creates borrowing issues - pub fn get_content(&self) -> EdResult { + pub fn get_content(&self) -> String { match self { - MarkupNode::Nested { .. } => GetContentOnNestedNode {}.fail(), - MarkupNode::Text { content, .. } => Ok(content.clone()), - MarkupNode::Blank { .. } => Ok(BLANK_PLACEHOLDER.to_owned()), + MarkupNode::Nested { .. } => "".to_owned(), + MarkupNode::Text { content, .. } => content.clone(), + MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), } } + // gets content and adds newline from newline_at_end + pub fn get_full_content(&self) -> String { + let mut full_content = self.get_content(); + + for _ in 0..self.get_newlines_at_end() { + full_content.push('\n') + } + + full_content + } + pub fn get_content_mut(&mut self) -> EdResult<&mut String> { match self { MarkupNode::Nested { .. } => ExpectedTextNode { @@ -172,11 +202,10 @@ impl MarkupNode { } } - pub fn is_all_alphanumeric(&self) -> EdResult { - Ok(self - .get_content()? + pub fn is_all_alphanumeric(&self) -> bool { + self.get_content() .chars() - .all(|chr| chr.is_ascii_alphanumeric())) + .all(|chr| chr.is_ascii_alphanumeric()) } pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> { @@ -209,6 +238,34 @@ impl MarkupNode { pub fn is_nested(&self) -> bool { matches!(self, MarkupNode::Nested { .. }) } + + pub fn get_newlines_at_end(&self) -> usize { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end, + } + } + + pub fn add_newline_at_end(&mut self) { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end += 1, + } + } } fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { @@ -223,12 +280,13 @@ pub const RIGHT_SQUARE_BR: &str = " ]"; pub const COLON: &str = ": "; pub const COMMA: &str = ", "; pub const STRING_QUOTES: &str = "\"\""; +pub const EQUALS: &str = " = "; fn new_markup_node( text: String, - node_id: ExprId, + node_id: ASTNodeId, highlight_style: HighlightStyle, - markup_node_pool: &mut SlowPool, + mark_node_pool: &mut SlowPool, ) -> MarkNodeId { let node = MarkupNode::Text { content: text, @@ -236,9 +294,51 @@ fn new_markup_node( syn_high_style: highlight_style, attributes: Attributes::new(), parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(node) + mark_node_pool.add(node) +} + +pub fn def2_to_markup<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + def2: &Def2, + def2_node_id: DefId, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> EdResult { + let ast_node_id = ASTNodeId::ADefId(def2_node_id); + + let mark_node_id = match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + let expr_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let tld_mn = tld_mark_node( + *identifier_id, + expr_mn_id, + ast_node_id, + mark_node_pool, + env, + interns, + )?; + + mark_node_pool.add(tld_mn) + } + Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), + }; + + Ok(mark_node_id) } // make Markup Nodes: generate String representation, assign Highlighting Style @@ -247,55 +347,44 @@ pub fn expr2_to_markup<'a, 'b>( env: &mut Env<'b>, expr2: &Expr2, expr2_node_id: ExprId, - markup_node_pool: &mut SlowPool, -) -> MarkNodeId { - match expr2 { + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> EdResult { + let ast_node_id = ASTNodeId::AExprId(expr2_node_id); + + let mark_node_id = match expr2 { Expr2::SmallInt { text, .. } | Expr2::I128 { text, .. } | Expr2::U128 { text, .. } | Expr2::Float { text, .. } => { let num_str = get_string(env, text); - new_markup_node( - num_str, - expr2_node_id, - HighlightStyle::Number, - markup_node_pool, - ) + new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) } Expr2::Str(text) => new_markup_node( "\"".to_owned() + text.as_str(env.pool) + "\"", - expr2_node_id, + ast_node_id, HighlightStyle::String, - markup_node_pool, + mark_node_pool, ), Expr2::GlobalTag { name, .. } => new_markup_node( get_string(env, name), - expr2_node_id, + ast_node_id, HighlightStyle::Type, - markup_node_pool, + mark_node_pool, ), Expr2::Call { expr: expr_id, .. } => { let expr = env.pool.get(*expr_id); - expr2_to_markup(arena, env, expr, *expr_id, markup_node_pool) + expr2_to_markup(arena, env, expr, *expr_id, mark_node_pool, interns)? } Expr2::Var(symbol) => { //TODO make bump_format with arena let text = format!("{:?}", symbol); - new_markup_node( - text, - expr2_node_id, - HighlightStyle::Variable, - markup_node_pool, - ) + new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) } Expr2::List { elems, .. } => { - let mut children_ids = vec![new_markup_node( - LEFT_SQUARE_BR.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )]; + let mut children_ids = + vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; let indexed_node_ids: Vec<(usize, ExprId)> = elems.iter(env.pool).copied().enumerate().collect(); @@ -308,64 +397,43 @@ pub fn expr2_to_markup<'a, 'b>( env, sub_expr2, *node_id, - markup_node_pool, - )); + mark_node_pool, + interns, + )?); if idx + 1 < elems.len() { - children_ids.push(new_markup_node( - ", ".to_string(), - expr2_node_id, - HighlightStyle::Operator, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); } } - children_ids.push(new_markup_node( - RIGHT_SQUARE_BR.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); let list_node = MarkupNode::Nested { - ast_node_id: expr2_node_id, + ast_node_id, children_ids, parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(list_node) + mark_node_pool.add(list_node) } Expr2::EmptyRecord => { let children_ids = vec![ - new_markup_node( - LEFT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - ), - new_markup_node( - RIGHT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - ), + mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), + mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), ]; let record_node = MarkupNode::Nested { - ast_node_id: expr2_node_id, + ast_node_id, children_ids, parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(record_node) + mark_node_pool.add(record_node) } Expr2::Record { fields, .. } => { - let mut children_ids = vec![new_markup_node( - LEFT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )]; + let mut children_ids = + vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; for (idx, field_node_id) in fields.iter_node_ids().enumerate() { let record_field = env.pool.get(field_node_id); @@ -374,21 +442,16 @@ pub fn expr2_to_markup<'a, 'b>( children_ids.push(new_markup_node( field_name.as_str(env.pool).to_owned(), - expr2_node_id, + ast_node_id, HighlightStyle::RecordField, - markup_node_pool, + mark_node_pool, )); match record_field { RecordField::InvalidLabelOnly(_, _) => (), RecordField::LabelOnly(_, _, _) => (), RecordField::LabeledValue(_, _, sub_expr2_node_id) => { - children_ids.push(new_markup_node( - COLON.to_string(), - expr2_node_id, - HighlightStyle::Operator, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); let sub_expr2 = env.pool.get(*sub_expr2_node_id); children_ids.push(expr2_to_markup( @@ -396,66 +459,117 @@ pub fn expr2_to_markup<'a, 'b>( env, sub_expr2, *sub_expr2_node_id, - markup_node_pool, - )); + mark_node_pool, + interns, + )?); } } if idx + 1 < fields.len() { - children_ids.push(new_markup_node( - ", ".to_string(), - expr2_node_id, - HighlightStyle::Operator, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); } } - children_ids.push(new_markup_node( - RIGHT_ACCOLADE.to_string(), - expr2_node_id, - HighlightStyle::Bracket, - markup_node_pool, - )); + children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); let record_node = MarkupNode::Nested { - ast_node_id: expr2_node_id, + ast_node_id, children_ids, parent_id_opt: None, + newlines_at_end: 0, }; - markup_node_pool.add(record_node) + mark_node_pool.add(record_node) + } + Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), + Expr2::LetValue { + def_id, + body_id: _, + body_var: _, + } => { + let pattern_id = env.pool.get(*def_id).get_pattern_id(); + + let pattern2 = env.pool.get(pattern_id); + + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let value_def = env.pool.get(*def_id); + + match value_def { + ValueDef::NoAnnotation { + pattern_id: _, + expr_id, + expr_var: _, + } => { + let body_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let body_mn = mark_node_pool.get_mut(body_mn_id); + body_mn.add_newline_at_end(); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + mark_node_pool.add(full_let_node) + } + other => { + unimplemented!( + "I don't know how to convert {:?} into a MarkupNode yet.", + other + ) + } + } } - Expr2::Blank => markup_node_pool.add(MarkupNode::Blank { - ast_node_id: expr2_node_id, - attributes: Attributes::new(), - syn_high_style: HighlightStyle::Blank, - parent_id_opt: None, - }), Expr2::RuntimeError() => new_markup_node( "RunTimeError".to_string(), - expr2_node_id, + ast_node_id, HighlightStyle::Blank, - markup_node_pool, + mark_node_pool, ), rest => todo!("implement expr2_to_markup for {:?}", rest), - } + }; + + Ok(mark_node_id) } -pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut SlowPool) { - let node = markup_node_pool.get(markup_node_id); +pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { + let node = mark_node_pool.get(markup_node_id); if let MarkupNode::Nested { ast_node_id: _, children_ids, parent_id_opt: _, + newlines_at_end: _, } = node { // need to clone because of borrowing issues let children_ids_clone = children_ids.clone(); for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool); + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); } } } @@ -463,9 +577,9 @@ pub fn set_parent_for_all(markup_node_id: MarkNodeId, markup_node_pool: &mut Slo pub fn set_parent_for_all_helper( markup_node_id: MarkNodeId, parent_node_id: MarkNodeId, - markup_node_pool: &mut SlowPool, + mark_node_pool: &mut SlowPool, ) { - let node = markup_node_pool.get_mut(markup_node_id); + let node = mark_node_pool.get_mut(markup_node_id); match node { MarkupNode::Nested { @@ -479,7 +593,7 @@ pub fn set_parent_for_all_helper( let children_ids_clone = children_ids.clone(); for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, markup_node_pool); + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); } } MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), @@ -487,23 +601,237 @@ pub fn set_parent_for_all_helper( } } +fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::PackageRelated, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +fn header_val_mn( + content: String, + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: highlight_style, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let expr_id = app_header.ast_node_id; + let ast_node_id = ASTNodeId::AExprId(expr_id); + + let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); + + let app_name_node_id = header_val_mn( + app_header.app_name.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let full_app_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![app_node_id, app_name_node_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); + + let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); + + let pack_base_node_id = header_val_mn( + "base: ".to_owned(), + expr_id, + HighlightStyle::RecordField, + mark_node_pool, + ); + + let pack_val_node_id = header_val_mn( + app_header.packages_base.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); + + let full_packages_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + packages_node_id, + pack_left_acc_node_id, + pack_base_node_id, + pack_val_node_id, + pack_right_acc_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); + + let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut import_child_ids: Vec = add_header_mn_list( + &app_header.imports, + expr_id, + HighlightStyle::Import, + mark_node_pool, + ); + + let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; + + full_import_children.append(&mut import_child_ids); + full_import_children.push(imports_right_square_node_id); + + let full_import_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_import_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); + + let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut provides_val_node_ids: Vec = add_header_mn_list( + &app_header.provides, + expr_id, + HighlightStyle::Provides, + mark_node_pool, + ); + + let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); + + let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; + + full_provides_children.append(&mut provides_val_node_ids); + full_provides_children.push(provides_right_square_node_id); + full_provides_children.push(provides_end_node_id); + + let full_provides_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_provides_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let full_app_node_id = mark_node_pool.add(full_app_node); + let full_packages_node = mark_node_pool.add(full_packages_node); + let full_import_node_id = mark_node_pool.add(full_import_node); + let full_provides_node_id = mark_node_pool.add(full_provides_node); + + let header_mark_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + full_app_node_id, + full_packages_node, + full_import_node_id, + full_provides_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let header_mn_id = mark_node_pool.add(header_mark_node); + + set_parent_for_all(header_mn_id, mark_node_pool); + + header_mn_id +} + +// Used for provides and imports +fn add_header_mn_list( + str_vec: &[String], + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> Vec { + let nr_of_elts = str_vec.len(); + + str_vec + .iter() + .enumerate() + .map(|(indx, provide_str)| { + let provide_str = header_val_mn( + provide_str.to_owned(), + expr_id, + highlight_style, + mark_node_pool, + ); + + if indx != nr_of_elts - 1 { + vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] + } else { + vec![provide_str] + } + }) + .flatten() + .collect() +} + +pub fn ast_to_mark_nodes<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + ast: &AST, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> EdResult> { + let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; + + for &def_id in ast.def_ids.iter() { + let def2 = env.pool.get(def_id); + + let expr2_markup_id = def2_to_markup(arena, env, def2, def_id, mark_node_pool, interns)?; + + set_parent_for_all(expr2_markup_id, mark_node_pool); + + all_mark_node_ids.push(expr2_markup_id); + } + + Ok(all_mark_node_ids) +} + impl fmt::Display for MarkupNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!( + write!( f, - "{} ({})", + "{} ({}, {})", self.node_type_as_string(), - self.get_content().unwrap_or_else(|_| "".to_string()) + self.get_content(), + self.get_newlines_at_end() ) } } pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { - let mut full_string = "\n\n(mark_node_tree)\n".to_owned(); + let mut full_string = "\n(mark_node_tree)\n".to_owned(); let node = mark_node_pool.get(root_node_id); - full_string.push_str(&format!("{}", node)); + full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id)); tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); @@ -524,11 +852,25 @@ fn tree_as_string_helper( .to_owned(); let child = mark_node_pool.get(child_id); + let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n"); - full_str.push_str(&format!("{}", child)); + full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id)); tree_string.push_str(&full_str); tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); } } + +// return to the the root parent_id of the current node +pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId { + let mut curr_mark_node_id = mark_node_id; + let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + + while let Some(curr_parent_id) = curr_parent_id_opt { + curr_mark_node_id = curr_parent_id; + curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + } + + curr_mark_node_id +} diff --git a/editor/src/editor/mod.rs b/editor/src/editor/mod.rs index 9a579fea50..89efb3a318 100644 --- a/editor/src/editor/mod.rs +++ b/editor/src/editor/mod.rs @@ -1,6 +1,6 @@ mod code_lines; mod config; -mod ed_error; +pub mod ed_error; mod grid_node_map; mod keyboard_input; pub mod main; diff --git a/editor/src/editor/mvc/app_update.rs b/editor/src/editor/mvc/app_update.rs index 5319577a15..1b6ef6bd60 100644 --- a/editor/src/editor/mvc/app_update.rs +++ b/editor/src/editor/mvc/app_update.rs @@ -1,8 +1,8 @@ use super::app_model::AppModel; use super::ed_update; -use crate::editor::ed_error::EdResult; use crate::window::keyboard_input::Modifiers; -use winit::event::VirtualKeyCode; +use crate::{editor::ed_error::EdResult, window::keyboard_input::from_winit}; +use winit::event::{ModifiersState, VirtualKeyCode}; pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> { if let Some(ref mut ed_model) = app_model.ed_model_opt { @@ -51,16 +51,26 @@ pub fn pass_keydown_to_focused( pub enum InputOutcome { Accepted, Ignored, + SilentIgnored, } -pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResult { +pub fn handle_new_char( + received_char: &char, + app_model: &mut AppModel, + modifiers_winit: ModifiersState, +) -> EdResult { if let Some(ref mut ed_model) = app_model.ed_model_opt { if ed_model.has_focus { - return ed_update::handle_new_char(received_char, ed_model); + let modifiers = from_winit(&modifiers_winit); + + if modifiers.new_char_modifiers() { + // shortcuts with modifiers are handled by ed_handle_key_down + return ed_update::handle_new_char(received_char, ed_model); + } } } - Ok(InputOutcome::Ignored) + Ok(InputOutcome::SilentIgnored) } /* diff --git a/editor/src/editor/mvc/break_line.rs b/editor/src/editor/mvc/break_line.rs new file mode 100644 index 0000000000..6fc8c7ca13 --- /dev/null +++ b/editor/src/editor/mvc/break_line.rs @@ -0,0 +1,80 @@ +use crate::editor::ed_error::EdResult; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::mvc::app_update::InputOutcome; +use crate::editor::mvc::ed_model::EdModel; +use crate::editor::util::index_of; +use crate::lang::ast::Def2; +use crate::lang::parse::ASTNodeId; +use crate::ui::text::text_pos::TextPos; + +// put everything after caret on new line, create a Def2::Blank if there was nothing after the caret. +pub fn break_line(ed_model: &mut EdModel) -> EdResult { + let carets = ed_model.get_carets(); + + for caret_pos in carets.iter() { + let caret_line_nr = caret_pos.line; + + // don't allow adding new lines on empty line + if caret_pos.column > 0 + && ed_model.grid_node_map.node_exists_at_pos(TextPos { + line: caret_line_nr, + column: caret_pos.column - 1, + }) + { + // one blank line between top level definitions + EdModel::insert_empty_line( + caret_line_nr + 1, + &mut ed_model.code_lines, + &mut ed_model.grid_node_map, + )?; + EdModel::insert_empty_line( + caret_line_nr + 1, + &mut ed_model.code_lines, + &mut ed_model.grid_node_map, + )?; + + insert_new_blank(ed_model, caret_pos, caret_pos.line + 2)?; + } + } + + ed_model.simple_move_carets_down(2); // one blank line between top level definitions + + Ok(InputOutcome::Accepted) +} + +pub fn insert_new_blank( + ed_model: &mut EdModel, + caret_pos: &TextPos, + insert_on_line_nr: usize, +) -> EdResult<()> { + let new_line_blank = Def2::Blank; + let new_line_blank_id = ed_model.module.env.pool.add(new_line_blank); + + let prev_def_mn_id = ed_model + .grid_node_map + .get_def_mark_node_id_before_line(caret_pos.line + 1, &ed_model.mark_node_pool)?; + let prev_def_mn_id_indx = index_of(prev_def_mn_id, &ed_model.markup_ids)?; + ed_model + .module + .ast + .def_ids + .insert(prev_def_mn_id_indx, new_line_blank_id); + + let blank_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls( + ASTNodeId::ADefId(new_line_blank_id), + None, + 2, + )); + + ed_model + .markup_ids + .insert(prev_def_mn_id_indx + 1, blank_mn_id); // + 1 because first markup node is header + + ed_model.insert_all_between_line( + insert_on_line_nr, // one blank line between top level definitions + 0, + &[blank_mn_id], + )?; + + Ok(()) +} diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 51f59ee993..a522bc6553 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -1,27 +1,22 @@ use crate::editor::code_lines::CodeLines; use crate::editor::grid_node_map::GridNodeMap; +use crate::editor::markup::nodes::ast_to_mark_nodes; use crate::editor::slow_pool::{MarkNodeId, SlowPool}; -use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::{ - ed_error::EdError::ParseError, - ed_error::{EdResult, MissingParent, NoNodeAtCaretPosition}, - markup::attribute::Attributes, - markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode}, + ed_error::SrcParseError, + ed_error::{EdResult, EmptyCodeString, MissingParent, NoNodeAtCaretPosition}, }; use crate::graphics::primitives::rect::Rect; -use crate::lang::ast::{Expr2, ExprId}; -use crate::lang::expr::{str_to_expr2, Env}; +use crate::lang::expr::Env; +use crate::lang::parse::{ASTNodeId, AST}; use crate::lang::pool::PoolStr; -use crate::lang::scope::Scope; -use crate::ui::text::caret_w_select::CaretWSelect; +use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect}; use crate::ui::text::lines::SelectableLines; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; -use bumpalo::collections::String as BumpString; use bumpalo::Bump; use nonempty::NonEmpty; -use roc_module::symbol::Interns; -use roc_region::all::Region; +use roc_load::file::LoadedModule; use std::path::Path; #[derive(Debug)] @@ -31,83 +26,98 @@ pub struct EdModel<'a> { pub code_lines: CodeLines, // allows us to map window coordinates to MarkNodeId's pub grid_node_map: GridNodeMap, - pub markup_root_id: MarkNodeId, - pub markup_node_pool: SlowPool, + pub markup_ids: Vec, // one root node for every expression + pub mark_node_pool: SlowPool, // contains single char dimensions, used to calculate line height, column width... pub glyph_dim_rect_opt: Option, pub has_focus: bool, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option)>, - pub selected_expr_opt: Option, - pub interns: &'a Interns, // this should eventually come from LoadedModule, see #1442 + pub selected_block_opt: Option, + pub loaded_module: LoadedModule, pub show_debug_view: bool, // EdModel is dirty if it has changed since the previous render. pub dirty: bool, } #[derive(Debug, Copy, Clone)] -pub struct SelectedExpression { - pub ast_node_id: ExprId, +pub struct SelectedBlock { + pub ast_node_id: ASTNodeId, pub mark_node_id: MarkNodeId, pub type_str: PoolStr, } pub fn init_model<'a>( - code_str: &'a BumpString, + code_str: &'a str, file_path: &'a Path, env: Env<'a>, - interns: &'a Interns, + loaded_module: LoadedModule, code_arena: &'a Bump, + caret_pos: CaretPos, // to set caret position ) -> EdResult> { let mut module = EdModule::new(code_str, env, code_arena)?; - let ast_root_id = module.ast_root_id; - let mut markup_node_pool = SlowPool::new(); + let mut mark_node_pool = SlowPool::new(); - let markup_root_id = if code_str.is_empty() { - let blank_root = MarkupNode::Blank { - ast_node_id: ast_root_id, - attributes: Attributes::new(), - syn_high_style: HighlightStyle::Blank, - parent_id_opt: None, - }; - - markup_node_pool.add(blank_root) + let markup_ids = if code_str.is_empty() { + EmptyCodeString {}.fail() } else { - let ast_root = &module.env.pool.get(ast_root_id); - - let temp_markup_root_id = expr2_to_markup( + ast_to_mark_nodes( code_arena, &mut module.env, - ast_root, - ast_root_id, - &mut markup_node_pool, - ); - set_parent_for_all(temp_markup_root_id, &mut markup_node_pool); + &module.ast, + &mut mark_node_pool, + &loaded_module.interns, + ) + }?; - temp_markup_root_id + let mut code_lines = CodeLines::default(); + let mut grid_node_map = GridNodeMap::default(); + + let mut line_nr = 0; + let mut col_nr = 0; + + for mark_node_id in &markup_ids { + EdModel::insert_mark_node_between_line( + &mut line_nr, + &mut col_nr, + *mark_node_id, + &mut grid_node_map, + &mut code_lines, + &mark_node_pool, + )? + } + + let caret = match caret_pos { + CaretPos::Start => CaretWSelect::default(), + CaretPos::Exact(txt_pos) => CaretWSelect::new(txt_pos, None), + CaretPos::End => CaretWSelect::new(code_lines.end_txt_pos(), None), }; - let code_lines = EdModel::build_code_lines_from_markup(markup_root_id, &markup_node_pool)?; - let grid_node_map = EdModel::build_node_map_from_markup(markup_root_id, &markup_node_pool)?; - Ok(EdModel { module, file_path, code_lines, grid_node_map, - markup_root_id, - markup_node_pool, + markup_ids, + mark_node_pool, glyph_dim_rect_opt: None, has_focus: true, - caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)), - selected_expr_opt: None, - interns, + caret_w_select_vec: NonEmpty::new((caret, None)), + selected_block_opt: None, + loaded_module, show_debug_view: false, dirty: true, }) } impl<'a> EdModel<'a> { + pub fn get_carets(&self) -> Vec { + self.caret_w_select_vec + .iter() + .map(|tup| tup.0.caret_pos) + .collect() + } + pub fn get_curr_mark_node_id(&self) -> UIResult { let caret_pos = self.get_caret(); self.grid_node_map.get_id_at_row_col(caret_pos) @@ -138,11 +148,11 @@ impl<'a> EdModel<'a> { pub fn get_curr_child_indices(&self) -> EdResult<(usize, usize)> { if self.node_exists_at_caret() { let curr_mark_node_id = self.get_curr_mark_node_id()?; - let curr_mark_node = self.markup_node_pool.get(curr_mark_node_id); + let curr_mark_node = self.mark_node_pool.get(curr_mark_node_id); if let Some(parent_id) = curr_mark_node.get_parent_id_opt() { - let parent = self.markup_node_pool.get(parent_id); - parent.get_child_indices(curr_mark_node_id, &self.markup_node_pool) + let parent = self.mark_node_pool.get(parent_id); + parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool) } else { MissingParent { node_id: curr_mark_node_id, @@ -161,38 +171,26 @@ impl<'a> EdModel<'a> { #[derive(Debug)] pub struct EdModule<'a> { pub env: Env<'a>, - pub ast_root_id: ExprId, + pub ast: AST, } // for debugging -// use crate::lang::ast::expr2_to_string; +//use crate::lang::ast::expr2_to_string; impl<'a> EdModule<'a> { pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult> { if !code_str.is_empty() { - let mut scope = Scope::new(env.home, env.pool, env.var_store); + let parse_res = AST::parse_from_string(code_str, &mut env, ast_arena); - let region = Region::new(0, 0, 0, 0); - - let expr2_result = str_to_expr2(ast_arena, code_str, &mut env, &mut scope, region); - - match expr2_result { - Ok((expr2, _output)) => { - let ast_root_id = env.pool.add(expr2); - - // for debugging - // dbg!(expr2_to_string(ast_root_id, env.pool)); - - Ok(EdModule { env, ast_root_id }) - } - Err(err) => Err(ParseError { + match parse_res { + Ok(ast) => Ok(EdModule { env, ast }), + Err(err) => SrcParseError { syntax_err: format!("{:?}", err), - }), + } + .fail(), } } else { - let ast_root_id = env.pool.add(Expr2::Blank); - - Ok(EdModule { env, ast_root_id }) + EmptyCodeString {}.fail() } } } @@ -200,40 +198,49 @@ impl<'a> EdModule<'a> { #[cfg(test)] pub mod test_ed_model { use crate::editor::ed_error::EdResult; + use crate::editor::main::load_module; use crate::editor::mvc::ed_model; + use crate::editor::resources::strings::HELLO_WORLD; use crate::lang::expr::Env; use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; + use crate::ui::text::caret_w_select::CaretPos; use crate::ui::text::lines::SelectableLines; + use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; - use bumpalo::collections::String as BumpString; use bumpalo::Bump; use ed_model::EdModel; - use roc_module::symbol::{IdentIds, Interns, ModuleIds}; + use roc_load::file::LoadedModule; + use roc_module::symbol::IdentIds; + use roc_module::symbol::ModuleIds; use roc_types::subs::VarStore; + use std::fs::File; + use std::io::Write; use std::path::Path; + use std::path::PathBuf; + use tempfile::tempdir; + use uuid::Uuid; pub fn init_dummy_model<'a>( - code_str: &'a BumpString, + code_str: &'a str, + loaded_module: LoadedModule, + module_ids: &'a ModuleIds, ed_model_refs: &'a mut EdModelRefs, + code_arena: &'a Bump, ) -> EdResult> { let file_path = Path::new(""); let dep_idents = IdentIds::exposed_builtins(8); let exposed_ident_ids = IdentIds::default(); - let mod_id = ed_model_refs - .interns - .module_ids - .get_or_insert(&"ModId123".into()); let env = Env::new( - mod_id, + loaded_module.module_id, &ed_model_refs.env_arena, &mut ed_model_refs.env_pool, &mut ed_model_refs.var_store, dep_idents, - &ed_model_refs.interns.module_ids, + module_ids, exposed_ident_ids, ); @@ -241,43 +248,69 @@ pub mod test_ed_model { code_str, file_path, env, - &ed_model_refs.interns, - &ed_model_refs.code_arena, + loaded_module, + code_arena, + CaretPos::End, ) } pub struct EdModelRefs { - code_arena: Bump, env_arena: Bump, env_pool: Pool, var_store: VarStore, - interns: Interns, } pub fn init_model_refs() -> EdModelRefs { EdModelRefs { - code_arena: Bump::new(), env_arena: Bump::new(), env_pool: Pool::with_capacity(1024), var_store: VarStore::default(), - interns: Interns { - module_ids: ModuleIds::default(), - all_ident_ids: IdentIds::exposed_builtins(8), - }, } } pub fn ed_model_from_dsl<'a>( - clean_code_str: &'a BumpString, - code_lines: &[&str], + clean_code_str: &'a mut String, + code_lines: Vec, ed_model_refs: &'a mut EdModelRefs, + module_ids: &'a ModuleIds, + code_arena: &'a Bump, ) -> Result, String> { - let code_lines_vec: Vec = (*code_lines).iter().map(|s| s.to_string()).collect(); - let caret_w_select = convert_dsl_to_selection(&code_lines_vec)?; + let full_code = vec![HELLO_WORLD, clean_code_str.as_str()]; + *clean_code_str = full_code.join("\n"); - let mut ed_model = init_dummy_model(clean_code_str, ed_model_refs)?; + let temp_dir = tempdir().expect("Failed to create temporary directory for test."); + let temp_file_path_buf = + PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); + let temp_file_full_path = temp_dir.path().join(temp_file_path_buf); - ed_model.set_caret(caret_w_select.caret_pos); + let mut file = File::create(temp_file_full_path.clone()).expect(&format!( + "Failed to create temporary file for path {:?}", + temp_file_full_path + )); + writeln!(file, "{}", clean_code_str).expect(&format!( + "Failed to write {:?} to file: {:?}", + clean_code_str, file + )); + + let loaded_module = load_module(&temp_file_full_path); + + let mut ed_model = init_dummy_model( + clean_code_str, + loaded_module, + module_ids, + ed_model_refs, + code_arena, + )?; + + // adjust for header and main function + let nr_hello_world_lines = HELLO_WORLD.matches('\n').count() - 2; + let caret_w_select = convert_dsl_to_selection(&code_lines)?; + let adjusted_caret_pos = TextPos { + line: caret_w_select.caret_pos.line + nr_hello_world_lines, + column: caret_w_select.caret_pos.column, + }; + + ed_model.set_caret(adjusted_caret_pos); Ok(ed_model) } diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 22f113bd10..9a515d55c4 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -1,16 +1,19 @@ #![allow(dead_code)] +use std::process::Command; +use std::process::Stdio; + use crate::editor::code_lines::CodeLines; -use crate::editor::ed_error::from_ui_res; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingSelection; use crate::editor::grid_node_map::GridNodeMap; use crate::editor::markup::attribute::Attributes; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; +use crate::editor::markup::nodes::EQUALS; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::mvc::ed_model::SelectedExpression; +use crate::editor::mvc::ed_model::SelectedBlock; use crate::editor::mvc::int_update::start_new_int; use crate::editor::mvc::int_update::update_int; use crate::editor::mvc::list_update::{add_blank_child, start_new_list}; @@ -22,11 +25,15 @@ use crate::editor::mvc::record_update::update_record_field; use crate::editor::mvc::string_update::start_new_string; use crate::editor::mvc::string_update::update_small_string; use crate::editor::mvc::string_update::update_string; +use crate::editor::mvc::tld_value_update::{start_new_tld_value, update_tld_val_name}; use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::Def2; +use crate::lang::ast::DefId; use crate::lang::ast::{Expr2, ExprId}; use crate::lang::constrain::constrain_expr; +use crate::lang::parse::ASTNodeId; use crate::lang::pool::Pool; use crate::lang::pool::PoolStr; use crate::lang::types::Type2; @@ -39,6 +46,8 @@ use crate::ui::text::selection::Selection; use crate::ui::text::text_pos::TextPos; use crate::ui::text::{lines, lines::Lines, lines::SelectableLines}; use crate::ui::ui_error::UIResult; +use crate::ui::util::path_to_string; +use crate::ui::util::write_to_file; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; use roc_can::expected::Expected; @@ -53,6 +62,10 @@ use snafu::OptionExt; use winit::event::VirtualKeyCode; use VirtualKeyCode::*; +use super::break_line::break_line; +use super::break_line::insert_new_blank; +use super::let_update::start_new_let_value; + impl<'a> EdModel<'a> { pub fn move_caret( &mut self, @@ -65,7 +78,7 @@ impl<'a> EdModel<'a> { caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?; caret_tup.1 = None; } - self.selected_expr_opt = None; + self.selected_block_opt = None; Ok(()) } @@ -79,6 +92,18 @@ impl<'a> EdModel<'a> { } } + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + // TODO error if no match was found for old_caret_pos + pub fn simple_move_caret_right(&mut self, old_caret_pos: TextPos, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + if caret_tup.0.caret_pos == old_caret_pos { + caret_tup.0.caret_pos.column += repeat; + caret_tup.1 = None; + } + } + } + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. // allows us to prevent multiple updates to EdModel.code_lines pub fn simple_move_carets_left(&mut self, repeat: usize) { @@ -88,94 +113,248 @@ impl<'a> EdModel<'a> { } } - pub fn build_node_map_from_markup( - markup_root_id: MarkNodeId, - markup_node_pool: &SlowPool, - ) -> EdResult { - let mut grid_node_map = GridNodeMap::new(); - - EdModel::build_grid_node_map(markup_root_id, &mut grid_node_map, markup_node_pool)?; - - Ok(grid_node_map) - } - - fn build_grid_node_map( - node_id: MarkNodeId, - grid_node_map: &mut GridNodeMap, - markup_node_pool: &SlowPool, - ) -> EdResult<()> { - let node = markup_node_pool.get(node_id); - - if node.is_nested() { - for child_id in node.get_children_ids() { - EdModel::build_grid_node_map(child_id, grid_node_map, markup_node_pool)?; - } - } else { - let node_content_str = node.get_content()?; - - grid_node_map.add_to_line(0, node_content_str.len(), node_id)?; + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + pub fn simple_move_carets_down(&mut self, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + caret_tup.0.caret_pos.column = 0; + caret_tup.0.caret_pos.line += repeat; + caret_tup.1 = None; } - - Ok(()) } - pub fn build_code_lines_from_markup( - markup_root_id: MarkNodeId, - markup_node_pool: &SlowPool, - ) -> EdResult { - let mut all_code_string = String::new(); + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + // TODO error if no match was found for old_caret_pos + pub fn simple_move_caret_down(&mut self, old_caret_pos: TextPos, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + if caret_tup.0.caret_pos == old_caret_pos { + caret_tup.0.caret_pos.column = 0; + caret_tup.0.caret_pos.line += repeat; + caret_tup.1 = None; + } + } + } - EdModel::build_markup_string(markup_root_id, &mut all_code_string, markup_node_pool)?; + // disregards EdModel.code_lines because the caller knows the resulting caret position will be valid. + // allows us to prevent multiple updates to EdModel.code_lines + pub fn simple_move_carets_up(&mut self, repeat: usize) { + for caret_tup in self.caret_w_select_vec.iter_mut() { + caret_tup.0.caret_pos.line -= repeat; + caret_tup.1 = None; + } + } - let code_lines = CodeLines::from_str(&all_code_string); - - Ok(code_lines) + pub fn add_mark_node(&mut self, node: MarkupNode) -> MarkNodeId { + self.mark_node_pool.add(node) } fn build_markup_string( node_id: MarkNodeId, all_code_string: &mut String, - markup_node_pool: &SlowPool, + mark_node_pool: &SlowPool, ) -> EdResult<()> { - let node = markup_node_pool.get(node_id); + let node = mark_node_pool.get(node_id); if node.is_nested() { for child_id in node.get_children_ids() { - EdModel::build_markup_string(child_id, all_code_string, markup_node_pool)?; + EdModel::build_markup_string(child_id, all_code_string, mark_node_pool)?; } } else { - let node_content_str = node.get_content()?; + let node_content_str = node.get_content(); all_code_string.push_str(&node_content_str); } + for _ in 0..node.get_newlines_at_end() { + all_code_string.push('\n'); + } + Ok(()) } // updates grid_node_map and code_lines but nothing else. pub fn insert_between_line( - &mut self, line_nr: usize, index: usize, new_str: &str, node_id: MarkNodeId, + grid_node_map: &mut GridNodeMap, + code_lines: &mut CodeLines, ) -> UIResult<()> { - self.grid_node_map - .insert_between_line(line_nr, index, new_str.len(), node_id)?; - self.code_lines.insert_between_line(line_nr, index, new_str) + grid_node_map.insert_between_line(line_nr, index, new_str.len(), node_id)?; + code_lines.insert_between_line(line_nr, index, new_str) + } + + pub fn insert_all_between_line( + &mut self, + line_nr: usize, + index: usize, + leaf_node_ids: &[MarkNodeId], + ) -> UIResult<()> { + let mut col_nr = index; + let mut curr_line_nr = line_nr; + + for &node_id in leaf_node_ids { + let mark_node = self.mark_node_pool.get(node_id); + let node_full_content = mark_node.get_full_content(); + + if node_full_content.contains('\n') { + //insert separate lines separately + let split_lines = node_full_content.split('\n'); + + for line in split_lines { + self.grid_node_map.insert_between_line( + curr_line_nr, + col_nr, + line.len(), + node_id, + )?; + + self.code_lines + .insert_between_line(curr_line_nr, col_nr, line)?; + + curr_line_nr += 1; + col_nr = 0; + } + } else { + let node_content = mark_node.get_content(); + + self.grid_node_map.insert_between_line( + line_nr, + col_nr, + node_content.len(), + node_id, + )?; + + self.code_lines + .insert_between_line(line_nr, col_nr, &node_content)?; + + col_nr += node_content.len(); + } + } + + Ok(()) + } + + pub fn insert_mark_node_between_line( + line_nr: &mut usize, + col_nr: &mut usize, + mark_node_id: MarkNodeId, + grid_node_map: &mut GridNodeMap, + code_lines: &mut CodeLines, + mark_node_pool: &SlowPool, + ) -> UIResult<()> { + let mark_node = mark_node_pool.get(mark_node_id); + let node_newlines = mark_node.get_newlines_at_end(); + + if mark_node.is_nested() { + let children_ids = mark_node.get_children_ids(); + + for child_id in children_ids { + EdModel::insert_mark_node_between_line( + line_nr, + col_nr, + child_id, + grid_node_map, + code_lines, + mark_node_pool, + )?; + } + } else { + let node_content = mark_node.get_content(); + + EdModel::insert_between_line( + *line_nr, + *col_nr, + &node_content, + mark_node_id, + grid_node_map, + code_lines, + )?; + + if node_newlines == 0 { + *col_nr += node_content.len(); + } + } + + if node_newlines > 0 { + EdModel::break_line(*line_nr, *col_nr, code_lines, grid_node_map)?; + + *line_nr += 1; + *col_nr = 0; + + for _ in 1..node_newlines { + EdModel::insert_empty_line(*line_nr, code_lines, grid_node_map)?; + + *line_nr += 1; + *col_nr = 0; + } + } + + Ok(()) + } + + // break(split) line at col_nr and move everything after col_nr to the next line + pub fn break_line( + line_nr: usize, + col_nr: usize, + code_lines: &mut CodeLines, + grid_node_map: &mut GridNodeMap, + ) -> UIResult<()> { + code_lines.break_line(line_nr, col_nr)?; + grid_node_map.break_line(line_nr, col_nr) + } + + pub fn insert_empty_line( + line_nr: usize, + code_lines: &mut CodeLines, + grid_node_map: &mut GridNodeMap, + ) -> UIResult<()> { + code_lines.insert_empty_line(line_nr)?; + grid_node_map.insert_empty_line(line_nr) + } + + pub fn push_empty_line(code_lines: &mut CodeLines, grid_node_map: &mut GridNodeMap) { + code_lines.push_empty_line(); + grid_node_map.push_empty_line(); + } + + pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> { + self.grid_node_map.clear_line(line_nr)?; + self.code_lines.clear_line(line_nr) + } + + pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> { + self.grid_node_map.del_line(line_nr); + self.code_lines.del_line(line_nr) } - // updates grid_node_map and code_lines but nothing else. pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { self.grid_node_map.del_at_line(line_nr, index)?; self.code_lines.del_at_line(line_nr, index) } + // updates grid_node_map and code_lines but nothing else. + pub fn del_range_at_line( + &mut self, + line_nr: usize, + col_range: std::ops::Range, + ) -> UIResult<()> { + self.grid_node_map + .del_range_at_line(line_nr, col_range.clone())?; + self.code_lines.del_range_at_line(line_nr, col_range) + } + + pub fn del_blank_expr_node(&mut self, txt_pos: TextPos) -> UIResult<()> { + self.del_at_line(txt_pos.line, txt_pos.column) + } + pub fn set_selected_expr( &mut self, expr_start_pos: TextPos, expr_end_pos: TextPos, - ast_node_id: ExprId, + ast_node_id: ASTNodeId, mark_node_id: MarkNodeId, ) -> EdResult<()> { self.set_raw_sel(RawSelection { @@ -185,9 +364,19 @@ impl<'a> EdModel<'a> { self.set_caret(expr_start_pos); - let type_str = self.expr2_to_type(ast_node_id); + let type_str = match ast_node_id { + ASTNodeId::ADefId(def_id) => { + if let Some(expr_id) = self.extract_expr_from_def(def_id) { + self.expr2_to_type(expr_id) + } else { + PoolStr::new(" * ", self.module.env.pool) + } + } - self.selected_expr_opt = Some(SelectedExpression { + ASTNodeId::AExprId(expr_id) => self.expr2_to_type(expr_id), + }; + + self.selected_block_opt = Some(SelectedBlock { ast_node_id, mark_node_id, type_str, @@ -201,11 +390,11 @@ impl<'a> EdModel<'a> { // select all MarkupNodes that refer to specific ast node and its children. pub fn select_expr(&mut self) -> EdResult<()> { // include parent in selection if an `Expr2` was already selected - if let Some(selected_expr) = &self.selected_expr_opt { - let expr2_level_mark_node = self.markup_node_pool.get(selected_expr.mark_node_id); + if let Some(selected_block) = &self.selected_block_opt { + let expr2_level_mark_node = self.mark_node_pool.get(selected_block.mark_node_id); if let Some(parent_id) = expr2_level_mark_node.get_parent_id_opt() { - let parent_mark_node = self.markup_node_pool.get(parent_id); + let parent_mark_node = self.mark_node_pool.get(parent_id); let ast_node_id = parent_mark_node.get_ast_node_id(); let (expr_start_pos, expr_end_pos) = self @@ -220,7 +409,7 @@ impl<'a> EdModel<'a> { if self.grid_node_map.node_exists_at_pos(caret_pos) { let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self .grid_node_map - .get_expr_start_end_pos(self.get_caret(), self)?; + .get_block_start_end_pos(self.get_caret(), self)?; self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; } else if self @@ -229,7 +418,7 @@ impl<'a> EdModel<'a> { { let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self .grid_node_map - .get_expr_start_end_pos(self.get_caret().decrement_col(), self)?; + .get_block_start_end_pos(self.get_caret().decrement_col(), self)?; self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; } @@ -238,6 +427,18 @@ impl<'a> EdModel<'a> { Ok(()) } + fn extract_expr_from_def(&self, def_id: DefId) -> Option { + let def = self.module.env.pool.get(def_id); + + match def { + Def2::ValueDef { + identifier_id: _, + expr_id, + } => Some(*expr_id), + Def2::Blank => None, + } + } + fn expr2_to_type(&mut self, expr2_id: ExprId) -> PoolStr { let var = self.module.env.var_store.fresh(); let expr = self.module.env.pool.get(expr2_id); @@ -274,7 +475,12 @@ impl<'a> EdModel<'a> { let content = subs.get_content_without_compacting(var); PoolStr::new( - &content_to_string(content, subs, self.module.env.home, self.interns), + &content_to_string( + content, + subs, + self.module.env.home, + &self.loaded_module.interns, + ), self.module.env.pool, ) } @@ -315,74 +521,121 @@ impl<'a> EdModel<'a> { virtual_keycode: VirtualKeyCode, ) -> EdResult<()> { match virtual_keycode { - Left => from_ui_res(self.move_caret_left(modifiers)), + Left => self.move_caret_left(modifiers)?, Up => { if modifiers.cmd_or_ctrl() && modifiers.shift { - self.select_expr() + self.select_expr()? } else { - from_ui_res(self.move_caret_up(modifiers)) + self.move_caret_up(modifiers)? } } - Right => from_ui_res(self.move_caret_right(modifiers)), - Down => from_ui_res(self.move_caret_down(modifiers)), + Right => self.move_caret_right(modifiers)?, + Down => self.move_caret_down(modifiers)?, A => { if modifiers.cmd_or_ctrl() { - from_ui_res(self.select_all()) - } else { - Ok(()) + self.select_all()? } } - Home => from_ui_res(self.move_caret_home(modifiers)), - End => from_ui_res(self.move_caret_end(modifiers)), + S => { + if modifiers.cmd_or_ctrl() { + self.save_file()? + } + } + R => { + if modifiers.cmd_or_ctrl() { + self.run_file()? + } + } + + Home => self.move_caret_home(modifiers)?, + End => self.move_caret_end(modifiers)?, + F11 => { self.show_debug_view = !self.show_debug_view; self.dirty = true; - Ok(()) } - _ => Ok(()), + _ => (), } + + Ok(()) } - fn replace_selected_expr_with_blank(&mut self) -> EdResult<()> { - let expr_mark_node_id_opt = if let Some(sel_expr) = &self.selected_expr_opt { - let expr2_level_mark_node = self.markup_node_pool.get(sel_expr.mark_node_id); + // Replaces selected expression with blank. + // If no expression is selected, this function will select one to guide the user to using backspace in a projectional editing way + fn backspace(&mut self) -> EdResult<()> { + if let Some(sel_block) = &self.selected_block_opt { + let expr2_level_mark_node = self.mark_node_pool.get(sel_block.mark_node_id); + let newlines_at_end = expr2_level_mark_node.get_newlines_at_end(); let blank_replacement = MarkupNode::Blank { - ast_node_id: sel_expr.ast_node_id, + ast_node_id: sel_block.ast_node_id, attributes: Attributes::new(), syn_high_style: HighlightStyle::Blank, parent_id_opt: expr2_level_mark_node.get_parent_id_opt(), + newlines_at_end, }; - self.markup_node_pool - .replace_node(sel_expr.mark_node_id, blank_replacement); + self.mark_node_pool + .replace_node(sel_block.mark_node_id, blank_replacement); let active_selection = self.get_selection().context(MissingSelection {})?; self.code_lines.del_selection(active_selection)?; self.grid_node_map.del_selection(active_selection)?; - self.module.env.pool.set(sel_expr.ast_node_id, Expr2::Blank); + match sel_block.ast_node_id { + ASTNodeId::ADefId(def_id) => { + self.module.env.pool.set(def_id, Def2::Blank); + } + ASTNodeId::AExprId(expr_id) => { + self.module.env.pool.set(expr_id, Expr2::Blank); + } + } - Some(sel_expr.mark_node_id) - } else { - None - }; + let expr_mark_node_id = sel_block.mark_node_id; - // have to split the previous `if` up to prevent borrowing issues - if let Some(expr_mark_node_id) = expr_mark_node_id_opt { let caret_pos = self.get_caret(); - self.insert_between_line( + EdModel::insert_between_line( caret_pos.line, caret_pos.column, nodes::BLANK_PLACEHOLDER, expr_mark_node_id, + &mut self.grid_node_map, + &mut self.code_lines, )?; - } - self.set_sel_none(); + self.set_sel_none(); + } else { + self.select_expr()?; + }; + + Ok(()) + } + + fn save_file(&mut self) -> UIResult<()> { + let all_lines_str = self.code_lines.all_lines_as_string(); + + write_to_file(self.file_path, &all_lines_str)?; + + println!("save successful!"); + + Ok(()) + } + + fn run_file(&mut self) -> UIResult<()> { + println!("Executing file..."); + + let roc_file_str = path_to_string(self.file_path); + + Command::new("cargo") + .arg("run") + .arg(roc_file_str) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .expect("Failed to run file"); Ok(()) } @@ -458,7 +711,7 @@ impl<'a> SelectableLines for EdModel<'a> { let end_col = selection.end_pos.column; if start_line_index == end_line_index { - let line_ref = self.code_lines.get_line(start_line_index)?; + let line_ref = self.code_lines.get_line_ref(start_line_index)?; Ok(Some(line_ref[start_col..end_col].to_string())) } else { @@ -480,7 +733,7 @@ impl<'a> SelectableLines for EdModel<'a> { fn set_sel_none(&mut self) { self.caret_w_select_vec.first_mut().0.selection_opt = None; - self.selected_expr_opt = None; + self.selected_block_opt = None; } fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) { @@ -505,7 +758,7 @@ impl<'a> SelectableLines for EdModel<'a> { fn last_text_pos(&self) -> UIResult { let nr_of_lines = self.code_lines.lines.len(); let last_line_index = nr_of_lines - 1; - let last_line = self.code_lines.get_line(last_line_index)?; + let last_line = self.code_lines.get_line_ref(last_line_index)?; Ok(TextPos { line: self.code_lines.lines.len() - 1, @@ -527,7 +780,7 @@ pub struct NodeContext<'a> { pub curr_mark_node_id: MarkNodeId, pub curr_mark_node: &'a MarkupNode, pub parent_id_opt: Option, - pub ast_node_id: ExprId, + pub ast_node_id: ASTNodeId, } pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> { @@ -535,7 +788,7 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> let curr_mark_node_id = ed_model .grid_node_map .get_id_at_row_col(ed_model.get_caret())?; - let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id); + let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); let parent_id_opt = curr_mark_node.get_parent_id_opt(); let ast_node_id = curr_mark_node.get_ast_node_id(); @@ -548,24 +801,345 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> }) } +// current(=caret is here) MarkupNode corresponds to a Def2 in the AST +pub fn handle_new_char_def( + received_char: &char, + def_id: DefId, + ed_model: &mut EdModel, +) -> EdResult { + let def_ref = ed_model.module.env.pool.get(def_id); + let ch = received_char; + + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node, + parent_id_opt: _, + ast_node_id: _, + } = get_node_context(ed_model)?; + + let outcome = match def_ref { + Def2::Blank { .. } => match ch { + 'a'..='z' => start_new_tld_value(ed_model, ch)?, + _ => InputOutcome::Ignored, + }, + Def2::ValueDef { .. } => { + if curr_mark_node.get_content() == EQUALS { + let node_caret_offset = ed_model + .grid_node_map + .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; + + if node_caret_offset == 0 || node_caret_offset == EQUALS.len() { + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + + if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + update_tld_val_name( + prev_mark_node_id, + ed_model.get_caret(), // TODO update for multiple carets + ed_model, + ch, + )? + } else { + unreachable!() + } + } else { + InputOutcome::Ignored + } + } else { + update_tld_val_name( + curr_mark_node_id, + ed_model.get_caret(), // TODO update for multiple carets + ed_model, + ch, + )? + } + } + }; + + Ok(outcome) +} + +// current(=caret is here) MarkupNode corresponds to an Expr2 in the AST +pub fn handle_new_char_expr( + received_char: &char, + expr_id: ExprId, + ed_model: &mut EdModel, +) -> EdResult { + let expr_ref = ed_model.module.env.pool.get(expr_id); + let ch = received_char; + + let NodeContext { + old_caret_pos: _, + curr_mark_node_id, + curr_mark_node, + parent_id_opt: _, + ast_node_id: _, + } = get_node_context(ed_model)?; + + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + + let outcome = if let Expr2::Blank { .. } = expr_ref { + match ch { + 'a'..='z' => start_new_let_value(ed_model, ch)?, + '"' => start_new_string(ed_model)?, + '{' => start_new_record(ed_model)?, + '0'..='9' => start_new_int(ed_model, ch)?, + '[' => { + // this can also be a tag union or become a set, assuming list for now + start_new_list(ed_model)? + } + '\r' => { + println!("For convenience and consistency there is only one way to format Roc, you can't add extra blank lines."); + InputOutcome::Ignored + } + _ => InputOutcome::Ignored, + } + } else if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + if prev_mark_node_id == curr_mark_node_id { + match expr_ref { + Expr2::SmallInt { .. } => update_int(ed_model, curr_mark_node_id, ch)?, + Expr2::SmallStr(old_arr_str) => update_small_string(ch, old_arr_str, ed_model)?, + Expr2::Str(..) => update_string(*ch, ed_model)?, + Expr2::InvalidLookup(old_pool_str) => update_invalid_lookup( + &ch.to_string(), + old_pool_str, + curr_mark_node_id, + expr_id, + ed_model, + )?, + Expr2::EmptyRecord => { + // prev_mark_node_id and curr_mark_node_id should be different to allow creating field at current caret position + InputOutcome::Ignored + } + Expr2::Record { + record_var: _, + fields, + } => { + if curr_mark_node + .get_content() + .chars() + .all(|chr| chr.is_ascii_alphanumeric()) + { + update_record_field( + &ch.to_string(), + ed_model.get_caret(), + curr_mark_node_id, + fields, + ed_model, + )? + } else { + InputOutcome::Ignored + } + } + _ => InputOutcome::Ignored, + } + } else if ch.is_ascii_alphanumeric() { + // prev_mark_node_id != curr_mark_node_id + + match expr_ref { + Expr2::SmallInt { .. } => update_int(ed_model, curr_mark_node_id, ch)?, + _ => { + let prev_ast_node_id = ed_model + .mark_node_pool + .get(prev_mark_node_id) + .get_ast_node_id(); + + match prev_ast_node_id { + ASTNodeId::ADefId(_) => InputOutcome::Ignored, + ASTNodeId::AExprId(prev_expr_id) => { + handle_new_char_diff_mark_nodes_prev_is_expr( + ch, + prev_expr_id, + expr_id, + prev_mark_node_id, + curr_mark_node_id, + ed_model, + )? + } + } + } + } + } else if *ch == ':' { + let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); + + if let Some(mark_parent_id) = mark_parent_id_opt { + let parent_ast_id = ed_model + .mark_node_pool + .get(mark_parent_id) + .get_ast_node_id(); + + match parent_ast_id { + ASTNodeId::ADefId(_) => InputOutcome::Ignored, + ASTNodeId::AExprId(parent_expr_id) => { + update_record_colon(ed_model, parent_expr_id)? + } + } + } else { + InputOutcome::Ignored + } + } else if *ch == ',' { + if curr_mark_node.get_content() == nodes::LEFT_SQUARE_BR { + InputOutcome::Ignored + } else { + let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); + + if let Some(mark_parent_id) = mark_parent_id_opt { + let parent_ast_id = ed_model + .mark_node_pool + .get(mark_parent_id) + .get_ast_node_id(); + + match parent_ast_id { + ASTNodeId::ADefId(_) => InputOutcome::Ignored, + ASTNodeId::AExprId(parent_expr_id) => { + let parent_expr2 = ed_model.module.env.pool.get(parent_expr_id); + + match parent_expr2 { + Expr2::List { + elem_var: _, + elems: _, + } => { + let (new_child_index, new_ast_child_index) = + ed_model.get_curr_child_indices()?; + // insert a Blank first, this results in cleaner code + add_blank_child(new_child_index, new_ast_child_index, ed_model)? + } + Expr2::Record { + record_var: _, + fields: _, + } => { + todo!("multiple record fields") + } + _ => InputOutcome::Ignored, + } + } + } + } else { + InputOutcome::Ignored + } + } + } else if "\"{[".contains(*ch) { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); + + if prev_mark_node.get_content() == nodes::LEFT_SQUARE_BR + && curr_mark_node.get_content() == nodes::RIGHT_SQUARE_BR + { + let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?; + // insert a Blank first, this results in cleaner code + add_blank_child(new_child_index, new_ast_child_index, ed_model)?; + handle_new_char(received_char, ed_model)? + } else { + InputOutcome::Ignored + } + } else { + InputOutcome::Ignored + } + } else { + InputOutcome::Ignored + }; + + Ok(outcome) +} + +// handle new char when prev_mark_node != curr_mark_node and prev_mark_node's AST node is an Expr2 +pub fn handle_new_char_diff_mark_nodes_prev_is_expr( + received_char: &char, + prev_expr_id: ExprId, + curr_expr_id: ExprId, + prev_mark_node_id: MarkNodeId, + curr_mark_node_id: MarkNodeId, + ed_model: &mut EdModel, +) -> EdResult { + let prev_expr_ref = ed_model.module.env.pool.get(prev_expr_id); + let curr_expr_ref = ed_model.module.env.pool.get(curr_expr_id); + let ch = received_char; + let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); + + let outcome = match prev_expr_ref { + Expr2::SmallInt { .. } => update_int(ed_model, prev_mark_node_id, ch)?, + Expr2::InvalidLookup(old_pool_str) => update_invalid_lookup( + &ch.to_string(), + old_pool_str, + prev_mark_node_id, + prev_expr_id, + ed_model, + )?, + Expr2::Record { + record_var: _, + fields, + } => { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); + + if (curr_mark_node.get_content() == nodes::RIGHT_ACCOLADE + || curr_mark_node.get_content() == nodes::COLON) + && prev_mark_node.is_all_alphanumeric() + { + update_record_field( + &ch.to_string(), + ed_model.get_caret(), + prev_mark_node_id, + fields, + ed_model, + )? + } else if prev_mark_node.get_content() == nodes::LEFT_ACCOLADE + && curr_mark_node.is_all_alphanumeric() + { + update_record_field( + &ch.to_string(), + ed_model.get_caret(), + curr_mark_node_id, + fields, + ed_model, + )? + } else { + InputOutcome::Ignored + } + } + Expr2::List { + elem_var: _, + elems: _, + } => { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); + + if prev_mark_node.get_content() == nodes::LEFT_SQUARE_BR + && curr_mark_node.get_content() == nodes::RIGHT_SQUARE_BR + { + // based on if, we are at the start of the list + let new_child_index = 1; + let new_ast_child_index = 0; + // insert a Blank first, this results in cleaner code + add_blank_child(new_child_index, new_ast_child_index, ed_model)?; + handle_new_char(received_char, ed_model)? + } else { + InputOutcome::Ignored + } + } + _ => match curr_expr_ref { + Expr2::EmptyRecord => { + let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.mark_node_pool); + + update_empty_record(&ch.to_string(), prev_mark_node_id, sibling_ids, ed_model)? + } + _ => InputOutcome::Ignored, + }, + }; + + Ok(outcome) +} + pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult { let input_outcome = match received_char { - '\u{1}' // Ctrl + A - | '\u{3}' // Ctrl + C - | '\u{16}' // Ctrl + V - | '\u{18}' // Ctrl + X - | '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html + '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html | '\u{f0000}'..='\u{ffffd}' // ^ | '\u{100000}'..='\u{10fffd}' // ^ => { - // chars that can be ignored InputOutcome::Ignored } '\u{8}' | '\u{7f}' => { // On Linux, '\u{8}' is backspace, // on macOS '\u{7f}'. - ed_model.replace_selected_expr_with_blank()?; + ed_model.backspace()?; InputOutcome::Accepted } @@ -573,246 +1147,52 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult let outcome = if ed_model.node_exists_at_caret() { let curr_mark_node_id = ed_model.get_curr_mark_node_id()?; - let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id); - let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); + let ast_node_id = curr_mark_node.get_ast_node_id(); - let ast_node_id = curr_mark_node.get_ast_node_id(); - let ast_node_ref = ed_model.module.env.pool.get(ast_node_id); - - if let Expr2::Blank {..} = ast_node_ref { - match ch { - '"' => { - start_new_string(ed_model)? - }, - '{' => { - start_new_record(ed_model)? - } - '0'..='9' => { - start_new_int(ed_model, ch)? - } - '[' => { - // this can also be a tag union or become a set, assuming list for now - start_new_list(ed_model)? - } - _ => InputOutcome::Ignored - } - } else if let Some(prev_mark_node_id) = prev_mark_node_id_opt{ - if prev_mark_node_id == curr_mark_node_id { - match ast_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, curr_mark_node_id, ch)? - } - Expr2::SmallStr(old_arr_str) => { - update_small_string( - ch, old_arr_str, ed_model - )? - } - Expr2::Str(old_pool_str) => { - update_string( - &ch.to_string(), old_pool_str, ed_model - )? - } - Expr2::InvalidLookup(old_pool_str) => { - update_invalid_lookup( - &ch.to_string(), - old_pool_str, - curr_mark_node_id, - ast_node_id, - ed_model - )? - } - Expr2::EmptyRecord => { - // prev_mark_node_id and curr_mark_node_id should be different to allow creating field at current caret position - InputOutcome::Ignored - } - Expr2::Record{ record_var:_, fields } => { - if curr_mark_node.get_content()?.chars().all(|chr| chr.is_ascii_alphanumeric()){ - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - curr_mark_node_id, - fields, - ed_model, - )? - } else { - InputOutcome::Ignored - } - } - _ => InputOutcome::Ignored - } - } else if ch.is_ascii_alphanumeric() { // prev_mark_node_id != curr_mark_node_id - - match ast_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, curr_mark_node_id, ch)? - } - _ => { - let prev_ast_node_id = - ed_model - .markup_node_pool - .get(prev_mark_node_id) - .get_ast_node_id(); - - let prev_node_ref = ed_model.module.env.pool.get(prev_ast_node_id); - - match prev_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, prev_mark_node_id, ch)? - } - Expr2::InvalidLookup(old_pool_str) => { - update_invalid_lookup( - &ch.to_string(), - old_pool_str, - prev_mark_node_id, - prev_ast_node_id, - ed_model - )? - } - Expr2::Record{ record_var:_, fields } => { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - - if (curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE || curr_mark_node.get_content()? == nodes::COLON) && - prev_mark_node.is_all_alphanumeric()? { - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - prev_mark_node_id, - fields, - ed_model, - )? - } else if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE && curr_mark_node.is_all_alphanumeric()? { - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - curr_mark_node_id, - fields, - ed_model, - )? - } else { - InputOutcome::Ignored - } - } - Expr2::List{ elem_var: _, elems: _} => { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - - if prev_mark_node.get_content()? == nodes::LEFT_SQUARE_BR && curr_mark_node.get_content()? == nodes::RIGHT_SQUARE_BR { - // based on if, we are at the start of the list - let new_child_index = 1; - let new_ast_child_index = 0; - // insert a Blank first, this results in cleaner code - add_blank_child(new_child_index, new_ast_child_index, ed_model)?; - handle_new_char(received_char, ed_model)? - } else { - InputOutcome::Ignored - } - } - _ => { - match ast_node_ref { - Expr2::EmptyRecord => { - let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); - - update_empty_record( - &ch.to_string(), - prev_mark_node_id, - sibling_ids, - ed_model - )? - } - _ => InputOutcome::Ignored - } - } - } - } - } - } else if *ch == ':' { - let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); - - if let Some(mark_parent_id) = mark_parent_id_opt { - let parent_ast_id = ed_model.markup_node_pool.get(mark_parent_id).get_ast_node_id(); - - update_record_colon(ed_model, parent_ast_id)? - } else { - InputOutcome::Ignored - } - } else if *ch == ',' { - if curr_mark_node.get_content()? == nodes::LEFT_SQUARE_BR { - InputOutcome::Ignored - } else { - let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); - - if let Some(mark_parent_id) = mark_parent_id_opt { - let parent_ast_id = ed_model.markup_node_pool.get(mark_parent_id).get_ast_node_id(); - let parent_expr2 = ed_model.module.env.pool.get(parent_ast_id); - - match parent_expr2 { - Expr2::List { elem_var:_, elems:_} => { - - let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?; - // insert a Blank first, this results in cleaner code - add_blank_child( - new_child_index, - new_ast_child_index, - ed_model - )? - } - Expr2::Record { record_var:_, fields:_ } => { - todo!("multiple record fields") - } - _ => { - InputOutcome::Ignored - } - } - } else { - InputOutcome::Ignored - } - } - } else if "\"{[".contains(*ch) { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - - if prev_mark_node.get_content()? == nodes::LEFT_SQUARE_BR && curr_mark_node.get_content()? == nodes::RIGHT_SQUARE_BR { - let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?; - // insert a Blank first, this results in cleaner code - add_blank_child( - new_child_index, - new_ast_child_index, - ed_model - )?; - handle_new_char(received_char, ed_model)? - } else { - InputOutcome::Ignored - } - - } else { - InputOutcome::Ignored - } - - } else { - match ast_node_ref { - Expr2::SmallInt{ .. } => { - update_int(ed_model, curr_mark_node_id, ch)? - }, - // only SmallInt currently allows prepending at the start - _ => InputOutcome::Ignored - } + match ast_node_id { + ASTNodeId::ADefId(def_id) => { + handle_new_char_def(received_char, def_id, ed_model)? + }, + ASTNodeId::AExprId(expr_id) => { + handle_new_char_expr(received_char, expr_id, ed_model)? } + } } else { //no MarkupNode at the current position - let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; - if let Some(prev_mark_node_id) = prev_mark_node_id_opt { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + if *received_char == '\r' { + break_line(ed_model)? + } else { + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); - let prev_ast_node = ed_model.module.env.pool.get(prev_mark_node.get_ast_node_id()); + let prev_ast_node = ed_model.module.env.pool.get(prev_mark_node.get_ast_node_id().to_expr_id()?); - match prev_ast_node { - Expr2::SmallInt{ .. } => { - update_int(ed_model, prev_mark_node_id, ch)? - }, - _ => { - InputOutcome::Ignored + match prev_ast_node { + Expr2::SmallInt{ .. } => { + update_int(ed_model, prev_mark_node_id, ch)? + }, + _ => { + InputOutcome::Ignored + } + } + } else { + match ch { + 'a'..='z' => { + for caret_pos in ed_model.get_carets() { + + if caret_pos.line > 0 { + insert_new_blank(ed_model, &caret_pos, caret_pos.line)?; + } + } + handle_new_char(received_char, ed_model)? + } + _ => { + InputOutcome::Ignored + } } } - } else { - InputOutcome::Ignored } }; @@ -840,13 +1220,14 @@ pub mod test_ed_update { use crate::editor::mvc::ed_update::handle_new_char; use crate::editor::mvc::ed_update::EdModel; use crate::editor::mvc::ed_update::EdResult; + use crate::editor::resources::strings::HELLO_WORLD; use crate::ui::text::lines::SelectableLines; use crate::ui::ui_error::UIResult; use crate::window::keyboard_input::no_mods; use crate::window::keyboard_input::test_modifiers::ctrl_cmd_shift; use crate::window::keyboard_input::Modifiers; - use bumpalo::collections::String as BumpString; use bumpalo::Bump; + use roc_module::symbol::ModuleIds; use winit::event::VirtualKeyCode::*; fn ed_res_to_res(ed_res: EdResult) -> Result { @@ -866,155 +1247,310 @@ pub mod test_ed_update { // Create ed_model from pre_lines DSL, do handle_new_char() with new_char, check if modified ed_model has expected // string representation of code, caret position and active selection. pub fn assert_insert( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, new_char: char, ) -> Result<(), String> { assert_insert_seq(pre_lines, expected_post_lines, &new_char.to_string()) } - pub fn assert_insert_ignore(lines: &[&str], new_char: char) -> Result<(), String> { - assert_insert_seq(lines, lines, &new_char.to_string()) + pub fn assert_insert_nls( + pre_lines: Vec, + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert(pre_lines, add_nls(expected_post_lines), new_char) + } + + pub fn assert_insert_no_pre( + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert_seq_no_pre(expected_post_lines, &new_char.to_string()) + } + + pub fn assert_insert_seq_no_pre( + expected_post_lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + assert_insert_seq(vec!["┃".to_owned()], expected_post_lines, new_char_seq) + } + + // pre-insert `val = ` + pub fn assert_insert_in_def( + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert_seq_in_def(expected_post_lines, &new_char.to_string()) + } + + // pre-insert `val = ` + pub fn assert_insert_seq_in_def( + expected_post_lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + let prefix = "val🡲🡲🡲"; + + let full_input = merge_strings(vec![prefix, new_char_seq]); + + let mut expected_post_lines_vec = expected_post_lines.to_vec(); + + let first_line_opt = expected_post_lines_vec.first(); + let val_str = "val = "; + + if let Some(first_line) = first_line_opt { + expected_post_lines_vec[0] = merge_strings(vec![val_str, first_line]); + } else { + expected_post_lines_vec = vec![val_str.to_owned()]; + } + + assert_insert_seq_no_pre(expected_post_lines_vec, &full_input) + } + + pub fn assert_insert_in_def_nls( + expected_post_lines: Vec, + new_char: char, + ) -> Result<(), String> { + assert_insert_seq_in_def(add_nls(expected_post_lines), &new_char.to_string()) } // Create ed_model from pre_lines DSL, do handle_new_char() for every char in new_char_seq, check if modified ed_model has expected // string representation of code, caret position and active selection. pub fn assert_insert_seq( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, new_char_seq: &str, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("\n").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for input_char in new_char_seq.chars() { if input_char == '🡲' { ed_model.simple_move_carets_right(1); } else if input_char == '🡰' { ed_model.simple_move_carets_left(1); + } else if input_char == '🡱' { + ed_model.simple_move_carets_up(1); } else { + //dbg!(input_char); ed_res_to_res(handle_new_char(&input_char, &mut ed_model))?; } } - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); // remove header for clean tests assert_eq!(post_lines, expected_post_lines); Ok(()) } - pub fn assert_insert_seq_ignore(lines: &[&str], new_char_seq: &str) -> Result<(), String> { - assert_insert_seq(lines, lines, new_char_seq) + fn strip_header(lines: &mut Vec) { + let nr_hello_world_lines = HELLO_WORLD.matches('\n').count() - 2; + lines.drain(0..nr_hello_world_lines); + } + + pub fn assert_insert_seq_nls( + pre_lines: Vec, + expected_post_lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + assert_insert_seq(pre_lines, add_nls(expected_post_lines), new_char_seq) + } + + pub fn assert_insert_seq_ignore(lines: Vec, new_char_seq: &str) -> Result<(), String> { + assert_insert_seq(lines.clone(), lines, new_char_seq) + } + + pub fn assert_insert_seq_ignore_nls( + lines: Vec, + new_char_seq: &str, + ) -> Result<(), String> { + assert_insert_seq_ignore(add_nls(lines), new_char_seq) + } + + pub fn assert_insert_ignore(lines: Vec, new_char: char) -> Result<(), String> { + assert_insert_seq_ignore(lines, &new_char.to_string()) + } + + pub fn assert_insert_ignore_nls(lines: Vec, new_char: char) -> Result<(), String> { + assert_insert_seq_ignore(add_nls(lines), &new_char.to_string()) + } + + // to create Vec from list of &str + macro_rules! ovec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = Vec::new(); + $( + temp_vec.push($x.to_owned()); + )* + temp_vec + } + }; } #[test] fn test_ignore_basic() -> Result<(), String> { - // space is added because Blank is inserted - assert_insert(&["┃"], &["┃ "], 'a')?; - assert_insert(&["┃"], &["┃ "], ';')?; - assert_insert(&["┃"], &["┃ "], '-')?; - assert_insert(&["┃"], &["┃ "], '_')?; + assert_insert_no_pre(ovec!["┃"], ';')?; + assert_insert_no_pre(ovec!["┃"], '-')?; + assert_insert_no_pre(ovec!["┃"], '_')?; + // extra space because of Expr2::Blank placholder + assert_insert_in_def_nls(ovec!["┃ "], ';')?; + assert_insert_in_def_nls(ovec!["┃ "], '-')?; + assert_insert_in_def_nls(ovec!["┃ "], '_')?; Ok(()) } - #[test] - fn test_int() -> Result<(), String> { - assert_insert(&["┃"], &["0┃"], '0')?; - assert_insert(&["┃"], &["1┃"], '1')?; - assert_insert(&["┃"], &["2┃"], '2')?; - assert_insert(&["┃"], &["3┃"], '3')?; - assert_insert(&["┃"], &["4┃"], '4')?; - assert_insert(&["┃"], &["5┃"], '5')?; - assert_insert(&["┃"], &["6┃"], '6')?; - assert_insert(&["┃"], &["7┃"], '7')?; - assert_insert(&["┃"], &["8┃"], '8')?; - assert_insert(&["┃"], &["9┃"], '9')?; + // add newlines like the editor's formatting would add them + fn add_nls(lines: Vec) -> Vec { + let mut new_lines = lines.clone(); - assert_insert(&["1┃"], &["19┃"], '9')?; - assert_insert(&["9876┃"], &["98769┃"], '9')?; - assert_insert(&["10┃"], &["103┃"], '3')?; - assert_insert(&["┃0"], &["1┃0"], '1')?; - assert_insert(&["10000┃"], &["100000┃"], '0')?; + new_lines.append(&mut vec!["".to_owned(), "".to_owned()]); - assert_insert(&["┃1234"], &["5┃1234"], '5')?; - assert_insert(&["1┃234"], &["10┃234"], '0')?; - assert_insert(&["12┃34"], &["121┃34"], '1')?; - assert_insert(&["123┃4"], &["1232┃4"], '2')?; - - Ok(()) - } - - #[test] - fn test_ignore_int() -> Result<(), String> { - assert_insert_seq_ignore(&["┃0"], "{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["┃7"], "{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["0┃"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["8┃"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["20┃"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["83┃"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["1┃0"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["8┃4"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["┃10"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["┃84"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_seq_ignore(&["129┃96"], ",{}()[]-><-_\"azAZ:@")?; - assert_insert_seq_ignore(&["97┃684"], ",{}()[]-><-_\"azAZ:@")?; - - assert_insert_ignore(&["0┃"], '0')?; - assert_insert_ignore(&["0┃"], '9')?; - assert_insert_ignore(&["┃0"], '0')?; - assert_insert_ignore(&["┃1234"], '0')?; - assert_insert_ignore(&["┃100"], '0')?; - - Ok(()) + new_lines } //TODO test_int arch bit limit + #[test] + fn test_int() -> Result<(), String> { + assert_insert_in_def_nls(ovec!["0┃"], '0')?; + assert_insert_in_def_nls(ovec!["1┃"], '1')?; + assert_insert_in_def_nls(ovec!["2┃"], '2')?; + assert_insert_in_def_nls(ovec!["3┃"], '3')?; + assert_insert_in_def_nls(ovec!["4┃"], '4')?; + assert_insert_in_def_nls(ovec!["5┃"], '5')?; + assert_insert_in_def_nls(ovec!["6┃"], '6')?; + assert_insert_in_def_nls(ovec!["7┃"], '7')?; + assert_insert_in_def_nls(ovec!["8┃"], '8')?; + assert_insert_in_def_nls(ovec!["9┃"], '9')?; + + assert_insert(ovec!["val = 1┃"], add_nls(ovec!["val = 19┃"]), '9')?; + assert_insert(ovec!["val = 9876┃"], add_nls(ovec!["val = 98769┃"]), '9')?; + assert_insert(ovec!["val = 10┃"], add_nls(ovec!["val = 103┃"]), '3')?; + assert_insert(ovec!["val = ┃0"], add_nls(ovec!["val = 1┃0"]), '1')?; + assert_insert(ovec!["val = 10000┃"], add_nls(ovec!["val = 100000┃"]), '0')?; + + assert_insert(ovec!["val = ┃1234"], add_nls(ovec!["val = 5┃1234"]), '5')?; + assert_insert(ovec!["val = 1┃234"], add_nls(ovec!["val = 10┃234"]), '0')?; + assert_insert(ovec!["val = 12┃34"], add_nls(ovec!["val = 121┃34"]), '1')?; + assert_insert(ovec!["val = 123┃4"], add_nls(ovec!["val = 1232┃4"]), '2')?; + + Ok(()) + } + + fn merge_strings(strings: Vec<&str>) -> String { + strings + .iter() + .map(|&some_str| some_str.to_owned()) + .collect::>() + .join("") + } + + const IGNORE_CHARS: &str = "{}()[]-><-_\"azAZ:@09"; + const IGNORE_CHARS_NO_NUM: &str = ",{}()[]-><-_\"azAZ:@"; + const IGNORE_NO_LTR: &str = "{\"5"; + const IGNORE_NO_NUM: &str = "a{\""; + + #[test] + fn test_ignore_int() -> Result<(), String> { + assert_insert_seq_ignore_nls(ovec!["vec = ┃0"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = ┃7"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = 0┃"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 8┃"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 20┃"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 83┃"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = 1┃0"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 8┃4"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = ┃10"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = ┃84"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_seq_ignore_nls(ovec!["vec = 129┃96"], IGNORE_CHARS_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["vec = 97┃684"], IGNORE_CHARS_NO_NUM)?; + + assert_insert_ignore_nls(ovec!["vec = 0┃"], '0')?; + assert_insert_ignore_nls(ovec!["vec = 0┃"], '9')?; + assert_insert_ignore_nls(ovec!["vec = ┃0"], '0')?; + assert_insert_ignore_nls(ovec!["vec = ┃1234"], '0')?; + assert_insert_ignore_nls(ovec!["vec = ┃100"], '0')?; + + Ok(()) + } #[test] fn test_string() -> Result<(), String> { - assert_insert(&["┃"], &["\"┃\""], '"')?; - assert_insert(&["\"┃\""], &["\"a┃\""], 'a')?; - assert_insert(&["\"┃\""], &["\"{┃\""], '{')?; - assert_insert(&["\"┃\""], &["\"}┃\""], '}')?; - assert_insert(&["\"┃\""], &["\"[┃\""], '[')?; - assert_insert(&["\"┃\""], &["\"]┃\""], ']')?; - assert_insert(&["\"┃\""], &["\"-┃\""], '-')?; - assert_insert(&["\"┃-\""], &["\"<┃-\""], '<')?; - assert_insert(&["\"-┃\""], &["\"->┃\""], '>')?; + assert_insert_in_def_nls(ovec!["\"┃\""], '"')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"a┃\""]), 'a')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"{┃\""]), '{')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"}┃\""]), '}')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"[┃\""]), '[')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"]┃\""]), ']')?; + assert_insert(ovec!["val = \"┃\""], add_nls(ovec!["val = \"-┃\""]), '-')?; + assert_insert(ovec!["val = \"┃-\""], add_nls(ovec!["val = \"<┃-\""]), '<')?; + assert_insert(ovec!["val = \"-┃\""], add_nls(ovec!["val = \"->┃\""]), '>')?; - assert_insert(&["\"a┃\""], &["\"ab┃\""], 'b')?; - assert_insert(&["\"ab┃\""], &["\"abc┃\""], 'c')?; - assert_insert(&["\"┃a\""], &["\"z┃a\""], 'z')?; - assert_insert(&["\"┃a\""], &["\" ┃a\""], ' ')?; - assert_insert(&["\"a┃b\""], &["\"az┃b\""], 'z')?; - assert_insert(&["\"a┃b\""], &["\"a ┃b\""], ' ')?; - - assert_insert(&["\"ab ┃\""], &["\"ab {┃\""], '{')?; - assert_insert(&["\"ab ┃\""], &["\"ab }┃\""], '}')?; - assert_insert(&["\"{ str: 4┃}\""], &["\"{ str: 44┃}\""], '4')?; + assert_insert(ovec!["val = \"a┃\""], add_nls(ovec!["val = \"ab┃\""]), 'b')?; assert_insert( - &["\"┃ello, hello, hello\""], - &["\"h┃ello, hello, hello\""], + ovec!["val = \"ab┃\""], + add_nls(ovec!["val = \"abc┃\""]), + 'c', + )?; + assert_insert(ovec!["val = \"┃a\""], add_nls(ovec!["val = \"z┃a\""]), 'z')?; + assert_insert(ovec!["val = \"┃a\""], add_nls(ovec!["val = \" ┃a\""]), ' ')?; + assert_insert( + ovec!["val = \"a┃b\""], + add_nls(ovec!["val = \"az┃b\""]), + 'z', + )?; + assert_insert( + ovec!["val = \"a┃b\""], + add_nls(ovec!["val = \"a ┃b\""]), + ' ', + )?; + + assert_insert( + ovec!["val = \"ab ┃\""], + add_nls(ovec!["val = \"ab {┃\""]), + '{', + )?; + assert_insert( + ovec!["val = \"ab ┃\""], + add_nls(ovec!["val = \"ab }┃\""]), + '}', + )?; + assert_insert( + ovec!["val = \"{ str: 4┃}\""], + add_nls(ovec!["val = \"{ str: 44┃}\""]), + '4', + )?; + assert_insert( + ovec!["val = \"┃ello, hello, hello\""], + add_nls(ovec!["val = \"h┃ello, hello, hello\""]), 'h', )?; assert_insert( - &["\"hello┃ hello, hello\""], - &["\"hello,┃ hello, hello\""], + ovec!["val = \"hello┃ hello, hello\""], + add_nls(ovec!["val = \"hello,┃ hello, hello\""]), ',', )?; assert_insert( - &["\"hello, hello, hello┃\""], - &["\"hello, hello, hello.┃\""], + ovec!["val = \"hello, hello, hello┃\""], + add_nls(ovec!["val = \"hello, hello, hello.┃\""]), '.', )?; @@ -1023,549 +1559,797 @@ pub mod test_ed_update { #[test] fn test_ignore_string() -> Result<(), String> { - assert_insert(&["┃\"\""], &["┃\"\""], 'a')?; - assert_insert(&["┃\"\""], &["┃\"\""], 'A')?; - assert_insert(&["┃\"\""], &["┃\"\""], '"')?; - assert_insert(&["┃\"\""], &["┃\"\""], '{')?; - assert_insert(&["┃\"\""], &["┃\"\""], '[')?; - assert_insert(&["┃\"\""], &["┃\"\""], '}')?; - assert_insert(&["┃\"\""], &["┃\"\""], ']')?; - assert_insert(&["┃\"\""], &["┃\"\""], '-')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"\""]), '-')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], 'a')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], 'A')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '"')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '{')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '[')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '}')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], ']')?; - assert_insert(&["\"\"┃"], &["\"\"┃"], '-')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = \"\"┃"]), '-')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], 'a')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], 'A')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '"')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '{')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '[')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '}')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], ']')?; - assert_insert(&["┃\"a\""], &["┃\"a\""], '-')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"a\""]), '-')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], 'a')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], 'A')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '"')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '{')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '[')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '}')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], ']')?; - assert_insert(&["\"a\"┃"], &["\"a\"┃"], '-')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = \"a\"┃"]), '-')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], 'a')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], 'A')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '"')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '{')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '[')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '}')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], ']')?; - assert_insert(&["┃\"{ }\""], &["┃\"{ }\""], '-')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"{ }\""]), '-')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], 'a')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], 'A')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '"')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '{')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '[')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '}')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], ']')?; - assert_insert(&["\"{ }\"┃"], &["\"{ }\"┃"], '-')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), 'A')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), 'a')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '"')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '[')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '}')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), ']')?; + assert_insert_ignore(add_nls(ovec!["val = \"{ }\"┃"]), '-')?; - assert_insert(&["\"[ 1, 2, 3 ]\"┃"], &["\"[ 1, 2, 3 ]\"┃"], '{')?; - assert_insert(&["┃\"[ 1, 2, 3 ]\""], &["┃\"[ 1, 2, 3 ]\""], '{')?; - assert_insert( - &["\"hello, hello, hello\"┃"], - &["\"hello, hello, hello\"┃"], - '.', - )?; - assert_insert( - &["┃\"hello, hello, hello\""], - &["┃\"hello, hello, hello\""], - '.', - )?; + assert_insert_ignore(add_nls(ovec!["val = \"[ 1, 2, 3 ]\"┃"]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"[ 1, 2, 3 ]\""]), '{')?; + assert_insert_ignore(add_nls(ovec!["val = \"hello, hello, hello\"┃"]), '.')?; + assert_insert_ignore(add_nls(ovec!["val = ┃\"hello, hello, hello\""]), '.')?; Ok(()) } #[test] fn test_record() -> Result<(), String> { - assert_insert(&["┃"], &["{ ┃ }"], '{')?; - assert_insert(&["{ ┃ }"], &["{ a┃ }"], 'a')?; - assert_insert(&["{ a┃ }"], &["{ ab┃: RunTimeError }"], 'b')?; - assert_insert(&["{ a┃ }"], &["{ a1┃: RunTimeError }"], '1')?; - assert_insert(&["{ a1┃ }"], &["{ a1z┃: RunTimeError }"], 'z')?; - assert_insert(&["{ a1┃ }"], &["{ a15┃: RunTimeError }"], '5')?; - assert_insert(&["{ ab┃ }"], &["{ abc┃: RunTimeError }"], 'c')?; - assert_insert(&["{ ┃abc }"], &["{ z┃abc: RunTimeError }"], 'z')?; - assert_insert(&["{ a┃b }"], &["{ az┃b: RunTimeError }"], 'z')?; - assert_insert(&["{ a┃b }"], &["{ a9┃b: RunTimeError }"], '9')?; - - // extra space for Blank node - assert_insert(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ':')?; - assert_insert(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ':')?; - assert_insert(&["{ aBc┃ }"], &["{ aBc┃: RunTimeError }"], ':')?; - - assert_insert_seq(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ":\"")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ":\"")?; - - assert_insert_seq(&["{ a┃ }"], &["{ a0┃: RunTimeError }"], ":0")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc9┃: RunTimeError }"], ":9")?; - assert_insert_seq(&["{ a┃ }"], &["{ a1000┃: RunTimeError }"], ":1000")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc98761┃: RunTimeError }"], ":98761")?; - - assert_insert(&["{ a: \"┃\" }"], &["{ a: \"a┃\" }"], 'a')?; - assert_insert(&["{ a: \"a┃\" }"], &["{ a: \"ab┃\" }"], 'b')?; - assert_insert(&["{ a: \"a┃b\" }"], &["{ a: \"az┃b\" }"], 'z')?; - assert_insert(&["{ a: \"┃ab\" }"], &["{ a: \"z┃ab\" }"], 'z')?; - - assert_insert(&["{ a: 1┃ }"], &["{ a: 10┃ }"], '0')?; - assert_insert(&["{ a: 100┃ }"], &["{ a: 1004┃ }"], '4')?; - assert_insert(&["{ a: 9┃76 }"], &["{ a: 98┃76 }"], '8')?; - assert_insert(&["{ a: 4┃691 }"], &["{ a: 40┃691 }"], '0')?; - assert_insert(&["{ a: 469┃1 }"], &["{ a: 4699┃1 }"], '9')?; - - assert_insert(&["{ camelCase: \"┃\" }"], &["{ camelCase: \"a┃\" }"], 'a')?; - assert_insert(&["{ camelCase: \"a┃\" }"], &["{ camelCase: \"ab┃\" }"], 'b')?; - - assert_insert(&["{ camelCase: 3┃ }"], &["{ camelCase: 35┃ }"], '5')?; - assert_insert(&["{ camelCase: ┃2 }"], &["{ camelCase: 5┃2 }"], '5')?; - assert_insert(&["{ camelCase: 10┃2 }"], &["{ camelCase: 106┃2 }"], '6')?; - - assert_insert(&["{ a┃: \"\" }"], &["{ ab┃: \"\" }"], 'b')?; - assert_insert(&["{ ┃a: \"\" }"], &["{ z┃a: \"\" }"], 'z')?; - assert_insert(&["{ ab┃: \"\" }"], &["{ abc┃: \"\" }"], 'c')?; - assert_insert(&["{ ┃ab: \"\" }"], &["{ z┃ab: \"\" }"], 'z')?; - assert_insert( - &["{ camelCase┃: \"hello\" }"], - &["{ camelCaseB┃: \"hello\" }"], - 'B', + assert_insert_in_def_nls(ovec!["{ ┃ }"], '{')?; + assert_insert_nls(ovec!["val = { ┃ }"], ovec!["val = { a┃ }"], 'a')?; + assert_insert_nls( + ovec!["val = { a┃ }"], + ovec!["val = { ab┃: RunTimeError }"], + 'b', + )?; // TODO: remove RunTimeError, see isue #1649 + assert_insert_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a1┃: RunTimeError }"], + '1', )?; - assert_insert( - &["{ camel┃Case: \"hello\" }"], - &["{ camelZ┃Case: \"hello\" }"], - 'Z', + assert_insert_nls( + ovec!["val = { a1┃ }"], + ovec!["val = { a1z┃: RunTimeError }"], + 'z', )?; - assert_insert( - &["{ ┃camelCase: \"hello\" }"], - &["{ z┃camelCase: \"hello\" }"], + assert_insert_nls( + ovec!["val = { a1┃ }"], + ovec!["val = { a15┃: RunTimeError }"], + '5', + )?; + assert_insert_nls( + ovec!["val = { ab┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + 'c', + )?; + assert_insert_nls( + ovec!["val = { ┃abc }"], + ovec!["val = { z┃abc: RunTimeError }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { a┃b }"], + ovec!["val = { az┃b: RunTimeError }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { a┃b }"], + ovec!["val = { a9┃b: RunTimeError }"], + '9', + )?; + + assert_insert_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a┃: RunTimeError }"], + ':', + )?; + assert_insert_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + ':', + )?; + assert_insert_nls( + ovec!["val = { aBc┃ }"], + ovec!["val = { aBc┃: RunTimeError }"], + ':', + )?; + + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a┃: RunTimeError }"], + ":\"", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + ":\"", + )?; + + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a0┃: RunTimeError }"], + ":0", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc9┃: RunTimeError }"], + ":9", + )?; + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a1000┃: RunTimeError }"], + ":1000", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc98761┃: RunTimeError }"], + ":98761", + )?; + + assert_insert_nls( + ovec!["val = { a: \"┃\" }"], + ovec!["val = { a: \"a┃\" }"], + 'a', + )?; + assert_insert_nls( + ovec!["val = { a: \"a┃\" }"], + ovec!["val = { a: \"ab┃\" }"], + 'b', + )?; + assert_insert_nls( + ovec!["val = { a: \"a┃b\" }"], + ovec!["val = { a: \"az┃b\" }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { a: \"┃ab\" }"], + ovec!["val = { a: \"z┃ab\" }"], 'z', )?; - assert_insert(&["{ a┃: 0 }"], &["{ ab┃: 0 }"], 'b')?; - assert_insert(&["{ ┃a: 2100 }"], &["{ z┃a: 2100 }"], 'z')?; - assert_insert(&["{ ab┃: 9876 }"], &["{ abc┃: 9876 }"], 'c')?; - assert_insert(&["{ ┃ab: 102 }"], &["{ z┃ab: 102 }"], 'z')?; - assert_insert(&["{ camelCase┃: 99999 }"], &["{ camelCaseB┃: 99999 }"], 'B')?; - assert_insert(&["{ camel┃Case: 88156 }"], &["{ camelZ┃Case: 88156 }"], 'Z')?; - assert_insert(&["{ ┃camelCase: 1 }"], &["{ z┃camelCase: 1 }"], 'z')?; + assert_insert_nls(ovec!["val = { a: 1┃ }"], ovec!["val = { a: 10┃ }"], '0')?; + assert_insert_nls(ovec!["val = { a: 100┃ }"], ovec!["val = { a: 1004┃ }"], '4')?; + assert_insert_nls(ovec!["val = { a: 9┃76 }"], ovec!["val = { a: 98┃76 }"], '8')?; + assert_insert_nls( + ovec!["val = { a: 4┃691 }"], + ovec!["val = { a: 40┃691 }"], + '0', + )?; + assert_insert_nls( + ovec!["val = { a: 469┃1 }"], + ovec!["val = { a: 4699┃1 }"], + '9', + )?; - assert_insert_seq(&["┃"], &["{ camelCase: \"hello┃\" }"], "{camelCase:\"hello")?; - assert_insert_seq(&["┃"], &["{ camelCase: 10009┃ }"], "{camelCase:10009")?; + assert_insert_nls( + ovec!["val = { camelCase: \"┃\" }"], + ovec!["val = { camelCase: \"a┃\" }"], + 'a', + )?; + assert_insert_nls( + ovec!["val = { camelCase: \"a┃\" }"], + ovec!["val = { camelCase: \"ab┃\" }"], + 'b', + )?; + + assert_insert_nls( + ovec!["val = { camelCase: 3┃ }"], + ovec!["val = { camelCase: 35┃ }"], + '5', + )?; + assert_insert_nls( + ovec!["val = { camelCase: ┃2 }"], + ovec!["val = { camelCase: 5┃2 }"], + '5', + )?; + assert_insert_nls( + ovec!["val = { camelCase: 10┃2 }"], + ovec!["val = { camelCase: 106┃2 }"], + '6', + )?; + + assert_insert_nls( + ovec!["val = { a┃: \"\" }"], + ovec!["val = { ab┃: \"\" }"], + 'b', + )?; + assert_insert_nls( + ovec!["val = { ┃a: \"\" }"], + ovec!["val = { z┃a: \"\" }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { ab┃: \"\" }"], + ovec!["val = { abc┃: \"\" }"], + 'c', + )?; + assert_insert_nls( + ovec!["val = { ┃ab: \"\" }"], + ovec!["val = { z┃ab: \"\" }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { camelCase┃: \"hello\" }"], + ovec!["val = { camelCaseB┃: \"hello\" }"], + 'B', + )?; + assert_insert_nls( + ovec!["val = { camel┃Case: \"hello\" }"], + ovec!["val = { camelZ┃Case: \"hello\" }"], + 'Z', + )?; + assert_insert_nls( + ovec!["val = { ┃camelCase: \"hello\" }"], + ovec!["val = { z┃camelCase: \"hello\" }"], + 'z', + )?; + + assert_insert_nls(ovec!["val = { a┃: 0 }"], ovec!["val = { ab┃: 0 }"], 'b')?; + assert_insert_nls( + ovec!["val = { ┃a: 2100 }"], + ovec!["val = { z┃a: 2100 }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { ab┃: 9876 }"], + ovec!["val = { abc┃: 9876 }"], + 'c', + )?; + assert_insert_nls( + ovec!["val = { ┃ab: 102 }"], + ovec!["val = { z┃ab: 102 }"], + 'z', + )?; + assert_insert_nls( + ovec!["val = { camelCase┃: 99999 }"], + ovec!["val = { camelCaseB┃: 99999 }"], + 'B', + )?; + assert_insert_nls( + ovec!["val = { camel┃Case: 88156 }"], + ovec!["val = { camelZ┃Case: 88156 }"], + 'Z', + )?; + assert_insert_nls( + ovec!["val = { ┃camelCase: 1 }"], + ovec!["val = { z┃camelCase: 1 }"], + 'z', + )?; + + assert_insert_seq_nls( + ovec!["val = { ┃ }"], + ovec!["val = { camelCase: \"hello┃\" }"], + "camelCase:\"hello", + )?; + assert_insert_seq_nls( + ovec!["val = { ┃ }"], + ovec!["val = { camelCase: 10009┃ }"], + "camelCase:10009", + )?; Ok(()) } #[test] fn test_nested_record() -> Result<(), String> { - assert_insert_seq(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ":{")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ":{")?; - assert_insert_seq(&["{ camelCase┃ }"], &["{ camelCase┃: RunTimeError }"], ":{")?; + assert_insert_seq_nls( + ovec!["val = { a┃ }"], + ovec!["val = { a┃: RunTimeError }"], + ":{", + )?; + assert_insert_seq_nls( + ovec!["val = { abc┃ }"], + ovec!["val = { abc┃: RunTimeError }"], + ":{", + )?; + assert_insert_seq_nls( + ovec!["val = { camelCase┃ }"], + ovec!["val = { camelCase┃: RunTimeError }"], + ":{", + )?; - assert_insert_seq(&["{ a: { ┃ } }"], &["{ a: { zulu┃ } }"], "zulu")?; - assert_insert_seq( - &["{ abc: { ┃ } }"], - &["{ abc: { camelCase┃ } }"], + assert_insert_seq_nls( + ovec!["val = { a: { ┃ } }"], + ovec!["val = { a: { zulu┃ } }"], + "zulu", + )?; + assert_insert_seq_nls( + ovec!["val = { abc: { ┃ } }"], + ovec!["val = { abc: { camelCase┃ } }"], "camelCase", )?; - assert_insert_seq(&["{ camelCase: { ┃ } }"], &["{ camelCase: { z┃ } }"], "z")?; - - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu┃: RunTimeError } }"], - ":", - )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase┃: RunTimeError } }"], - ":", - )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z┃: RunTimeError } }"], - ":", - )?; - - assert_insert_seq( - &["{ a┃: { zulu } }"], - &["{ a0┃: { zulu: RunTimeError } }"], - "0", - )?; - assert_insert_seq( - &["{ ab┃c: { camelCase } }"], - &["{ abz┃c: { camelCase: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { ┃ } }"], + ovec!["val = { camelCase: { z┃ } }"], "z", )?; - assert_insert_seq( - &["{ ┃camelCase: { z } }"], - &["{ x┃camelCase: { z: RunTimeError } }"], + + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu┃: RunTimeError } }"], + ":", + )?; + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase┃: RunTimeError } }"], + ":", + )?; + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z┃: RunTimeError } }"], + ":", + )?; + + assert_insert_seq_nls( + ovec!["val = { a┃: { zulu } }"], + ovec!["val = { a0┃: { zulu: RunTimeError } }"], + "0", + )?; + assert_insert_seq_nls( + ovec!["val = { ab┃c: { camelCase } }"], + ovec!["val = { abz┃c: { camelCase: RunTimeError } }"], + "z", + )?; + assert_insert_seq_nls( + ovec!["val = { ┃camelCase: { z } }"], + ovec!["val = { x┃camelCase: { z: RunTimeError } }"], "x", )?; - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu┃: RunTimeError } }"], ":\"", )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase┃: RunTimeError } }"], ":\"", )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z┃: RunTimeError } }"], ":\"", )?; - assert_insert_seq( - &["{ a: { zulu: \"┃\" } }"], - &["{ a: { zulu: \"azula┃\" } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: \"┃\" } }"], + ovec!["val = { a: { zulu: \"azula┃\" } }"], "azula", )?; - assert_insert_seq( - &["{ a: { zulu: \"az┃a\" } }"], - &["{ a: { zulu: \"azul┃a\" } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: \"az┃a\" } }"], + ovec!["val = { a: { zulu: \"azul┃a\" } }"], "ul", )?; - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu1┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu1┃: RunTimeError } }"], ":1", )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase0┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase0┃: RunTimeError } }"], ":0", )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z45┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z45┃: RunTimeError } }"], ":45", )?; - assert_insert_seq(&["{ a: { zulu: ┃0 } }"], &["{ a: { zulu: 4┃0 } }"], "4")?; - assert_insert_seq( - &["{ a: { zulu: 10┃98 } }"], - &["{ a: { zulu: 1077┃98 } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: ┃0 } }"], + ovec!["val = { a: { zulu: 4┃0 } }"], + "4", + )?; + assert_insert_seq_nls( + ovec!["val = { a: { zulu: 10┃98 } }"], + ovec!["val = { a: { zulu: 1077┃98 } }"], "77", )?; - assert_insert_seq( - &["{ a: { zulu┃ } }"], - &["{ a: { zulu┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃ } }"], + ovec!["val = { a: { zulu┃: RunTimeError } }"], ":{", )?; - assert_insert_seq( - &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { abc: { camelCase┃ } }"], + ovec!["val = { abc: { camelCase┃: RunTimeError } }"], ":{", )?; - assert_insert_seq( - &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z┃: RunTimeError } }"], + assert_insert_seq_nls( + ovec!["val = { camelCase: { z┃ } }"], + ovec!["val = { camelCase: { z┃: RunTimeError } }"], ":{", )?; - assert_insert_seq( - &["{ a: { zulu: { ┃ } } }"], - &["{ a: { zulu: { he┃ } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu: { ┃ } } }"], + ovec!["val = { a: { zulu: { he┃ } } }"], "he", )?; - assert_insert_seq( - &["{ a: { ┃zulu: { } } }"], - &["{ a: { x┃zulu: { } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { ┃zulu: { } } }"], + ovec!["val = { a: { x┃zulu: { } } }"], "x", )?; - assert_insert_seq( - &["{ a: { z┃ulu: { } } }"], - &["{ a: { z9┃ulu: { } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { z┃ulu: { } } }"], + ovec!["val = { a: { z9┃ulu: { } } }"], "9", )?; - assert_insert_seq( - &["{ a: { zulu┃: { } } }"], - &["{ a: { zulu7┃: { } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { zulu┃: { } } }"], + ovec!["val = { a: { zulu7┃: { } } }"], "7", )?; - assert_insert_seq( - &["{ a┃: { bcD: { eFgHij: { k15 } } } }"], - &["{ a4┃: { bcD: { eFgHij: { k15: RunTimeError } } } }"], + assert_insert_seq_nls( + ovec!["val = { a┃: { bcD: { eFgHij: { k15 } } } }"], + ovec!["val = { a4┃: { bcD: { eFgHij: { k15: RunTimeError } } } }"], "4", )?; - assert_insert_seq( - &["{ ┃a: { bcD: { eFgHij: { k15 } } } }"], - &["{ y┃a: { bcD: { eFgHij: { k15: RunTimeError } } } }"], + assert_insert_seq_nls( + ovec!["val = { ┃a: { bcD: { eFgHij: { k15 } } } }"], + ovec!["val = { y┃a: { bcD: { eFgHij: { k15: RunTimeError } } } }"], "y", )?; - assert_insert_seq( - &["{ a: { bcD: { eF┃gHij: { k15 } } } }"], - &["{ a: { bcD: { eFxyz┃gHij: { k15: RunTimeError } } } }"], + assert_insert_seq_nls( + ovec!["val = { a: { bcD: { eF┃gHij: { k15 } } } }"], + ovec!["val = { a: { bcD: { eFxyz┃gHij: { k15: RunTimeError } } } }"], "xyz", )?; - assert_insert_seq( - &["┃"], - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - "{g:{oi:{ng:{d:{e:{e:{p:{camelCase", + assert_insert_seq_nls( + ovec!["val = { ┃ }"], + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + "g:{oi:{ng:{d:{e:{e:{p:{camelCase", )?; Ok(()) } - const IGNORE_CHARS: &str = "{}()[]-><-_\"azAZ:@09"; - const IGNORE_NO_LTR: &str = "{\"5"; - const IGNORE_NO_NUM: &str = "a{\""; - #[test] fn test_ignore_record() -> Result<(), String> { - assert_insert_seq_ignore(&["┃{ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ ┃}"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃}"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ ┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃a: RunTimeError }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃abc: RunTimeError }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃a: RunTimeError }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃abc: RunTimeError }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ a: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a:┃ RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a15: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a15: ┃RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a15: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a15:┃ RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a15: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a15: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a15: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a15:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: ┃RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ camelCase: RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase:┃ RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃\"\" }"], "0")?; - assert_insert_seq_ignore(&["{ a: ┃\"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"\"┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"\" }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃\"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"\" }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: 1 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: 2 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃6 }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 8┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 0 }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: 1 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: 2 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃6 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 8┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 0 }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase: 1 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ camelCase: 7 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: ┃2 }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCase: 4┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCase: 9 }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: 1 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: 7 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃2 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: 4┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: 9 }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ camelCase: \"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: ┃\"\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: \"\"┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase: \"\" }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃\"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: \"\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCase: \"\" }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: \"z\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: \"z\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃\"z\" }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"z\"┃ }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: \"z\" }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: \"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: \"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃\"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"z\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: \"z\" }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore( - &["┃{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{┃ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + assert_insert_seq_ignore_nls( + ovec!["val = {┃ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{ a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" }"], + assert_insert_seq_ignore_nls( + ovec!["val = { a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{ a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ }"], + assert_insert_seq_ignore_nls( + ovec!["val = { a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ }"], IGNORE_CHARS, )?; - assert_insert_seq_ignore( - &["{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃"], + assert_insert_seq_ignore_nls( + ovec!["val = { a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃"], IGNORE_CHARS, )?; - assert_insert_seq_ignore(&["┃{ a: 915480 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a: 915480 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a: ┃915480 }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 915480┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ a: 915480 }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: 915480 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: 915480 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃915480 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 915480┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: 915480 }┃"], IGNORE_CHARS)?; Ok(()) } #[test] fn test_ignore_nested_record() -> Result<(), String> { - assert_insert_seq_ignore(&["{ a: { ┃ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: ┃{ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: {┃ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: { }┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: { } ┃}"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a: { } }┃"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ a:┃ { } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{┃ a: { } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ a: { } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃a: { } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { ┃ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: ┃{ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: {┃ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { }┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { } ┃}"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a: { } }┃"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { a:┃ { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ a: { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃a: { } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: R┃unTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: Ru┃nTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: RunTimeError } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: RunTimeError } }"], "1")?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: ┃{ z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: R┃unTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: Ru┃nTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1:┃ { z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = {┃ camelCaseB1: { z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: RunTimeError } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a: RunTimeError } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a: RunTimeError } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\"┃ } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃\"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" ┃} }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" }┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } ┃}"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } }┃"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: \"\" } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: \"\" } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: \"\" } }"], "1")?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\"┃ } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃\"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" ┃} }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: ┃{ z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" }┃ }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" } ┃}"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"\" } }┃"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1:┃ { z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = {┃ camelCaseB1: { z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: \"\" } }"], + IGNORE_NO_LTR, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a: \"\" } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a: \"\" } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 0┃ } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃123 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ 999 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 80 ┃} }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: 99000 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: 12 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 7 }┃ }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 98 } ┃}"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 4582 } }┃"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: 0 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: 44 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: 100123 } }"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: 5 } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: 6 } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 0┃ } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃123 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ 999 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 80 ┃} }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: 99000 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: ┃{ z15a: 12 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 7 }┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { z15a: 98 } ┃}"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: 4582 } }┃"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1:┃ { z15a: 0 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCaseB1: { z15a: 44 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: 100123 } }"], + IGNORE_NO_NUM, + )?; + assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a: 5 } }"], "1")?; + assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a: 6 } }"], "1")?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a:┃ \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a:┃ \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" ┃} }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" ┃} }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: {┃ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: {┃ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: ┃{ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: ┃{ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } ┃}"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } ┃}"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }┃"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }┃"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ camelCaseB1:┃ { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1:┃ { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{┃ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = {┃ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["┃{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ ┃camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { ┃camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], "1", )?; - assert_insert_seq_ignore( - &["{ camelCaseB1: { ┃z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { camelCaseB1: { ┃z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], "1", )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase:┃ RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase:┃ RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: R┃unTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: R┃unTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }┃"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }┃"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeEr┃ror } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeEr┃ror } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = {┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = ┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; - assert_insert_seq_ignore( - &["{ ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], + assert_insert_seq_ignore_nls( + ovec!["val = { ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], "2", )?; Ok(()) @@ -1573,76 +2357,39 @@ pub mod test_ed_update { #[test] fn test_single_elt_list() -> Result<(), String> { - assert_insert(&["┃"], &["[ ┃ ]"], '[')?; + assert_insert_in_def_nls(ovec!["[ ┃ ]"], '[')?; - assert_insert_seq(&["┃"], &["[ 0┃ ]"], "[0")?; - assert_insert_seq(&["┃"], &["[ 1┃ ]"], "[1")?; - assert_insert_seq(&["┃"], &["[ 9┃ ]"], "[9")?; + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 0┃ ]"], '0')?; + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 1┃ ]"], '1')?; + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 9┃ ]"], '9')?; - assert_insert_seq(&["┃"], &["[ \"┃\" ]"], "[\"")?; - assert_insert_seq( - &["┃"], - &["[ \"hello, hello.0123456789ZXY{}[]-><-┃\" ]"], - "[\"hello, hello.0123456789ZXY{}[]-><-", + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ \"┃\" ]"], '\"')?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ \"hello, hello.0123456789ZXY{}[]-><-┃\" ]"], + "\"hello, hello.0123456789ZXY{}[]-><-", )?; - assert_insert_seq(&["┃"], &["[ { ┃ } ]"], "[{")?; - assert_insert_seq(&["┃"], &["[ { a┃ } ]"], "[{a")?; - assert_insert_seq( - &["┃"], - &["[ { camelCase: { zulu: \"nested┃\" } } ]"], - "[{camelCase:{zulu:\"nested", + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ { ┃ } ]"], '{')?; + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ { a┃ } ]"], "{a")?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ { camelCase: { zulu: \"nested┃\" } } ]"], + "{camelCase:{zulu:\"nested", )?; - assert_insert_seq(&["┃"], &["[ [ ┃ ] ]"], "[[")?; - assert_insert_seq(&["┃"], &["[ [ [ ┃ ] ] ]"], "[[[")?; - assert_insert_seq(&["┃"], &["[ [ 0┃ ] ]"], "[[0")?; - assert_insert_seq(&["┃"], &["[ [ \"abc┃\" ] ]"], "[[\"abc")?; - assert_insert_seq( - &["┃"], - &["[ [ { camelCase: { a: 79000┃ } } ] ]"], - "[[{camelCase:{a:79000", + assert_insert_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ [ ┃ ] ]"], '[')?; + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ [ [ ┃ ] ] ]"], "[[")?; + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ [ 0┃ ] ]"], "[0")?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ \"abc┃\" ] ]"], + "[\"abc", )?; - - Ok(()) - } - - #[test] - fn test_multi_elt_list() -> Result<(), String> { - assert_insert_seq(&["┃"], &["[ 0, 1┃ ]"], "[0,1")?; - assert_insert_seq(&["┃"], &["[ 987, 6543, 210┃ ]"], "[987,6543,210")?; - - assert_insert_seq( - &["┃"], - &["[ \"a\", \"bcd\", \"EFGH┃\" ]"], - "[\"a🡲,\"bcd🡲,\"EFGH", - )?; - - assert_insert_seq( - &["┃"], - &["[ { a: 1 }, { b: 23 }, { c: 456┃ } ]"], - "[{a:1🡲🡲,{b:23🡲🡲,{c:456", - )?; - - assert_insert_seq(&["┃"], &["[ [ 1 ], [ 23 ], [ 456┃ ] ]"], "[[1🡲🡲,[23🡲🡲,[456")?; - - // insert element in between - assert_insert_seq(&["┃"], &["[ 0, 2┃, 1 ]"], "[0,1🡰🡰🡰,2")?; - assert_insert_seq(&["┃"], &["[ 0, 2, 3┃, 1 ]"], "[0,1🡰🡰🡰,2,3")?; - assert_insert_seq(&["┃"], &["[ 0, 3┃, 2, 1 ]"], "[0,1🡰🡰🡰,2🡰🡰🡰,3")?; - - assert_insert_seq( - &["┃"], - &["[ \"abc\", \"f┃\", \"de\" ]"], - "[\"abc🡲,\"de🡰🡰🡰🡰🡰,\"f", - )?; - - assert_insert_seq(&["┃"], &["[ [ 0 ], [ 2┃ ], [ 1 ] ]"], "[[0🡲🡲,[1🡰🡰🡰🡰🡰,[2")?; - - assert_insert_seq( - &["┃"], - &["[ { a: 0 }, { a: 2┃ }, { a: 1 } ]"], - "[{a:0🡲🡲,{a:1🡰🡰🡰🡰🡰🡰🡰🡰,{a:2", + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ { camelCase: { a: 79000┃ } } ] ]"], + "[{camelCase:{a:79000", )?; Ok(()) @@ -1650,244 +2397,442 @@ pub mod test_ed_update { #[test] fn test_ignore_single_elt_list() -> Result<(), String> { - assert_insert_seq_ignore(&["┃[ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ 0 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 0 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 0 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 0 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ 137 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 137 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 137 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 137 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃137 ]"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["[ 137┃ ]"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 137 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 137 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 137 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 137 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃137 ]"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 137┃ ]"], IGNORE_NO_NUM)?; - assert_insert_seq_ignore(&["┃[ \"teststring\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"teststring\" ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ \"teststring\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"teststring\" ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃\"teststring\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"teststring\"┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ \"teststring\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"teststring\" ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ \"teststring\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"teststring\" ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃\"teststring\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"teststring\"┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 } ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 } ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃{ a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ {┃ a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a:┃ 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 ┃} ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 1 }┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 } ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 } ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃{ a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ {┃ a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a:┃ 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 ┃} ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 1 }┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ [ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ] ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ [ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ] ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃[ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ]┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [┃ ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ ┃] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ [ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ] ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ [ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ] ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃[ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ]┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [┃ ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ ┃] ]"], IGNORE_CHARS)?; + + Ok(()) + } + + #[test] + fn test_multi_elt_list() -> Result<(), String> { + assert_insert_seq_nls(ovec!["val = [ ┃ ]"], ovec!["val = [ 0, 1┃ ]"], "0,1")?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 987, 6543, 210┃ ]"], + "987,6543,210", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ \"a\", \"bcd\", \"EFGH┃\" ]"], + "\"a🡲,\"bcd🡲,\"EFGH", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ { a: 1 }, { b: 23 }, { c: 456┃ } ]"], + "{a:1🡲🡲,{b:23🡲🡲,{c:456", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ 1 ], [ 23 ], [ 456┃ ] ]"], + "[1🡲🡲,[23🡲🡲,[456", + )?; + + // insert element in between + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 0, 2┃, 1 ]"], + "0,1🡰🡰🡰,2", + )?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 0, 2, 3┃, 1 ]"], + "0,1🡰🡰🡰,2,3", + )?; + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ 0, 3┃, 2, 1 ]"], + "0,1🡰🡰🡰,2🡰🡰🡰,3", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ \"abc\", \"f┃\", \"de\" ]"], + "\"abc🡲,\"de🡰🡰🡰🡰🡰,\"f", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ [ 0 ], [ 2┃ ], [ 1 ] ]"], + "[0🡲🡲,[1🡰🡰🡰🡰🡰,[2", + )?; + + assert_insert_seq_nls( + ovec!["val = [ ┃ ]"], + ovec!["val = [ { a: 0 }, { a: 2┃ }, { a: 1 } ]"], + "{a:0🡲🡲,{a:1🡰🡰🡰🡰🡰🡰🡰🡰,{a:2", + )?; Ok(()) } #[test] fn test_ignore_multi_elt_list() -> Result<(), String> { - assert_insert_seq_ignore(&["┃[ 0, 1 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0, 1 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 0, 1 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0, 1 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 0,┃ 1 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 0, 1 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0, 1 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 0, 1 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0, 1 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 0,┃ 1 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ 123, 56, 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123, 56, 7 ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ 123, 56, 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123, 56, 7 ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123,┃ 56, 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ 123, 56,┃ 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ 123, 56, 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123, 56, 7 ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ 123, 56, 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123, 56, 7 ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123,┃ 56, 7 ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ 123, 56,┃ 7 ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\", \"56\", \"7\" ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\", \"56\", \"7\" ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\",┃ \"56\", \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ \"123\", \"56\",┃ \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\", \"56\", \"7\" ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ \"123\", \"56\", \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\", \"56\", \"7\" ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\",┃ \"56\", \"7\" ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ \"123\", \"56\",┃ \"7\" ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 0 }, { a: 1 } ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 0 }, { a: 1 } ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ { a: 0 },┃ { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 0 }, { a: 1 } ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ { a: 0 }, { a: 1 } ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 0 }, { a: 1 } ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ { a: 0 },┃ { a: 1 } ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃[ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ] ]┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[┃ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ] ┃]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ],┃ [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ ┃[ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ]┃, [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [┃ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ┃], [ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], ┃[ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [┃ 1 ] ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ]┃ ]"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["[ [ 0 ], [ 1 ┃] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = ┃[ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ] ]┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [┃ [ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ] ┃]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ],┃ [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ ┃[ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ]┃, [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [┃ 0 ], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ┃], [ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], ┃[ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [┃ 1 ] ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ]┃ ]"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["val = [ [ 0 ], [ 1 ┃] ]"], IGNORE_CHARS)?; Ok(()) } - // Create ed_model from pre_lines DSL, do ctrl+shift+up as many times as repeat. + #[test] + fn test_tld_value() -> Result<(), String> { + assert_insert_nls(ovec!["┃"], ovec!["a┃ = "], 'a')?; + assert_insert_nls(ovec!["┃"], ovec!["m┃ = "], 'm')?; + assert_insert_nls(ovec!["┃"], ovec!["z┃ = "], 'z')?; + + assert_insert_seq_nls(ovec!["┃"], ovec!["ab┃ = "], "ab")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["mainVal┃ = "], "mainVal")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["camelCase123┃ = "], "camelCase123")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["c137┃ = "], "c137")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["c137Bb┃ = "], "c137Bb")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["bBbb┃ = "], "bBbb")?; + assert_insert_seq_nls(ovec!["┃"], ovec!["cC0Z┃ = "], "cC0Z")?; + + Ok(()) + } + + #[test] + fn test_ignore_tld_value() -> Result<(), String> { + assert_insert_seq_ignore_nls(ovec!["a ┃= 0"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["a =┃ 0"], IGNORE_CHARS)?; + + assert_insert_seq_ignore_nls(ovec!["aBC ┃= 0"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["aBC =┃ 0"], IGNORE_CHARS)?; + + assert_insert_seq_ignore_nls(ovec!["camelCase123 ┃= 0"], IGNORE_CHARS)?; + assert_insert_seq_ignore_nls(ovec!["camelCase123 =┃ 0"], IGNORE_CHARS)?; + + Ok(()) + } + + #[test] + fn test_enter() -> Result<(), String> { + assert_insert_seq( + ovec!["┃"], + ovec!["ab = 5", "", "cd = \"good┃\"", "", ""], + "ab🡲🡲🡲5\rcd🡲🡲🡲\"good", + )?; + + assert_insert_seq( + ovec!["┃"], + ovec!["ab = 1", "", "cD = 2┃", "", "eF = 3", "", ""], + "ab🡲🡲🡲1\reF🡲🡲🡲3🡰🡰🡰🡰🡰🡰🡱🡱🡲🡲🡲🡲🡲🡲\rcD🡲🡲🡲2", + )?; + + Ok(()) + } + + // Create ed_model from pre_lines DSL, do handle_new_char for every char in input_seq, do ctrl+shift+up as many times as repeat. // check if modified ed_model has expected string representation of code, caret position and active selection. pub fn assert_ctrl_shift_up_repeat( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, + input_seq: &str, repeats: usize, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; + + for input_char in input_seq.chars() { + if input_char == '🡲' { + ed_model.simple_move_carets_right(1); + } else if input_char == '🡰' { + ed_model.simple_move_carets_left(1); + } else if input_char == '🡱' { + ed_model.simple_move_carets_up(1); + } else { + //dbg!(input_char); + ed_res_to_res(handle_new_char(&input_char, &mut ed_model))?; + } + } for _ in 0..repeats { ed_model.ed_handle_key_down(&ctrl_cmd_shift(), Up)?; } - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); // remove header for clean tests - assert_eq!(post_lines, expected_post_lines); + assert_eq!(post_lines, add_nls(expected_post_lines)); Ok(()) } - pub fn assert_ctrl_shift_up( - pre_lines: &[&str], - expected_post_lines: &[&str], + pub fn assert_ctrl_shift_up_no_inp( + pre_lines: Vec, + expected_post_lines: Vec, ) -> Result<(), String> { - assert_ctrl_shift_up_repeat(pre_lines, expected_post_lines, 1) + assert_ctrl_shift_up_repeat(pre_lines, expected_post_lines, "", 1) + } + + pub fn assert_ctrl_shift_up_repeat_no_inp( + pre_lines: Vec, + expected_post_lines: Vec, + repeats: usize, + ) -> Result<(), String> { + assert_ctrl_shift_up_repeat(pre_lines, expected_post_lines, "", repeats) } #[test] fn test_ctrl_shift_up_blank() -> Result<(), String> { - // Blank is auto-inserted - assert_ctrl_shift_up(&["┃"], &["┃❮ ❯"])?; - assert_ctrl_shift_up_repeat(&["┃"], &["┃❮ ❯"], 4)?; + // Blank is auto-inserted when creating top level def + assert_ctrl_shift_up_repeat(ovec!["┃"], ovec!["val = ┃❮ ❯"], "val=🡲🡲🡲", 1)?; + assert_ctrl_shift_up_repeat(ovec!["┃"], ovec!["┃❮val = ❯"], "val=🡲🡲🡲", 4)?; Ok(()) } #[test] fn test_ctrl_shift_up_int() -> Result<(), String> { - assert_ctrl_shift_up(&["5┃"], &["┃❮5❯"])?; - assert_ctrl_shift_up_repeat(&["0┃"], &["┃❮0❯"], 3)?; - assert_ctrl_shift_up(&["12345┃"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["┃12345"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["1┃2345"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["12┃345"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["123┃45"], &["┃❮12345❯"])?; - assert_ctrl_shift_up(&["1234┃5"], &["┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 5┃"], ovec!["val = ┃❮5❯"])?; + assert_ctrl_shift_up_repeat_no_inp(ovec!["val = 0┃"], ovec!["┃❮val = 0❯"], 4)?; + assert_ctrl_shift_up_no_inp(ovec!["val = 12345┃"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃12345"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 1┃2345"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 12┃345"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 123┃45"], ovec!["val = ┃❮12345❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = 1234┃5"], ovec!["val = ┃❮12345❯"])?; Ok(()) } #[test] fn test_ctrl_shift_up_string() -> Result<(), String> { - assert_ctrl_shift_up(&["\"┃\""], &["┃❮\"\"❯"])?; - assert_ctrl_shift_up(&["┃\"\""], &["┃❮\"\"❯"])?; - assert_ctrl_shift_up(&["\"┃0\""], &["┃❮\"0\"❯"])?; - assert_ctrl_shift_up(&["\"0┃\""], &["┃❮\"0\"❯"])?; - assert_ctrl_shift_up(&["\"abc┃\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up(&["\"ab┃c\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up(&["\"┃abc\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up(&["┃\"abc\""], &["┃❮\"abc\"❯"])?; - assert_ctrl_shift_up_repeat(&["\"abc┃\""], &["┃❮\"abc\"❯"], 3)?; - assert_ctrl_shift_up( - &["\"hello, hello.0123456789ZXY{}[]-><-┃\""], - &["┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯"], + assert_ctrl_shift_up_no_inp(ovec!["val = \"┃\""], ovec!["val = ┃❮\"\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃\"\""], ovec!["val = ┃❮\"\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"┃0\""], ovec!["val = ┃❮\"0\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"0┃\""], ovec!["val = ┃❮\"0\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"abc┃\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"ab┃c\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"┃abc\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃\"abc\""], ovec!["val = ┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_repeat_no_inp(ovec!["val = \"abc┃\""], ovec!["┃❮val = \"abc\"❯"], 4)?; + assert_ctrl_shift_up_no_inp( + ovec!["val = \"hello, hello.0123456789ZXY{}[]-><-┃\""], + ovec!["val = ┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯"], )?; - assert_ctrl_shift_up(&["\"\"┃"], &["┃❮\"\"❯"])?; - assert_ctrl_shift_up(&["\"abc\"┃"], &["┃❮\"abc\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"\"┃"], ovec!["val = ┃❮\"\"❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = \"abc\"┃"], ovec!["val = ┃❮\"abc\"❯"])?; Ok(()) } #[test] fn test_ctrl_shift_up_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - assert_ctrl_shift_up(&["{ ┃ }"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up(&["{┃ }"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up(&["┃{ }"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up(&["{ ┃}"], &["┃❮{ }❯"])?; - assert_ctrl_shift_up_repeat(&["{ ┃ }"], &["┃❮{ }❯"], 4)?; - assert_ctrl_shift_up(&["{ }┃"], &["┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃ }"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ }"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ }"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃}"], ovec!["val = ┃❮{ }❯"])?; + assert_ctrl_shift_up_repeat_no_inp(ovec!["val = { ┃ }"], ovec!["┃❮val = { }❯"], 4)?; + assert_ctrl_shift_up_no_inp(ovec!["val = { }┃"], ovec!["val = ┃❮{ }❯"])?; + // TODO uncomment tests once #1649 is fixed + /*assert_ctrl_shift_up_no_inp(ovec!["val = { pear┃ }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { pea┃r }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { p┃ear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃pear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ pear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ pear }"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { pear ┃}"], ovec!["val = ┃❮{ pear }❯"])?; + assert_ctrl_shift_up_repeat(ovec!["val = { pear┃ }"], ovec!["val = ┃❮{ pear }❯"], 3)?; + assert_ctrl_shift_up_no_inp(ovec!["val = { pear }┃"], ovec!["val = ┃❮{ pear }❯"])?; - /*assert_ctrl_shift_up(&["{ pear┃ }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ pea┃r }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ p┃ear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ ┃pear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{┃ pear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["┃{ pear }"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up(&["{ pear ┃}"], &["┃❮{ pear }❯"])?; - assert_ctrl_shift_up_repeat(&["{ pear┃ }"], &["┃❮{ pear }❯"], 3)?; - assert_ctrl_shift_up(&["{ pear }┃"], &["┃❮{ pear }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { camelCase123┃ }"], ovec!["val = ┃❮{ camelCase123 }❯"])?;*/ - assert_ctrl_shift_up(&["{ camelCase123┃ }"], &["┃❮{ camelCase123 }❯"])?;*/ - - assert_ctrl_shift_up(&["{ a: \"┃\" }"], &["{ a: ┃❮\"\"❯ }"])?; - assert_ctrl_shift_up(&["{ a: ┃\"\" }"], &["{ a: ┃❮\"\"❯ }"])?; - assert_ctrl_shift_up(&["{ a: \"\"┃ }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ a: \"\" ┃}"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: \"\" ┃}"], &["┃❮{ a: \"\" }❯"], 3)?; - assert_ctrl_shift_up(&["{ a: \"\" }┃"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ a:┃ \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ a┃: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{ ┃a: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["{┃ a: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up(&["┃{ a: \"\" }"], &["┃❮{ a: \"\" }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: \"┃\" }"], &["┃❮{ a: \"\" }❯"], 2)?; - assert_ctrl_shift_up_repeat(&["{ a: \"┃\" }"], &["┃❮{ a: \"\" }❯"], 4)?; - - assert_ctrl_shift_up(&["{ a: 1┃0 }"], &["{ a: ┃❮10❯ }"])?; - assert_ctrl_shift_up(&["{ a: ┃9 }"], &["{ a: ┃❮9❯ }"])?; - assert_ctrl_shift_up(&["{ a: 98┃89 }"], &["{ a: ┃❮9889❯ }"])?; - assert_ctrl_shift_up(&["{ a: 44┃ }"], &["┃❮{ a: 44 }❯"])?; - assert_ctrl_shift_up(&["{ a: 0 ┃}"], &["┃❮{ a: 0 }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: 123 ┃}"], &["┃❮{ a: 123 }❯"], 3)?; - assert_ctrl_shift_up(&["{ a: 96 }┃"], &["┃❮{ a: 96 }❯"])?; - assert_ctrl_shift_up(&["{ a:┃ 985600 }"], &["┃❮{ a: 985600 }❯"])?; - assert_ctrl_shift_up(&["{ a┃: 5648 }"], &["┃❮{ a: 5648 }❯"])?; - assert_ctrl_shift_up(&["{ ┃a: 1000000 }"], &["┃❮{ a: 1000000 }❯"])?; - assert_ctrl_shift_up(&["{┃ a: 1 }"], &["┃❮{ a: 1 }❯"])?; - assert_ctrl_shift_up(&["┃{ a: 900600 }"], &["┃❮{ a: 900600 }❯"])?; - assert_ctrl_shift_up_repeat(&["{ a: 10┃000 }"], &["┃❮{ a: 10000 }❯"], 2)?; - assert_ctrl_shift_up_repeat(&["{ a: ┃45 }"], &["┃❮{ a: 45 }❯"], 4)?; - - assert_ctrl_shift_up(&["{ abc: \"de┃\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: \"d┃e\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: \"┃de\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: ┃\"de\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; - assert_ctrl_shift_up(&["{ abc: \"de\"┃ }"], &["┃❮{ abc: \"de\" }❯"])?; - assert_ctrl_shift_up_repeat(&["{ abc: \"d┃e\" }"], &["┃❮{ abc: \"de\" }❯"], 2)?; - assert_ctrl_shift_up_repeat(&["{ abc: \"d┃e\" }"], &["┃❮{ abc: \"de\" }❯"], 3)?; - - assert_ctrl_shift_up( - &["{ camelCase123: \"hello, hello.012┃3456789ZXY{}[]-><-\" }"], - &["{ camelCase123: ┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯ }"], + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"┃\" }"], ovec!["val = { a: ┃❮\"\"❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: ┃\"\" }"], ovec!["val = { a: ┃❮\"\"❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"\"┃ }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"\" ┃}"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: \"\" ┃}"], + ovec!["┃❮val = { a: \"\" }❯"], + 3, )?; - assert_ctrl_shift_up( - &["{ camel┃Case123: \"hello, hello.0123456789ZXY{}[]-><-\" }"], - &["┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], + assert_ctrl_shift_up_no_inp(ovec!["val = { a: \"\" }┃"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a:┃ \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a┃: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { ┃a: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ a: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ a: \"\" }"], ovec!["val = ┃❮{ a: \"\" }❯"])?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: \"┃\" }"], + ovec!["val = ┃❮{ a: \"\" }❯"], + 2, )?; - assert_ctrl_shift_up_repeat( - &["{ camelCase123: \"hello, hello┃.0123456789ZXY{}[]-><-\" }"], - &["┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: \"┃\" }"], + ovec!["┃❮val = { a: \"\" }❯"], + 4, + )?; + + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 1┃0 }"], ovec!["val = { a: ┃❮10❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: ┃9 }"], ovec!["val = { a: ┃❮9❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 98┃89 }"], ovec!["val = { a: ┃❮9889❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 44┃ }"], ovec!["val = ┃❮{ a: 44 }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 0 ┃}"], ovec!["val = ┃❮{ a: 0 }❯"])?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: 123 ┃}"], + ovec!["┃❮val = { a: 123 }❯"], + 3, + )?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a: 96 }┃"], ovec!["val = ┃❮{ a: 96 }❯"])?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { a:┃ 985600 }"], + ovec!["val = ┃❮{ a: 985600 }❯"], + )?; + assert_ctrl_shift_up_no_inp(ovec!["val = { a┃: 5648 }"], ovec!["val = ┃❮{ a: 5648 }❯"])?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { ┃a: 1000000 }"], + ovec!["val = ┃❮{ a: 1000000 }❯"], + )?; + assert_ctrl_shift_up_no_inp(ovec!["val = {┃ a: 1 }"], ovec!["val = ┃❮{ a: 1 }❯"])?; + assert_ctrl_shift_up_no_inp( + ovec!["val = ┃{ a: 900600 }"], + ovec!["val = ┃❮{ a: 900600 }❯"], + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: 10┃000 }"], + ovec!["val = ┃❮{ a: 10000 }❯"], + 2, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { a: ┃45 }"], + ovec!["┃❮val = { a: 45 }❯"], + 4, + )?; + + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"de┃\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"d┃e\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"┃de\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: ┃\"de\" }"], + ovec!["val = { abc: ┃❮\"de\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: \"de\"┃ }"], + ovec!["val = ┃❮{ abc: \"de\" }❯"], + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: \"d┃e\" }"], + ovec!["val = ┃❮{ abc: \"de\" }❯"], + 2, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: \"d┃e\" }"], + ovec!["┃❮val = { abc: \"de\" }❯"], + 3, + )?; + + assert_ctrl_shift_up_no_inp( + ovec!["val = { camelCase123: \"hello, hello.012┃3456789ZXY{}[]-><-\" }"], + ovec!["val = { camelCase123: ┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { camel┃Case123: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + ovec!["val = ┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { camelCase123: \"hello, hello┃.0123456789ZXY{}[]-><-\" }"], + ovec!["val = ┃❮{ camelCase123: \"hello, hello.0123456789ZXY{}[]-><-\" }❯"], 2, )?; @@ -1896,103 +2841,173 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_nested_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - assert_ctrl_shift_up(&["{ abc: { ┃ } }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: {┃ } }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: ┃{ } }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { ┃} }"], &["{ abc: ┃❮{ }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { }┃ }"], &["┃❮{ abc: { } }❯"])?; - - /*assert_ctrl_shift_up(&["{ abc: { ┃d } }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: {┃ d } }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: ┃{ d } }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { d ┃} }"], &["{ abc: ┃❮{ d }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { d┃e } }"], &["{ abc: ┃❮{ de }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { d }┃ }"], &["┃❮{ abc: { d } }❯"])?; - assert_ctrl_shift_up(&["┃{ abc: { d } }"], &["┃❮{ abc: { d } }❯"])?;*/ - - assert_ctrl_shift_up(&["{ abc: { de: { ┃ } } }"], &["{ abc: { de: ┃❮{ }❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: ┃{ } } }"], &["{ abc: { de: ┃❮{ }❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: { }┃ } }"], &["{ abc: ┃❮{ de: { } }❯ }"])?; - - assert_ctrl_shift_up(&["{ abc: { de: \"┃\" } }"], &["{ abc: { de: ┃❮\"\"❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: ┃\"\" } }"], &["{ abc: { de: ┃❮\"\"❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: \"\"┃ } }"], &["{ abc: ┃❮{ de: \"\" }❯ }"])?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g┃\" } }"], - &["{ abc: { de: ┃❮\"f g\"❯ } }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { ┃ } }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: { de┃: \"f g\" } }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: {┃ } }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: {┃ de: \"f g\" } }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: ┃{ } }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g\" ┃} }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { ┃} }"], + ovec!["val = { abc: ┃❮{ }❯ }"], )?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g\" }┃ }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], - )?; - assert_ctrl_shift_up( - &["┃{ abc: { de: \"f g\" } }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], - )?; - assert_ctrl_shift_up( - &["{ abc: { de: \"f g\" } }┃"], - &["┃❮{ abc: { de: \"f g\" } }❯"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { }┃ }"], + ovec!["val = ┃❮{ abc: { } }❯"], )?; - assert_ctrl_shift_up_repeat( - &["{ abc: { de: \"f g┃\" } }"], - &["{ abc: ┃❮{ de: \"f g\" }❯ }"], + // TODO uncomment tests once #1649 is fixed + /*assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { ┃d } }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: {┃ d } }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: ┃{ d } }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { d ┃} }"], ovec!["val = { abc: ┃❮{ d }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { d┃e } }"], ovec!["val = { abc: ┃❮{ de }❯ }"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = { abc: { d }┃ }"], ovec!["val = ┃❮{ abc: { d } }❯"])?; + assert_ctrl_shift_up_no_inp(ovec!["val = ┃{ abc: { d } }"], ovec!["val = ┃❮{ abc: { d } }❯"])?;*/ + + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: { ┃ } } }"], + ovec!["val = { abc: { de: ┃❮{ }❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: ┃{ } } }"], + ovec!["val = { abc: { de: ┃❮{ }❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: { }┃ } }"], + ovec!["val = { abc: ┃❮{ de: { } }❯ }"], + )?; + + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"┃\" } }"], + ovec!["val = { abc: { de: ┃❮\"\"❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: ┃\"\" } }"], + ovec!["val = { abc: { de: ┃❮\"\"❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"\"┃ } }"], + ovec!["val = { abc: ┃❮{ de: \"\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g┃\" } }"], + ovec!["val = { abc: { de: ┃❮\"f g\"❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de┃: \"f g\" } }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: {┃ de: \"f g\" } }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g\" ┃} }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g\" }┃ }"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = ┃{ abc: { de: \"f g\" } }"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: \"f g\" } }┃"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], + )?; + + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: \"f g┃\" } }"], + ovec!["val = { abc: ┃❮{ de: \"f g\" }❯ }"], 2, )?; - assert_ctrl_shift_up_repeat( - &["{ abc: { de: ┃\"f g\" } }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃\"f g\" } }"], + ovec!["val = ┃❮{ abc: { de: \"f g\" } }❯"], 3, )?; - assert_ctrl_shift_up_repeat( - &["{ abc: { de: ┃\"f g\" } }"], - &["┃❮{ abc: { de: \"f g\" } }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃\"f g\" } }"], + ovec!["┃❮val = { abc: { de: \"f g\" } }❯"], 4, )?; - assert_ctrl_shift_up(&["{ abc: { de: ┃951 } }"], &["{ abc: { de: ┃❮951❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 11┃0 } }"], &["{ abc: { de: ┃❮110❯ } }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 444┃ } }"], &["{ abc: ┃❮{ de: 444 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { de┃: 99 } }"], &["{ abc: ┃❮{ de: 99 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: {┃ de: 0 } }"], &["{ abc: ┃❮{ de: 0 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 230 ┃} }"], &["{ abc: ┃❮{ de: 230 }❯ }"])?; - assert_ctrl_shift_up(&["{ abc: { de: 7 }┃ }"], &["┃❮{ abc: { de: 7 } }❯"])?; - assert_ctrl_shift_up(&["┃{ abc: { de: 1 } }"], &["┃❮{ abc: { de: 1 } }❯"])?; - assert_ctrl_shift_up( - &["{ abc: { de: 111111 } }┃"], - &["┃❮{ abc: { de: 111111 } }❯"], + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: ┃951 } }"], + ovec!["val = { abc: { de: ┃❮951❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 11┃0 } }"], + ovec!["val = { abc: { de: ┃❮110❯ } }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 444┃ } }"], + ovec!["val = { abc: ┃❮{ de: 444 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de┃: 99 } }"], + ovec!["val = { abc: ┃❮{ de: 99 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: {┃ de: 0 } }"], + ovec!["val = { abc: ┃❮{ de: 0 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 230 ┃} }"], + ovec!["val = { abc: ┃❮{ de: 230 }❯ }"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 7 }┃ }"], + ovec!["val = ┃❮{ abc: { de: 7 } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = ┃{ abc: { de: 1 } }"], + ovec!["val = ┃❮{ abc: { de: 1 } }❯"], + )?; + assert_ctrl_shift_up_no_inp( + ovec!["val = { abc: { de: 111111 } }┃"], + ovec!["val = ┃❮{ abc: { de: 111111 } }❯"], )?; - assert_ctrl_shift_up_repeat(&["{ abc: { de: 1┃5 } }"], &["{ abc: ┃❮{ de: 15 }❯ }"], 2)?; - assert_ctrl_shift_up_repeat(&["{ abc: { de: ┃55 } }"], &["┃❮{ abc: { de: 55 } }❯"], 3)?; - assert_ctrl_shift_up_repeat(&["{ abc: { de: ┃400 } }"], &["┃❮{ abc: { de: 400 } }❯"], 4)?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: 1┃5 } }"], + ovec!["val = { abc: ┃❮{ de: 15 }❯ }"], + 2, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃55 } }"], + ovec!["val = ┃❮{ abc: { de: 55 } }❯"], + 3, + )?; + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { abc: { de: ┃400 } }"], + ovec!["┃❮val = { abc: { de: 400 } }❯"], + 5, + )?; - /*assert_ctrl_shift_up_repeat( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - &["{ g: { oi: { ng: { d: ┃❮{ e: { e: { p: { camelCase } } } }❯ } } } }"], + // TODO uncomment tests once #1649 is fixed + /*assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + ovec!["val = { g: { oi: { ng: { d: ┃❮{ e: { e: { p: { camelCase } } } }❯ } } } }"], 4, )?; - assert_ctrl_shift_up_repeat( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - &["{ g: ┃❮{ oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } }❯ }"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + ovec!["val = { g: ┃❮{ oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } }❯ }"], 7, )?; - assert_ctrl_shift_up_repeat( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], - &["┃❮{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }❯"], + assert_ctrl_shift_up_repeat_no_inp( + ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], + ovec!["val = ┃❮{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }❯"], 9, )?;*/ @@ -2002,16 +3017,23 @@ pub mod test_ed_update { // Create ed_model from pre_lines DSL, do handle_new_char() with new_char_seq, select current Expr2, // check if generated tooltips match expected_tooltips. pub fn assert_type_tooltips_seq( - pre_lines: &[&str], - expected_tooltips: &[&str], + pre_lines: Vec, + expected_tooltips: Vec, new_char_seq: &str, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for input_char in new_char_seq.chars() { if input_char == '🡲' { @@ -2024,7 +3046,7 @@ pub mod test_ed_update { for expected_tooltip in expected_tooltips.iter() { ed_model.select_expr()?; - let created_tooltip = ed_model.selected_expr_opt.unwrap().type_str; + let created_tooltip = ed_model.selected_block_opt.unwrap().type_str; assert_eq!( created_tooltip.as_str(ed_model.module.env.pool), @@ -2038,54 +3060,63 @@ pub mod test_ed_update { // Create ed_model from pre_lines DSL, do handle_new_char() with new_char, select current Expr2, // check if generated tooltip matches expected_tooltip. pub fn assert_type_tooltip( - pre_lines: &[&str], + pre_lines: Vec, expected_tooltip: &str, new_char: char, ) -> Result<(), String> { - assert_type_tooltips_seq(pre_lines, &[expected_tooltip], &new_char.to_string()) + assert_type_tooltips_seq(pre_lines, ovec![expected_tooltip], &new_char.to_string()) } - pub fn assert_type_tooltip_clean(lines: &[&str], expected_tooltip: &str) -> Result<(), String> { - assert_type_tooltips_seq(lines, &[expected_tooltip], "") + pub fn assert_type_tooltip_clean( + lines: Vec, + expected_tooltip: &str, + ) -> Result<(), String> { + assert_type_tooltips_seq(lines, ovec![expected_tooltip], "") } // When doing ctrl+shift+up multiple times we select the surrounding expression every time, // every new selection should have the correct tooltip pub fn assert_type_tooltips_clean( - lines: &[&str], - expected_tooltips: &[&str], + lines: Vec, + expected_tooltips: Vec, ) -> Result<(), String> { assert_type_tooltips_seq(lines, expected_tooltips, "") } #[test] fn test_type_tooltip() -> Result<(), String> { - assert_type_tooltip(&["┃"], "{}", '{')?; + assert_type_tooltip_clean(ovec!["val = ┃5"], "Num *")?; + assert_type_tooltip_clean(ovec!["val = 42┃"], "Num *")?; + assert_type_tooltip_clean(ovec!["val = 13┃7"], "Num *")?; - assert_type_tooltip_clean(&["┃5"], "Num *")?; - assert_type_tooltip_clean(&["42┃"], "Num *")?; - assert_type_tooltip_clean(&["13┃7"], "Num *")?; + assert_type_tooltip_clean(ovec!["val = \"┃abc\""], "Str")?; + assert_type_tooltip_clean(ovec!["val = ┃\"abc\""], "Str")?; + assert_type_tooltip_clean(ovec!["val = \"abc\"┃"], "Str")?; - assert_type_tooltip_clean(&["\"┃abc\""], "Str")?; - assert_type_tooltip_clean(&["┃\"abc\""], "Str")?; - assert_type_tooltip_clean(&["\"abc\"┃"], "Str")?; + assert_type_tooltip_clean(ovec!["val = { ┃ }"], "{}")?; + assert_type_tooltip_clean(ovec!["val = { a: \"abc\" }┃"], "{ a : Str }")?; + assert_type_tooltip_clean(ovec!["val = { ┃a: 0 }"], "{ a : Num * }")?; + assert_type_tooltip_clean(ovec!["val = { ┃z: { } }"], "{ z : {} }")?; + assert_type_tooltip_clean(ovec!["val = { camelCase: ┃0 }"], "Num *")?; - assert_type_tooltip_clean(&["{ a: \"abc\" }┃"], "{ a : Str }")?; - assert_type_tooltip_clean(&["{ ┃a: 0 }"], "{ a : Num * }")?; - assert_type_tooltip_clean(&["{ ┃z: { } }"], "{ z : {} }")?; - assert_type_tooltip_clean(&["{ camelCase: ┃0 }"], "Num *")?; + assert_type_tooltips_seq(ovec!["┃"], ovec!["*"], "val=🡲🡲🡲")?; + assert_type_tooltips_seq( + ovec!["┃"], + ovec!["*", "{ a : * }", "{ a : * }"], + "val=🡲🡲🡲{a:", + )?; - assert_type_tooltips_seq(&["┃"], &["*"], "")?; - assert_type_tooltips_seq(&["┃"], &["*", "{ a : * }"], "{a:")?; - - assert_type_tooltips_clean(&["{ camelCase: ┃0 }"], &["Num *", "{ camelCase : Num * }"])?; assert_type_tooltips_clean( - &["{ a: { b: { c: \"hello┃, hello.0123456789ZXY{}[]-><-\" } } }"], - &[ + ovec!["val = { camelCase: ┃0 }"], + ovec!["Num *", "{ camelCase : Num * }"], + )?; + assert_type_tooltips_clean( + ovec!["val = { a: { b: { c: \"hello┃, hello.0123456789ZXY{}[]-><-\" } } }"], + ovec![ "Str", "{ c : Str }", "{ b : { c : Str } }", - "{ a : { b : { c : Str } } }", + "{ a : { b : { c : Str } } }" ], )?; @@ -2094,56 +3125,64 @@ pub mod test_ed_update { #[test] fn test_type_tooltip_list() -> Result<(), String> { - assert_type_tooltip(&["┃"], "List *", '[')?; - assert_type_tooltips_seq(&["┃"], &["List (Num *)"], "[0")?; - assert_type_tooltips_seq(&["┃"], &["List (Num *)", "List (List (Num *))"], "[[0")?; - assert_type_tooltips_seq(&["┃"], &["Str", "List Str"], "[\"a")?; - assert_type_tooltips_seq( - &["┃"], - &[ + assert_type_tooltip_clean(ovec!["val = [ ┃ ]"], "List *")?; + assert_type_tooltips_clean(ovec!["val = [ ┃0 ]"], ovec!["Num *", "List (Num *)"])?; + assert_type_tooltips_clean( + ovec!["val = [ [ ┃0 ] ]"], + ovec!["Num *", "List (Num *)", "List (List (Num *))"], + )?; + + assert_type_tooltips_clean( + ovec!["val = [ [ [ \"ab┃c\" ] ] ]"], + ovec![ "Str", "List Str", "List (List Str)", - "List (List (List Str))", + "List (List (List Str))" ], - "[[[\"a", )?; - assert_type_tooltips_seq( - &["┃"], - &[ + assert_type_tooltips_clean( + ovec!["val = [ [ { a┃: 1 } ] ]"], + ovec![ "{ a : Num * }", "List { a : Num * }", - "List (List { a : Num * })", + "List (List { a : Num * })" ], - "[[{a:1", )?; // multi element lists - assert_type_tooltips_seq(&["┃"], &["List (Num *)"], "[1,2,3")?; - assert_type_tooltips_seq(&["┃"], &["Str", "List Str"], "[\"abc🡲,\"de🡲,\"f")?; - assert_type_tooltips_seq( - &["┃"], - &["{ a : Num * }", "List { a : Num * }"], - "[{a:0🡲🡲,{a:12🡲🡲,{a:444", + assert_type_tooltips_clean(ovec!["val = [ ┃1, 2, 3 ]"], ovec!["Num *", "List (Num *)"])?; + assert_type_tooltips_clean( + ovec!["val = [ \"┃abc\", \"de\", \"f\" ]"], + ovec!["Str", "List Str"], )?; + assert_type_tooltips_clean( + ovec!["val = [ { a:┃ 1 }, { a: 12 }, { a: 444 } ]"], + ovec!["{ a : Num * }", "List { a : Num * }"], + )?; + Ok(()) } #[test] fn test_type_tooltip_mismatch() -> Result<(), String> { - assert_type_tooltips_seq(&["┃"], &["Str", "List "], "[1,\"abc")?; - assert_type_tooltips_seq(&["┃"], &["List "], "[\"abc🡲,50")?; - - assert_type_tooltips_seq( - &["┃"], - &["Str", "{ a : Str }", "List "], - "[{a:0🡲🡲,{a:\"0", + assert_type_tooltips_clean( + ovec!["val = [ 1, \"ab┃c\" ]"], + ovec!["Str", "List "], + )?; + assert_type_tooltips_clean( + ovec!["val = [ \"abc\", 5┃0 ]"], + ovec!["Num *", "List "], )?; - assert_type_tooltips_seq( - &["┃"], - &["List (Num *)", "List (List )"], - "[[0,1,\"2🡲🡲🡲,[3, 4, 5", + assert_type_tooltips_clean( + ovec!["val = [ { a: 0 }, { a: \"0┃\" } ]"], + ovec!["Str", "{ a : Str }", "List "], + )?; + + assert_type_tooltips_clean( + ovec!["val = [ [ 0, 1, \"2\" ], [ 3, 4, 5 ┃] ]"], + ovec!["List (Num *)", "List (List )"], )?; Ok(()) @@ -2155,17 +3194,24 @@ pub mod test_ed_update { // move_caret_fun. Next check if modified ed_model has expected string representation of code, caret position and // active selection. fn assert_ctrl_shift_up_move( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, repeats: usize, move_caret_fun: ModelMoveCaretFun, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for _ in 0..repeats { ed_model.ed_handle_key_down(&ctrl_cmd_shift(), Up)?; @@ -2173,32 +3219,45 @@ pub mod test_ed_update { move_caret_fun(&mut ed_model, &no_mods())?; - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); // remove header for clean tests assert_eq!(post_lines, expected_post_lines); Ok(()) } + fn assert_ctrl_shift_up_move_nls( + pre_lines: Vec, + expected_post_lines: Vec, + repeats: usize, + move_caret_fun: ModelMoveCaretFun, + ) -> Result<(), String> { + assert_ctrl_shift_up_move( + pre_lines, + add_nls(expected_post_lines), + repeats, + move_caret_fun, + ) + } + fn assert_ctrl_shift_single_up_move( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, move_caret_fun: ModelMoveCaretFun, ) -> Result<(), String> { assert_ctrl_shift_up_move(pre_lines, expected_post_lines, 1, move_caret_fun) } + fn assert_ctrl_shift_single_up_move_nls( + pre_lines: Vec, + expected_post_lines: Vec, + move_caret_fun: ModelMoveCaretFun, + ) -> Result<(), String> { + assert_ctrl_shift_single_up_move(pre_lines, add_nls(expected_post_lines), move_caret_fun) + } + // because complex lifetime stuff - macro_rules! move_right { - () => { - |ed_model, modifiers| EdModel::move_caret_right(ed_model, modifiers) - }; - } - macro_rules! move_left { - () => { - |ed_model, modifiers| EdModel::move_caret_left(ed_model, modifiers) - }; - } macro_rules! move_up { () => { |ed_model, modifiers| EdModel::move_caret_up(ed_model, modifiers) @@ -2220,31 +3279,38 @@ pub mod test_ed_update { }; } - #[test] - fn test_ctrl_shift_up_move_blank() -> Result<(), String> { - // Blank is auto-inserted - assert_ctrl_shift_single_up_move(&["┃"], &[" ┃"], move_right!())?; - assert_ctrl_shift_up_move(&["┃"], &["┃ "], 3, move_left!())?; - - Ok(()) - } - #[test] fn test_ctrl_shift_up_move_int() -> Result<(), String> { - assert_ctrl_shift_single_up_move(&["┃0"], &["0┃"], move_down!())?; - assert_ctrl_shift_single_up_move(&["┃9654"], &["┃9654"], move_up!())?; - assert_ctrl_shift_single_up_move(&["┃100546"], &["100546┃"], move_end!())?; + assert_ctrl_shift_single_up_move_nls(ovec!["val = ┃0"], ovec!["val = 0┃"], move_down!())?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃9654"], + ovec!["val = ┃9654"], + move_up!(), + )?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃100546"], + ovec!["val = 100546┃"], + move_end!(), + )?; Ok(()) } #[test] fn test_ctrl_shift_up_move_string() -> Result<(), String> { - assert_ctrl_shift_single_up_move(&["┃\"\""], &["\"\"┃"], move_down!())?; - assert_ctrl_shift_single_up_move(&["┃\"abc\""], &["┃\"abc\""], move_up!())?; - assert_ctrl_shift_single_up_move( - &["┃\"hello, hello.0123456789ZXY{}[]-><-\""], - &["\"hello, hello.0123456789ZXY{}[]-><-\"┃"], + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃\"\""], + ovec!["val = \"\"┃"], + move_down!(), + )?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃\"abc\""], + ovec!["val = ┃\"abc\""], + move_up!(), + )?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃\"hello, hello.0123456789ZXY{}[]-><-\""], + ovec!["val = \"hello, hello.0123456789ZXY{}[]-><-\"┃"], move_end!(), )?; @@ -2253,27 +3319,35 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_move_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - assert_ctrl_shift_single_up_move(&["┃{ }"], &["┃{ }"], move_home!())?; - //assert_ctrl_shift_single_up_move(&["┃{ a }"], &["{ a }┃"], move_down!())?; - //assert_ctrl_shift_single_up_move(&["┃{ a: { b } }"], &["{ a: { b } }┃"], move_right!())?; - assert_ctrl_shift_single_up_move(&["{ a: { ┃ } }"], &["{ a: { } }┃"], move_end!())?; - assert_ctrl_shift_up_move( - &["{ a: { b: { ┃ } } }"], - &["{ a: ┃{ b: { } } }"], + assert_ctrl_shift_single_up_move_nls( + ovec!["val = ┃{ }"], + ovec!["┃val = { }"], + move_home!(), + )?; + // TODO uncomment tests once #1649 is fixed. + //assert_ctrl_shift_single_up_move(ovec!["┃{ a }"], ovec!["{ a }┃"], move_down!())?; + //assert_ctrl_shift_single_up_move(ovec!["┃{ a: { b } }"], ovec!["{ a: { b } }┃"], move_right!())?; + assert_ctrl_shift_single_up_move_nls( + ovec!["val = { a: { ┃ } }"], + ovec!["val = { a: { } }┃"], + move_end!(), + )?; + assert_ctrl_shift_up_move_nls( + ovec!["val = { a: { b: { ┃ } } }"], + ovec!["val = { a: ┃{ b: { } } }"], 2, move_up!(), )?; - assert_ctrl_shift_up_move( - &["{ camelCase: { cC123: \"hello┃, hello.0123456789ZXY{}[]-><-\" } }"], - &["{ camelCase: { cC123: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], + assert_ctrl_shift_up_move_nls( + ovec!["val = { camelCase: { cC123: \"hello┃, hello.0123456789ZXY{}[]-><-\" } }"], + ovec!["val = { camelCase: { cC123: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], 2, move_down!(), )?; - assert_ctrl_shift_up_move( - &["{ camelCase: { cC123: 9┃5 } }"], - &["{ camelCase: { cC123: 95 }┃ }"], + assert_ctrl_shift_up_move_nls( + ovec!["val = { camelCase: { cC123: 9┃5 } }"], + ovec!["val = { camelCase: { cC123: 95 }┃ }"], 2, move_down!(), )?; @@ -2285,16 +3359,23 @@ pub mod test_ed_update { // Next check if modified ed_model has expected string representation of code, caret position and // active selection. fn assert_ctrl_shift_up_backspace( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, repeats: usize, ) -> Result<(), String> { - let test_arena = Bump::new(); - let code_str = BumpString::from_str_in(&pre_lines.join("").replace("┃", ""), &test_arena); + let mut code_str = pre_lines.join("").replace("┃", ""); let mut model_refs = init_model_refs(); + let code_arena = Bump::new(); + let module_ids = ModuleIds::default(); - let mut ed_model = ed_model_from_dsl(&code_str, pre_lines, &mut model_refs)?; + let mut ed_model = ed_model_from_dsl( + &mut code_str, + pre_lines, + &mut model_refs, + &module_ids, + &code_arena, + )?; for _ in 0..repeats { ed_model.ed_handle_key_down(&ctrl_cmd_shift(), Up)?; @@ -2302,34 +3383,41 @@ pub mod test_ed_update { handle_new_char(&'\u{8}', &mut ed_model)?; // \u{8} is the char for backspace on linux - let post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + let mut post_lines = ui_res_to_res(ed_model_to_dsl(&ed_model))?; + strip_header(&mut post_lines); assert_eq!(post_lines, expected_post_lines); Ok(()) } + fn assert_ctrl_shift_up_backspace_nls( + pre_lines: Vec, + expected_post_lines: Vec, + repeats: usize, + ) -> Result<(), String> { + assert_ctrl_shift_up_backspace(pre_lines, add_nls(expected_post_lines), repeats) + } fn assert_ctrl_shift_single_up_backspace( - pre_lines: &[&str], - expected_post_lines: &[&str], + pre_lines: Vec, + expected_post_lines: Vec, ) -> Result<(), String> { assert_ctrl_shift_up_backspace(pre_lines, expected_post_lines, 1) } - #[test] - fn test_ctrl_shift_up_backspace_blank() -> Result<(), String> { - // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["┃"], &["┃ "])?; - - Ok(()) + fn assert_ctrl_shift_single_up_backspace_nls( + pre_lines: Vec, + expected_post_lines: Vec, + ) -> Result<(), String> { + assert_ctrl_shift_single_up_backspace(pre_lines, add_nls(expected_post_lines)) } #[test] fn test_ctrl_shift_up_backspace_int() -> Result<(), String> { // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["95┃21"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["0┃"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["┃10000"], &["┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = 95┃21"], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = 0┃"], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = ┃10000"], ovec!["val = ┃ "])?; Ok(()) } @@ -2337,12 +3425,12 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_backspace_string() -> Result<(), String> { // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["\"┃\""], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["\"\"┃"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["┃\"abc\""], &["┃ "])?; - assert_ctrl_shift_single_up_backspace( - &["\"hello┃, hello.0123456789ZXY{}[]-><-\""], - &["┃ "], + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = \"┃\""], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = \"\"┃"], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = ┃\"abc\""], ovec!["val = ┃ "])?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = \"hello┃, hello.0123456789ZXY{}[]-><-\""], + ovec!["val = ┃ "], )?; Ok(()) @@ -2350,35 +3438,49 @@ pub mod test_ed_update { #[test] fn test_ctrl_shift_up_backspace_record() -> Result<(), String> { - // TODO uncomment tests once editor::lang::constrain::constrain_expr does not contain anymore todo's - // Blank is inserted when root is deleted - assert_ctrl_shift_single_up_backspace(&["{┃ }"], &["┃ "])?; - //assert_ctrl_shift_single_up_backspace(&["{ a┃ }"], &["┃ "])?; - //assert_ctrl_shift_single_up_backspace(&["{ a: { b }┃ }"], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["{ a: \"b cd\"┃ }"], &["┃ "])?; + // Blank is inserted when root of Expr2 is deleted + assert_ctrl_shift_single_up_backspace_nls(ovec!["val = {┃ }"], ovec!["val = ┃ "])?; - //assert_ctrl_shift_single_up_backspace(&["{ a: ┃{ b } }"], &["{ a: ┃ }"])?; - assert_ctrl_shift_single_up_backspace(&["{ a: \"┃b cd\" }"], &["{ a: ┃ }"])?; - assert_ctrl_shift_single_up_backspace(&["{ a: ┃12 }"], &["{ a: ┃ }"])?; + // TODO: uncomment tests, once isue #1649 is fixed + //assert_ctrl_shift_single_up_backspace(ovec!["{ a┃ }"], ovec!["┃ "])?; + //assert_ctrl_shift_single_up_backspace(ovec!["{ a: { b }┃ }"], ovec!["┃ "])?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = { a: \"b cd\"┃ }"], + ovec!["val = ┃ "], + )?; + + //assert_ctrl_shift_single_up_backspace(ovec!["{ a: ┃{ b } }"], ovec!["{ a: ┃ }"])?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = { a: \"┃b cd\" }"], + ovec!["val = { a: ┃ }"], + )?; + assert_ctrl_shift_single_up_backspace_nls( + ovec!["val = { a: ┃12 }"], + ovec!["val = { a: ┃ }"], + )?; /*assert_ctrl_shift_single_up_backspace( - &["{ g: { oi: { ng: { d: { ┃e: { e: { p: { camelCase } } } } } } } }"], - &["{ g: { oi: { ng: { d: ┃ } } } }"], + ovec!["{ g: { oi: { ng: { d: { ┃e: { e: { p: { camelCase } } } } } } } }"], + ovec!["{ g: { oi: { ng: { d: ┃ } } } }"], )?;*/ - assert_ctrl_shift_up_backspace( - &["{ a: { b: { c: \"abc┃ \" } } }"], - &["{ a: { b: ┃ } }"], + assert_ctrl_shift_up_backspace_nls( + ovec!["val = { a: { b: { c: \"abc┃ \" } } }"], + ovec!["val = { a: { b: ┃ } }"], 2, )?; - assert_ctrl_shift_up_backspace( - &["{ a: { b: { c: 100┃000 } } }"], - &["{ a: { b: ┃ } }"], + assert_ctrl_shift_up_backspace_nls( + ovec!["val = { a: { b: { c: 100┃000 } } }"], + ovec!["val = { a: { b: ┃ } }"], + 2, + )?; + assert_ctrl_shift_up_backspace_nls( + ovec!["val = { a: { b: { c: {┃ } } } }"], + ovec!["val = { a: { b: ┃ } }"], 2, )?; - assert_ctrl_shift_up_backspace(&["{ a: { b: { c: {┃ } } } }"], &["{ a: { b: ┃ } }"], 2)?; /*assert_ctrl_shift_up_backspace( - &["{ g: { oi: { ng: { d: { e: { e: { p┃: { camelCase } } } } } } } }"], - &["{ g: ┃ }"], + ovec!["{ g: { oi: { ng: { d: { e: { e: { p┃: { camelCase } } } } } } } }"], + ovec!["{ g: ┃ }"], 6, )?;*/ diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index 1181d110f2..6fee1301f5 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -1,7 +1,7 @@ use super::ed_model::EdModel; use crate::editor::config::Config; use crate::editor::ed_error::EdResult; -use crate::editor::mvc::ed_model::SelectedExpression; +use crate::editor::mvc::ed_model::SelectedBlock; use crate::editor::render_ast::build_code_graphics; use crate::editor::render_debug::build_debug_graphics; use crate::editor::resources::strings::START_TIP; @@ -20,33 +20,49 @@ use winit::dpi::PhysicalSize; #[derive(Debug)] pub struct RenderedWgpu { - pub text_sections: Vec, - pub rects: Vec, + pub text_sections_behind: Vec, // displayed in front of rect_behind, behind everything else + pub text_sections_front: Vec, // displayed in front of everything + pub rects_behind: Vec, // displayed at lowest depth + pub rects_front: Vec, // displayed in front of text_sections_behind, behind text_sections_front } impl RenderedWgpu { pub fn new() -> Self { Self { - text_sections: Vec::new(), - rects: Vec::new(), + text_sections_behind: Vec::new(), + text_sections_front: Vec::new(), + rects_behind: Vec::new(), + rects_front: Vec::new(), } } - pub fn add_text(&mut self, new_text_section: glyph_brush::OwnedSection) { - self.text_sections.push(new_text_section); + pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_behind.push(new_text_section); } - pub fn add_rect(&mut self, new_rect: Rect) { - self.rects.push(new_rect); + pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_front.push(new_text_section); } - pub fn add_rects(&mut self, new_rects: Vec) { - self.rects.extend(new_rects); + pub fn add_rect_behind(&mut self, new_rect: Rect) { + self.rects_behind.push(new_rect); + } + + pub fn add_rects_behind(&mut self, new_rects: Vec) { + self.rects_behind.extend(new_rects); + } + + pub fn add_rect_front(&mut self, new_rect: Rect) { + self.rects_front.push(new_rect); } pub fn extend(&mut self, rendered_wgpu: RenderedWgpu) { - self.text_sections.extend(rendered_wgpu.text_sections); - self.rects.extend(rendered_wgpu.rects); + self.text_sections_behind + .extend(rendered_wgpu.text_sections_behind); + self.text_sections_front + .extend(rendered_wgpu.text_sections_front); + self.rects_behind.extend(rendered_wgpu.rects_behind); + self.rects_front.extend(rendered_wgpu.rects_front); } } @@ -61,7 +77,10 @@ pub fn model_to_wgpu<'a>( let mut all_rendered = RenderedWgpu::new(); - let tip_txt_coords = (txt_coords.x, txt_coords.y - 4.0 * config.code_font_size); + let tip_txt_coords = ( + txt_coords.x, + txt_coords.y - (START_TIP.matches('\n').count() as f32 + 1.0) * config.code_font_size, + ); let start_tip_text = owned_section_from_text(&Text { position: tip_txt_coords.into(), @@ -72,15 +91,15 @@ pub fn model_to_wgpu<'a>( ..Default::default() }); - all_rendered.add_text(start_tip_text); + all_rendered.add_text_behind(start_tip_text); let rendered_code_graphics = build_code_graphics( - ed_model.markup_node_pool.get(ed_model.markup_root_id), + &ed_model.markup_ids, size, txt_coords, config, glyph_dim_rect, - &ed_model.markup_node_pool, + &ed_model.mark_node_pool, )?; all_rendered.extend(rendered_code_graphics); @@ -93,7 +112,7 @@ pub fn model_to_wgpu<'a>( let rendered_selection = build_selection_graphics( caret_w_sel_vec, - &ed_model.selected_expr_opt, + &ed_model.selected_block_opt, txt_coords, config, glyph_dim_rect, @@ -103,7 +122,7 @@ pub fn model_to_wgpu<'a>( all_rendered.extend(rendered_selection); if ed_model.show_debug_view { - all_rendered.add_text(build_debug_graphics(size, txt_coords, config, ed_model)?); + all_rendered.add_text_behind(build_debug_graphics(size, txt_coords, config, ed_model)?); } Ok(all_rendered) @@ -111,7 +130,7 @@ pub fn model_to_wgpu<'a>( pub fn build_selection_graphics( caret_w_select_vec: Vec, - selected_expr_opt: &Option, + selected_expr_opt: &Option, txt_coords: Vector2, config: &Config, glyph_dim_rect: Rect, @@ -139,7 +158,7 @@ pub fn build_selection_graphics( let width = ((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_width); - all_rendered.add_rect(make_selection_rect( + all_rendered.add_rect_behind(make_selection_rect( sel_rect_x, sel_rect_y, width, @@ -158,12 +177,12 @@ pub fn build_selection_graphics( let (tip_rect, tip_text) = tooltip.render_tooltip(&glyph_dim_rect, &config.ed_theme.ui_theme); - all_rendered.add_rect(tip_rect); - all_rendered.add_text(tip_text); + all_rendered.add_rect_front(tip_rect); + all_rendered.add_text_front(tip_text); } } - all_rendered.add_rect(make_caret_rect( + all_rendered.add_rect_front(make_caret_rect( top_left_x, top_left_y, &glyph_dim_rect, diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index af6eb80f84..237da57d86 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -25,6 +25,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult EdResult EdResult EdResult { update_small_int_num(number, &content_str)?; diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs new file mode 100644 index 0000000000..697531bb5a --- /dev/null +++ b/editor/src/editor/mvc/let_update.rs @@ -0,0 +1,185 @@ +use roc_module::symbol::Symbol; + +use crate::editor::ed_error::EdResult; +use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::markup::common_nodes::new_equals_mn; +use crate::editor::markup::nodes::MarkupNode; +use crate::editor::mvc::app_update::InputOutcome; +use crate::editor::mvc::ed_model::EdModel; +use crate::editor::mvc::ed_update::get_node_context; +use crate::editor::mvc::ed_update::NodeContext; +use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::{Expr2, ValueDef}; +use crate::lang::parse::ASTNodeId; +use crate::lang::pattern::Pattern2; + +pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node, + parent_id_opt, + ast_node_id, + } = get_node_context(ed_model)?; + + let is_blank_node = curr_mark_node.is_blank(); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); + + let val_name_string = new_char.to_string(); + // safe unwrap because our ArrString has a 30B capacity + let val_expr2_node = Expr2::Blank; + let val_expr_id = ed_model.module.env.pool.add(val_expr2_node); + + let ident_id = ed_model + .module + .env + .ident_ids + .add(val_name_string.clone().into()); + let var_symbol = Symbol::new(ed_model.module.env.home, ident_id); + let body = Expr2::Var(var_symbol); + let body_id = ed_model.module.env.pool.add(body); + + let pattern = Pattern2::Identifier(var_symbol); + let pattern_id = ed_model.module.env.pool.add(pattern); + + let value_def = ValueDef::NoAnnotation { + pattern_id, + expr_id: val_expr_id, + expr_var: ed_model.module.env.var_store.fresh(), + }; + let def_id = ed_model.module.env.pool.add(value_def); + + let expr2_node = Expr2::LetValue { + def_id, + body_id, + body_var: ed_model.module.env.var_store.fresh(), + }; + + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, expr2_node); + + let val_name_mark_node = MarkupNode::Text { + content: val_name_string, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: Some(curr_mark_node_id), + newlines_at_end: curr_mark_node_nls, + }; + + let val_name_mn_id = ed_model.add_mark_node(val_name_mark_node); + + let equals_mn_id = ed_model.add_mark_node(new_equals_mn(ast_node_id, Some(curr_mark_node_id))); + + let body_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls( + ASTNodeId::AExprId(val_expr_id), + Some(curr_mark_node_id), + 1, + )); + + let val_mark_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], + parent_id_opt, + newlines_at_end: 1, + }; + + if is_blank_node { + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, val_mark_node); + + // remove data corresponding to Blank node + ed_model.del_blank_expr_node(old_caret_pos)?; + + let char_len = 1; + ed_model.simple_move_carets_right(char_len); + + // update GridNodeMap and CodeLines + ed_model.insert_all_between_line( + old_caret_pos.line, + old_caret_pos.column, + &[val_name_mn_id, equals_mn_id, body_mn_id], + )?; + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } +} + +// TODO reenable this for updating non-top level value defs +/* +pub fn update_let_value( + val_name_mn_id: MarkNodeId, + def_id: NodeId, + body_id: NodeId, + ed_model: &mut EdModel, + new_char: &char, +) -> EdResult { + if new_char.is_ascii_alphanumeric() { + let old_caret_pos = ed_model.get_caret(); + + // update markup + let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id); + let content_str_mut = val_name_mn_mut.get_content_mut()?; + + let old_val_name = content_str_mut.clone(); + + let node_caret_offset = ed_model + .grid_node_map + .get_offset_to_node_id(old_caret_pos, val_name_mn_id)?; + + if node_caret_offset <= content_str_mut.len() { + content_str_mut.insert(node_caret_offset, *new_char); + + // update ast + let value_def = ed_model.module.env.pool.get(def_id); + let value_ident_pattern_id = value_def.get_pattern_id(); + + // TODO no unwrap + let ident_id = ed_model + .module + .env + .ident_ids + .update_key(&old_val_name, content_str_mut) + .unwrap(); + + let new_var_symbol = Symbol::new(ed_model.module.env.home, ident_id); + + ed_model + .module + .env + .pool + .set(value_ident_pattern_id, Pattern2::Identifier(new_var_symbol)); + + ed_model + .module + .env + .pool + .set(body_id, Expr2::Var(new_var_symbol)); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + &new_char.to_string(), + val_name_mn_id, + )?; + + // update caret + ed_model.simple_move_carets_right(1); + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } + } else { + Ok(InputOutcome::Ignored) + } +} +*/ diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 56aed8c6e9..65e6fce10b 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,6 +1,8 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; -use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::common_nodes::{ + new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, +}; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; @@ -8,9 +10,9 @@ use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::Expr2; -use crate::lang::ast::{expr2_to_string, ExprId}; +use crate::lang::ast::ExprId; +use crate::lang::ast::{ast_node_to_string, Expr2}; +use crate::lang::parse::ASTNodeId; use crate::lang::pool::PoolVec; use crate::ui::text::text_pos::TextPos; @@ -24,63 +26,62 @@ pub fn start_new_list(ed_model: &mut EdModel) -> EdResult { } = get_node_context(ed_model)?; let is_blank_node = curr_mark_node.is_blank(); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); let expr2_node = Expr2::List { elem_var: ed_model.module.env.var_store.fresh(), elems: PoolVec::empty(ed_model.module.env.pool), }; - let mark_node_pool = &mut ed_model.markup_node_pool; + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, expr2_node); - ed_model.module.env.pool.set(ast_node_id, expr2_node); + let left_bracket_node_id = ed_model.add_mark_node(new_left_square_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); - let left_bracket_node = MarkupNode::Text { - content: nodes::LEFT_SQUARE_BR.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let left_bracket_node_id = mark_node_pool.add(left_bracket_node); - - let right_bracket_node = MarkupNode::Text { - content: nodes::RIGHT_SQUARE_BR.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let right_bracket_node_id = mark_node_pool.add(right_bracket_node); + let right_bracket_node_id = ed_model.add_mark_node(new_right_square_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); let nested_node = MarkupNode::Nested { ast_node_id, children_ids: vec![left_bracket_node_id, right_bracket_node_id], parent_id_opt, + newlines_at_end: curr_mark_node_nls, }; if is_blank_node { - mark_node_pool.replace_node(curr_mark_node_id, nested_node); + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, nested_node); - // remove data corresponding to Blank node - ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; + ed_model.del_blank_expr_node(old_caret_pos)?; ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, nodes::LEFT_SQUARE_BR, left_bracket_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column + nodes::LEFT_SQUARE_BR.len(), nodes::RIGHT_SQUARE_BR, right_bracket_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(InputOutcome::Accepted) @@ -105,10 +106,10 @@ pub fn add_blank_child( let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt { - let parent = ed_model.markup_node_pool.get(parent_id); + let parent = ed_model.mark_node_pool.get(parent_id); let list_ast_node_id = parent.get_ast_node_id(); - let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id); + let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id.to_expr_id()?); match list_ast_node { Expr2::List { @@ -118,11 +119,11 @@ pub fn add_blank_child( let blank_elt = Expr2::Blank; let blank_elt_id = ed_model.module.env.pool.add(blank_elt); - Ok((blank_elt_id, list_ast_node_id, parent_id)) + Ok((blank_elt_id, list_ast_node_id.to_expr_id()?, parent_id)) } _ => UnexpectedASTNode { required_node_type: "List".to_string(), - encountered_node_type: expr2_to_string(ast_node_id, ed_model.module.env.pool), + encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool), } .fail(), } @@ -159,7 +160,7 @@ pub fn add_blank_child( } _ => UnexpectedASTNode { required_node_type: "List".to_string(), - encountered_node_type: expr2_to_string(ast_node_id, ed_model.module.env.pool), + encountered_node_type: ast_node_to_string(ast_node_id, ed_model.module.env.pool), } .fail(), }?; @@ -173,7 +174,7 @@ pub fn add_blank_child( ed_model, )?; - let parent = ed_model.markup_node_pool.get_mut(parent_id); + let parent = ed_model.mark_node_pool.get_mut(parent_id); for (indx, child) in new_mark_children.iter().enumerate() { parent.add_child_at_index(new_child_index + indx, *child)?; @@ -191,35 +192,26 @@ pub fn update_mark_children( parent_id_opt: Option, ed_model: &mut EdModel, ) -> EdResult> { - let blank_mark_node = MarkupNode::Blank { - ast_node_id: blank_elt_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + let blank_mark_node_id = ed_model.add_mark_node(new_blank_mn( + ASTNodeId::AExprId(blank_elt_id), parent_id_opt, - }; - - let blank_mark_node_id = ed_model.markup_node_pool.add(blank_mark_node); + )); let mut children: Vec = vec![]; if new_child_index > 1 { - let comma_mark_node = MarkupNode::Text { - content: nodes::COMMA.to_owned(), - ast_node_id: list_ast_node_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - }; - - let comma_mark_node_id = ed_model.markup_node_pool.add(comma_mark_node); + let comma_mark_node_id = + ed_model.add_mark_node(new_comma_mn(list_ast_node_id, parent_id_opt)); ed_model.simple_move_carets_right(nodes::COMMA.len()); - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, nodes::COMMA, comma_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; children.push(comma_mark_node_id); @@ -234,11 +226,13 @@ pub fn update_mark_children( }; // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column + comma_shift, nodes::BLANK_PLACEHOLDER, blank_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(children) diff --git a/editor/src/editor/mvc/lookup_update.rs b/editor/src/editor/mvc/lookup_update.rs index 972f6e3a7e..03a4905942 100644 --- a/editor/src/editor/mvc/lookup_update.rs +++ b/editor/src/editor/mvc/lookup_update.rs @@ -10,7 +10,7 @@ pub fn update_invalid_lookup( input_str: &str, old_pool_str: &PoolStr, curr_mark_node_id: MarkNodeId, - ast_node_id: ExprId, + expr_id: ExprId, ed_model: &mut EdModel, ) -> EdResult { if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) { @@ -32,10 +32,10 @@ pub fn update_invalid_lookup( .module .env .pool - .set(ast_node_id, Expr2::InvalidLookup(new_pool_str)); + .set(expr_id, Expr2::InvalidLookup(new_pool_str)); // update MarkupNode - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; content_str_mut.insert_str(caret_offset, input_str); @@ -43,11 +43,13 @@ pub fn update_invalid_lookup( ed_model.simple_move_carets_right(input_str.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, input_str, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(InputOutcome::Accepted) diff --git a/editor/src/editor/mvc/mod.rs b/editor/src/editor/mvc/mod.rs index 4088a4a1ad..5b4a11ad9a 100644 --- a/editor/src/editor/mvc/mod.rs +++ b/editor/src/editor/mvc/mod.rs @@ -1,10 +1,13 @@ pub mod app_model; pub mod app_update; +mod break_line; pub mod ed_model; pub mod ed_update; pub mod ed_view; mod int_update; +mod let_update; mod list_update; mod lookup_update; mod record_update; mod string_update; +pub mod tld_value_update; diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 628028953d..0b8c911ab6 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -2,6 +2,9 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::RecordWithoutFields; use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::common_nodes::new_blank_mn; +use crate::editor::markup::common_nodes::new_left_accolade_mn; +use crate::editor::markup::common_nodes::new_right_accolade_mn; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; @@ -12,6 +15,7 @@ use crate::editor::slow_pool::MarkNodeId; use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; use crate::lang::ast::{Expr2, ExprId, RecordField}; +use crate::lang::parse::ASTNodeId; use crate::lang::pool::{PoolStr, PoolVec}; use crate::ui::text::text_pos::TextPos; use snafu::OptionExt; @@ -26,61 +30,44 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult { } = get_node_context(ed_model)?; let is_blank_node = curr_mark_node.is_blank(); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); let ast_pool = &mut ed_model.module.env.pool; let expr2_node = Expr2::EmptyRecord; - let mark_node_pool = &mut ed_model.markup_node_pool; + ast_pool.set(ast_node_id.to_expr_id()?, expr2_node); - ast_pool.set(ast_node_id, expr2_node); + let left_bracket_node_id = ed_model.add_mark_node(new_left_accolade_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); - let left_bracket_node = MarkupNode::Text { - content: nodes::LEFT_ACCOLADE.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let left_bracket_node_id = mark_node_pool.add(left_bracket_node); - - let right_bracket_node = MarkupNode::Text { - content: nodes::RIGHT_ACCOLADE.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one - }; - - let right_bracket_node_id = mark_node_pool.add(right_bracket_node); + let right_bracket_node_id = ed_model.add_mark_node(new_right_accolade_mn( + ast_node_id.to_expr_id()?, + Some(curr_mark_node_id), + )); let nested_node = MarkupNode::Nested { ast_node_id, children_ids: vec![left_bracket_node_id, right_bracket_node_id], parent_id_opt, + newlines_at_end: curr_mark_node_nls, }; if is_blank_node { - mark_node_pool.replace_node(curr_mark_node_id, nested_node); + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, nested_node); - // remove data corresponding to Blank node - ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; + ed_model.del_blank_expr_node(old_caret_pos)?; ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + ed_model.insert_all_between_line( old_caret_pos.line, old_caret_pos.column, - nodes::LEFT_ACCOLADE, - left_bracket_node_id, - )?; - - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column + nodes::LEFT_ACCOLADE.len(), - nodes::RIGHT_ACCOLADE, - right_bracket_node_id, + &[left_bracket_node_id, right_bracket_node_id], )?; Ok(InputOutcome::Accepted) @@ -100,7 +87,7 @@ pub fn update_empty_record( if input_chars.all(|ch| ch.is_ascii_alphabetic()) && input_chars.all(|ch| ch.is_ascii_lowercase()) { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); let NodeContext { old_caret_pos, @@ -110,8 +97,8 @@ pub fn update_empty_record( ast_node_id, } = get_node_context(ed_model)?; - if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE - && curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE + if prev_mark_node.get_content() == nodes::LEFT_ACCOLADE + && curr_mark_node.get_content() == nodes::RIGHT_ACCOLADE { // update AST let record_var = ed_model.module.env.var_store.fresh(); @@ -124,7 +111,11 @@ pub fn update_empty_record( let new_ast_node = Expr2::Record { record_var, fields }; - ed_model.module.env.pool.set(ast_node_id, new_ast_node); + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, new_ast_node); // update Markup @@ -134,12 +125,13 @@ pub fn update_empty_record( syn_high_style: HighlightStyle::RecordField, attributes: Attributes::new(), parent_id_opt, + newlines_at_end: 0, }; - let record_field_node_id = ed_model.markup_node_pool.add(record_field_node); + let record_field_node_id = ed_model.add_mark_node(record_field_node); if let Some(parent_id) = parent_id_opt { - let parent = ed_model.markup_node_pool.get_mut(parent_id); + let parent = ed_model.mark_node_pool.get_mut(parent_id); let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; @@ -155,11 +147,13 @@ pub fn update_empty_record( ed_model.simple_move_carets_right(1); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, new_input, record_field_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; Ok(InputOutcome::Accepted) @@ -183,118 +177,114 @@ pub fn update_record_colon( ast_node_id, } = get_node_context(ed_model)?; if let Some(parent_id) = parent_id_opt { - let curr_ast_node = ed_model.module.env.pool.get(ast_node_id); + let curr_ast_node = ed_model.module.env.pool.get(ast_node_id.to_expr_id()?); let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; if let Some(prev_mark_node_id) = prev_mark_node_id_opt { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); - let prev_ast_node = ed_model - .module - .env - .pool - .get(prev_mark_node.get_ast_node_id()); + match prev_mark_node.get_ast_node_id() { + ASTNodeId::ADefId(_) => Ok(InputOutcome::Ignored), + ASTNodeId::AExprId(prev_expr_id) => { + let prev_expr = ed_model.module.env.pool.get(prev_expr_id); - // current and prev node should always point to record when in valid position to add ':' - if matches!(prev_ast_node, Expr2::Record { .. }) - && matches!(curr_ast_node, Expr2::Record { .. }) - { - let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); + // current and prev node should always point to record when in valid position to add ':' + if matches!(prev_expr, Expr2::Record { .. }) + && matches!(curr_ast_node, Expr2::Record { .. }) + { + let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.mark_node_pool); - let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; + let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; - let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id); + let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id); - match ast_node_ref { - Expr2::Record { - record_var: _, - fields, - } => { - if ed_model.node_exists_at_caret() { - let next_mark_node_id = - ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; - let next_mark_node = ed_model.markup_node_pool.get(next_mark_node_id); - if next_mark_node.get_content()? == nodes::RIGHT_ACCOLADE { - // update AST node - let new_field_val = Expr2::Blank; - let new_field_val_id = ed_model.module.env.pool.add(new_field_val); + match ast_node_ref { + Expr2::Record { + record_var: _, + fields, + } => { + if ed_model.node_exists_at_caret() { + let next_mark_node_id = + ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; + let next_mark_node = + ed_model.mark_node_pool.get(next_mark_node_id); + if next_mark_node.get_content() == nodes::RIGHT_ACCOLADE { + // update AST node + let new_field_val = Expr2::Blank; + let new_field_val_id = + ed_model.module.env.pool.add(new_field_val); - let first_field_mut = fields - .iter_mut(ed_model.module.env.pool) - .next() - .with_context(|| RecordWithoutFields {})?; + let first_field_mut = fields + .iter_mut(ed_model.module.env.pool) + .next() + .with_context(|| RecordWithoutFields {})?; - *first_field_mut = RecordField::LabeledValue( - *first_field_mut.get_record_field_pool_str(), - *first_field_mut.get_record_field_var(), - new_field_val_id, - ); + *first_field_mut = RecordField::LabeledValue( + *first_field_mut.get_record_field_pool_str(), + *first_field_mut.get_record_field_var(), + new_field_val_id, + ); - // update Markup - let record_colon = nodes::COLON; + // update Markup + let record_colon = nodes::COLON; - let record_colon_node = MarkupNode::Text { - content: record_colon.to_owned(), - ast_node_id: record_ast_node_id, - syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), - parent_id_opt: Some(parent_id), - }; + let record_colon_node = MarkupNode::Text { + content: record_colon.to_owned(), + ast_node_id: ASTNodeId::AExprId(record_ast_node_id), + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt: Some(parent_id), + newlines_at_end: 0, + }; - let record_colon_node_id = - ed_model.markup_node_pool.add(record_colon_node); - ed_model - .markup_node_pool - .get_mut(parent_id) - .add_child_at_index(new_child_index, record_colon_node_id)?; + let record_colon_node_id = + ed_model.add_mark_node(record_colon_node); + ed_model + .mark_node_pool + .get_mut(parent_id) + .add_child_at_index( + new_child_index, + record_colon_node_id, + )?; - let record_blank_node = MarkupNode::Blank { - ast_node_id: new_field_val_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt: Some(parent_id), - }; + let record_blank_node_id = + ed_model.add_mark_node(new_blank_mn( + ASTNodeId::AExprId(new_field_val_id), + Some(parent_id), + )); - let record_blank_node_id = - ed_model.markup_node_pool.add(record_blank_node); - ed_model - .markup_node_pool - .get_mut(parent_id) - .add_child_at_index( - new_child_index + 1, - record_blank_node_id, - )?; + ed_model + .mark_node_pool + .get_mut(parent_id) + .add_child_at_index( + new_child_index + 1, + record_blank_node_id, + )?; - // update caret - ed_model.simple_move_carets_right(record_colon.len()); + // update caret + ed_model.simple_move_carets_right(record_colon.len()); - // update GridNodeMap and CodeLines - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column, - nodes::COLON, - record_colon_node_id, - )?; + // update GridNodeMap and CodeLines + ed_model.insert_all_between_line( + old_caret_pos.line, + old_caret_pos.column, + &[record_colon_node_id, record_blank_node_id], + )?; - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column + nodes::COLON.len(), - nodes::BLANK_PLACEHOLDER, - record_blank_node_id, - )?; - - Ok(InputOutcome::Accepted) - } else { - Ok(InputOutcome::Ignored) + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } + } else { + Ok(InputOutcome::Ignored) + } } - } else { - Ok(InputOutcome::Ignored) + _ => Ok(InputOutcome::Ignored), } + } else { + Ok(InputOutcome::Ignored) } - _ => Ok(InputOutcome::Ignored), } - } else { - Ok(InputOutcome::Ignored) } } else { Ok(InputOutcome::Ignored) @@ -315,7 +305,7 @@ pub fn update_record_field( ed_model: &mut EdModel, ) -> EdResult { // update MarkupNode - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; let node_caret_offset = ed_model .grid_node_map @@ -337,11 +327,13 @@ pub fn update_record_field( ed_model.simple_move_carets_right(new_input.len()); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, new_input, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; // update AST Node diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index b0145804ac..2c35ca89fd 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -7,6 +7,7 @@ use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::update_str_expr; use crate::lang::ast::ArrString; use crate::lang::ast::Expr2; use crate::lang::pool::PoolStr; @@ -27,7 +28,7 @@ pub fn update_small_string( let new_input = &new_char.to_string(); // update markup - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; let node_caret_offset = ed_model .grid_node_map @@ -36,7 +37,7 @@ pub fn update_small_string( if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { if old_array_str.len() < ArrString::capacity() { if let Expr2::SmallStr(ref mut mut_array_str) = - ed_model.module.env.pool.get_mut(ast_node_id) + ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?) { // safe because we checked the length unsafe { @@ -51,17 +52,23 @@ pub fn update_small_string( let new_ast_node = Expr2::Str(PoolStr::new(&new_str, ed_model.module.env.pool)); - ed_model.module.env.pool.set(ast_node_id, new_ast_node); + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, new_ast_node); } content_str_mut.insert_str(node_caret_offset, new_input); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, new_input, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; // update caret @@ -73,11 +80,7 @@ pub fn update_small_string( } } -pub fn update_string( - new_input: &str, - old_pool_str: &PoolStr, - ed_model: &mut EdModel, -) -> EdResult { +pub fn update_string(new_char: char, ed_model: &mut EdModel) -> EdResult { let NodeContext { old_caret_pos, curr_mark_node_id, @@ -87,31 +90,32 @@ pub fn update_string( } = get_node_context(ed_model)?; // update markup - let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); + let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id); let content_str_mut = curr_mark_node_mut.get_content_mut()?; let node_caret_offset = ed_model .grid_node_map .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { - content_str_mut.insert_str(node_caret_offset, new_input); + content_str_mut.insert(node_caret_offset, new_char); // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, - new_input, + &new_char.to_string(), curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; // update ast - let mut new_string = old_pool_str.as_str(ed_model.module.env.pool).to_owned(); - new_string.push_str(new_input); - - let new_pool_str = PoolStr::new(&new_string, &mut ed_model.module.env.pool); - let new_ast_node = Expr2::Str(new_pool_str); - - ed_model.module.env.pool.set(ast_node_id, new_ast_node); + update_str_expr( + ast_node_id.to_expr_id()?, + new_char, + node_caret_offset - 1, // -1 because offset was calculated with quotes + &mut ed_model.module.env.pool, + )?; // update caret ed_model.simple_move_carets_right(1); @@ -133,8 +137,13 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { if curr_mark_node.is_blank() { let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new()); + let curr_mark_node_nls = curr_mark_node.get_newlines_at_end(); - ed_model.module.env.pool.set(ast_node_id, new_expr2_node); + ed_model + .module + .env + .pool + .set(ast_node_id.to_expr_id()?, new_expr2_node); let new_string_node = MarkupNode::Text { content: nodes::STRING_QUOTES.to_owned(), @@ -142,21 +151,24 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { syn_high_style: HighlightStyle::String, attributes: Attributes::new(), parent_id_opt, + newlines_at_end: curr_mark_node_nls, }; ed_model - .markup_node_pool + .mark_node_pool .replace_node(curr_mark_node_id, new_string_node); // remove data corresponding to Blank node - ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; + ed_model.del_blank_expr_node(old_caret_pos)?; // update GridNodeMap and CodeLines - ed_model.insert_between_line( + EdModel::insert_between_line( old_caret_pos.line, old_caret_pos.column, nodes::STRING_QUOTES, curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, )?; ed_model.simple_move_carets_right(1); diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs new file mode 100644 index 0000000000..8cdb85feba --- /dev/null +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -0,0 +1,206 @@ +use roc_module::symbol::{Interns, Symbol}; + +use crate::{ + editor::{ + ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, + markup::{ + attribute::Attributes, + common_nodes::{new_blank_mn_w_nls, new_equals_mn}, + nodes::{set_parent_for_all, MarkupNode}, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, + }, + lang::{ + ast::{Def2, Expr2}, + expr::Env, + parse::ASTNodeId, + pattern::{get_identifier_string, Pattern2}, + pool::NodeId, + }, + ui::text::text_pos::TextPos, +}; + +use super::{ + app_update::InputOutcome, + ed_model::EdModel, + ed_update::{get_node_context, NodeContext}, +}; + +// Top Level Defined Value. example: `main = "Hello, World!"` + +pub fn tld_mark_node<'a>( + identifier_id: NodeId, + expr_mark_node_id: MarkNodeId, + ast_node_id: ASTNodeId, + mark_node_pool: &mut SlowPool, + env: &Env<'a>, + interns: &Interns, +) -> EdResult { + let pattern2 = env.pool.get(identifier_id); + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id], + parent_id_opt: None, + newlines_at_end: 2, + }; + + Ok(full_let_node) +} + +pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node: _, + parent_id_opt: _, + ast_node_id, + } = get_node_context(ed_model)?; + + let val_expr_node = Expr2::Blank; + let val_expr_id = ed_model.module.env.pool.add(val_expr_node); + + let val_expr_mn = new_blank_mn_w_nls(ASTNodeId::AExprId(val_expr_id), None, 0); + let val_expr_mn_id = ed_model.mark_node_pool.add(val_expr_mn); + + let val_name_string = new_char.to_string(); + + let ident_id = ed_model + .module + .env + .ident_ids + .add(val_name_string.clone().into()); + + let module_ident_ids_opt = ed_model + .loaded_module + .interns + .all_ident_ids + .get_mut(&ed_model.module.env.home); + + if let Some(module_ident_ids_ref) = module_ident_ids_opt { + // this might create different IdentId for interns and env.ident_ids which may be a problem + module_ident_ids_ref.add(val_name_string.into()); + } else { + KeyNotFound { + key_str: format!("{:?}", ed_model.module.env.home), + } + .fail()? + } + + let val_symbol = Symbol::new(ed_model.module.env.home, ident_id); + + let patt2 = Pattern2::Identifier(val_symbol); + let patt2_id = ed_model.module.env.pool.add(patt2); + + let tld_mark_node = tld_mark_node( + patt2_id, + val_expr_mn_id, + ast_node_id, + &mut ed_model.mark_node_pool, + &ed_model.module.env, + &ed_model.loaded_module.interns, + )?; + + let new_ast_node = Def2::ValueDef { + identifier_id: patt2_id, + expr_id: val_expr_id, + }; + + ed_model + .module + .env + .pool + .set(ast_node_id.to_def_id()?, new_ast_node); + + ed_model + .mark_node_pool + .replace_node(curr_mark_node_id, tld_mark_node); + + set_parent_for_all(curr_mark_node_id, &mut ed_model.mark_node_pool); + + // remove data corresponding to old Blank node + ed_model.del_line(old_caret_pos.line + 1)?; + ed_model.del_line(old_caret_pos.line)?; + + let char_len = 1; + ed_model.simple_move_carets_right(char_len); + + let mut curr_line = old_caret_pos.line; + let mut curr_column = old_caret_pos.column; + + EdModel::insert_mark_node_between_line( + &mut curr_line, + &mut curr_column, + curr_mark_node_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, + &ed_model.mark_node_pool, + )?; + + Ok(InputOutcome::Accepted) +} + +pub fn update_tld_val_name( + val_name_mn_id: MarkNodeId, + old_caret_pos: TextPos, + ed_model: &mut EdModel, + new_char: &char, +) -> EdResult { + if new_char.is_ascii_alphanumeric() { + // update markup + let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id); + let content_str_mut = val_name_mn_mut.get_content_mut()?; + + let old_val_name = content_str_mut.clone(); + + let node_caret_offset = ed_model + .grid_node_map + .get_offset_to_node_id(old_caret_pos, val_name_mn_id)?; + + if node_caret_offset <= content_str_mut.len() { + content_str_mut.insert(node_caret_offset, *new_char); + + let update_val_name_res = ed_model + .module + .env + .ident_ids + .update_key(&old_val_name, content_str_mut); + + if let Err(err_str) = update_val_name_res { + FailedToUpdateIdentIdName { err_str }.fail()?; + } + + EdModel::insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + &new_char.to_string(), + val_name_mn_id, + &mut ed_model.grid_node_map, + &mut ed_model.code_lines, + )?; + + ed_model.simple_move_caret_right(old_caret_pos, 1); + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } + } else { + Ok(InputOutcome::Ignored) + } +} diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index e7b01ab55b..3fa42bc1de 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,4 +1,5 @@ use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; +use super::slow_pool::MarkNodeId; use crate::editor::mvc::ed_view::RenderedWgpu; use crate::editor::slow_pool::SlowPool; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; @@ -10,37 +11,49 @@ use winit::dpi::PhysicalSize; use crate::{editor::config::Config, graphics::colors}; pub fn build_code_graphics<'a>( - markup_node: &'a MarkupNode, + markup_ids: &[MarkNodeId], size: &PhysicalSize, txt_coords: Vector2, config: &Config, glyph_dim_rect: Rect, - markup_node_pool: &'a SlowPool, + mark_node_pool: &'a SlowPool, ) -> EdResult { let area_bounds = (size.width as f32, size.height as f32); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); let mut rendered_wgpu = RenderedWgpu::new(); - let (glyph_text_vec, rects) = markup_to_wgpu( - markup_node, - &CodeStyle { - ed_theme: &config.ed_theme, - font_size: config.code_font_size, - txt_coords, - glyph_dim_rect, - }, - markup_node_pool, - )?; + let mut all_glyph_text_vec = vec![]; + let mut all_rects = vec![]; + let mut txt_row_col = (0, 0); + + for markup_id in markup_ids.iter() { + let mark_node = mark_node_pool.get(*markup_id); + + let (mut glyph_text_vec, mut rects) = markup_to_wgpu( + mark_node, + &CodeStyle { + ed_theme: &config.ed_theme, + font_size: config.code_font_size, + txt_coords, + glyph_dim_rect, + }, + &mut txt_row_col, + mark_node_pool, + )?; + + all_glyph_text_vec.append(&mut glyph_text_vec); + all_rects.append(&mut rects) + } let section = gr_text::owned_section_from_glyph_texts( - glyph_text_vec, + all_glyph_text_vec, txt_coords.into(), area_bounds, layout, ); - rendered_wgpu.add_rects(rects); - rendered_wgpu.add_text(section); + rendered_wgpu.add_rects_behind(all_rects); // currently only rects for Blank + rendered_wgpu.add_text_behind(section); Ok(rendered_wgpu) } @@ -55,51 +68,57 @@ struct CodeStyle<'a> { fn markup_to_wgpu<'a>( markup_node: &'a MarkupNode, code_style: &CodeStyle, - markup_node_pool: &'a SlowPool, + txt_row_col: &mut (usize, usize), + mark_node_pool: &'a SlowPool, ) -> EdResult<(Vec, Vec)> { let mut wgpu_texts: Vec = Vec::new(); let mut rects: Vec = Vec::new(); - let mut txt_row_col = (0, 0); - markup_to_wgpu_helper( markup_node, &mut wgpu_texts, &mut rects, code_style, - &mut txt_row_col, - markup_node_pool, + txt_row_col, + mark_node_pool, )?; Ok((wgpu_texts, rects)) } -// TODO use text_row fn markup_to_wgpu_helper<'a>( markup_node: &'a MarkupNode, wgpu_texts: &mut Vec, rects: &mut Vec, code_style: &CodeStyle, txt_row_col: &mut (usize, usize), - markup_node_pool: &'a SlowPool, + mark_node_pool: &'a SlowPool, ) -> EdResult<()> { match markup_node { MarkupNode::Nested { ast_node_id: _, children_ids, parent_id_opt: _, + newlines_at_end, } => { for child_id in children_ids.iter() { - let child = markup_node_pool.get(*child_id); + let child = mark_node_pool.get(*child_id); markup_to_wgpu_helper( child, wgpu_texts, rects, code_style, txt_row_col, - markup_node_pool, + mark_node_pool, )?; } + + for _ in 0..*newlines_at_end { + wgpu_texts.push(newline(code_style.font_size)); + + txt_row_col.0 += 1; + txt_row_col.1 = 0; + } } MarkupNode::Text { content, @@ -107,14 +126,23 @@ fn markup_to_wgpu_helper<'a>( syn_high_style, attributes: _, parent_id_opt: _, + newlines_at_end, } => { let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; - let glyph_text = glyph_brush::OwnedText::new(content) + let full_content = markup_node.get_full_content(); + + let glyph_text = glyph_brush::OwnedText::new(full_content) .with_color(colors::to_slice(*highlight_color)) .with_scale(code_style.font_size); txt_row_col.1 += content.len(); + + for _ in 0..*newlines_at_end { + txt_row_col.0 += 1; + txt_row_col.1 = 0; + } + wgpu_texts.push(glyph_text); } MarkupNode::Blank { @@ -122,8 +150,11 @@ fn markup_to_wgpu_helper<'a>( attributes: _, syn_high_style, parent_id_opt: _, + newlines_at_end, } => { - let glyph_text = glyph_brush::OwnedText::new(BLANK_PLACEHOLDER) + let full_content = markup_node.get_full_content(); + + let glyph_text = glyph_brush::OwnedText::new(full_content) .with_color(colors::to_slice(colors::WHITE)) .with_scale(code_style.font_size); @@ -132,7 +163,7 @@ fn markup_to_wgpu_helper<'a>( let char_width = code_style.glyph_dim_rect.width; let char_height = code_style.glyph_dim_rect.height; - let hole_rect = Rect { + let blank_rect = Rect { top_left_coords: ( code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width, code_style.txt_coords.y @@ -144,12 +175,21 @@ fn markup_to_wgpu_helper<'a>( height: char_height, color: *highlight_color, }; - rects.push(hole_rect); + rects.push(blank_rect); txt_row_col.1 += BLANK_PLACEHOLDER.len(); wgpu_texts.push(glyph_text); + + for _ in 0..*newlines_at_end { + txt_row_col.0 += 1; + txt_row_col.1 = 0; + } } }; Ok(()) } + +fn newline(font_size: f32) -> glyph_brush::OwnedText { + glyph_brush::OwnedText::new("\n").with_scale(font_size) +} diff --git a/editor/src/editor/render_debug.rs b/editor/src/editor/render_debug.rs index ea3ca106b1..36e4377d80 100644 --- a/editor/src/editor/render_debug.rs +++ b/editor/src/editor/render_debug.rs @@ -4,7 +4,7 @@ use crate::editor::mvc::ed_model::EdModel; use crate::graphics::colors; use crate::graphics::colors::from_hsb; use crate::graphics::primitives::text as gr_text; -use crate::lang::ast::expr2_to_string; +use crate::lang::ast::def2_to_string; use cgmath::Vector2; use winit::dpi::PhysicalSize; @@ -19,36 +19,49 @@ pub fn build_debug_graphics( let area_bounds = (size.width as f32, size.height as f32); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); - let debug_txt_coords: Vector2 = (txt_coords.x, txt_coords.y * 3.0).into(); + let debug_txt_coords: Vector2 = (txt_coords.x * 20.0, txt_coords.y).into(); + + let carets_text = + glyph_brush::OwnedText::new(format!("carets: {:?}\n\n", ed_model.get_carets())) + .with_color(colors::to_slice(from_hsb(0, 0, 100))) + .with_scale(config.debug_font_size); let grid_node_map_text = glyph_brush::OwnedText::new(format!("{}", ed_model.grid_node_map)) .with_color(colors::to_slice(from_hsb(20, 41, 100))) - .with_scale(config.code_font_size); + .with_scale(config.debug_font_size); let code_lines_text = glyph_brush::OwnedText::new(format!("{}", ed_model.code_lines)) .with_color(colors::to_slice(from_hsb(0, 49, 96))) - .with_scale(config.code_font_size); + .with_scale(config.debug_font_size); - let mark_node_tree_text = glyph_brush::OwnedText::new(tree_as_string( - ed_model.markup_root_id, - &ed_model.markup_node_pool, - )) - .with_color(colors::to_slice(from_hsb(266, 31, 96))) - .with_scale(config.code_font_size); + let mut mark_node_trees_string = "\nmark node trees:".to_owned(); - let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.markup_node_pool)) + for mark_id in ed_model.markup_ids[1..].iter() { + // 1.. -> skip header + mark_node_trees_string.push_str(&tree_as_string(*mark_id, &ed_model.mark_node_pool)); + } + + let mark_node_tree_text = glyph_brush::OwnedText::new(mark_node_trees_string) + .with_color(colors::to_slice(from_hsb(266, 31, 96))) + .with_scale(config.debug_font_size); + + let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.mark_node_pool)) .with_color(colors::to_slice(from_hsb(110, 45, 82))) - .with_scale(config.code_font_size); + .with_scale(config.debug_font_size); - let ast_node_text = glyph_brush::OwnedText::new(format!( - "\n\n(ast_root)\n{}", - expr2_to_string(ed_model.module.ast_root_id, ed_model.module.env.pool) - )) - .with_color(colors::to_slice(from_hsb(211, 80, 100))) - .with_scale(config.code_font_size); + let mut ast_node_text_str = "AST:\n".to_owned(); + + for def_id in ed_model.module.ast.def_ids.iter() { + ast_node_text_str.push_str(&def2_to_string(*def_id, ed_model.module.env.pool)) + } + + let ast_node_text = glyph_brush::OwnedText::new(ast_node_text_str) + .with_color(colors::to_slice(from_hsb(211, 80, 100))) + .with_scale(config.debug_font_size); let section = gr_text::owned_section_from_glyph_texts( vec![ + carets_text, grid_node_map_text, code_lines_text, mark_node_tree_text, diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index 852749c680..44f9546811 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -1,3 +1,27 @@ -pub const NOTHING_OPENED: &str = "Opening files is not yet supported, execute `cargo run edit` from the root folder of the repo to try the editor."; -pub const START_TIP: &str = - "Start by typing '[', '{', '\"' or a number.\nInput chars that would create parse errors will be ignored."; +#![allow(dead_code)] + +pub const NOTHING_OPENED: &str = + "Execute `cargo run edit` from the root folder of the repo to try the editor."; + +pub const START_TIP: &str = r#"Currently supported: lists, records, string, numbers and value definitions. + +Use `Ctrl+Shift+Up` or `Cmd+Shift+Up` to select surrounding expression. +Use backspace after `Ctrl+Shift+Up` to delete the selected expression. + +`Ctrl+S` or `Cmd+S` to save. +`Ctrl+R` to run. + +Input chars that would create parse errors or change formatting will be ignored. +For convenience and consistency, there is only one way to format roc. +"#; + +pub const HELLO_WORLD: &str = r#" +app "test-app" + packages { base: "platform" } + imports [] + provides [ main ] to base + +main = "Hello, world!" + + +"#; diff --git a/editor/src/editor/slow_pool.rs b/editor/src/editor/slow_pool.rs index d6ee4c85b9..72dc43336c 100644 --- a/editor/src/editor/slow_pool.rs +++ b/editor/src/editor/slow_pool.rs @@ -63,7 +63,7 @@ impl fmt::Display for SlowPool { "{}: {} ({}) ast_id {:?} {}", index, node.node_type_as_string(), - node.get_content().unwrap_or_else(|_| "".to_string()), + node.get_content(), ast_node_id.parse::().unwrap(), child_str )?; diff --git a/editor/src/editor/style.rs b/editor/src/editor/style.rs index 6325b8fca4..2c4dc0530c 100644 --- a/editor/src/editor/style.rs +++ b/editor/src/editor/style.rs @@ -1 +1 @@ -pub const CODE_TXT_XY: (f32, f32) = (40.0, 130.0); +pub const CODE_TXT_XY: (f32, f32) = (40.0, 350.0); diff --git a/editor/src/editor/syntax_highlight.rs b/editor/src/editor/syntax_highlight.rs index c2fbb31c07..602a335b48 100644 --- a/editor/src/editor/syntax_highlight.rs +++ b/editor/src/editor/syntax_highlight.rs @@ -14,6 +14,8 @@ pub enum HighlightStyle { PackageRelated, // app, packages, imports, exposes, provides... Variable, RecordField, + Import, + Provides, Blank, } @@ -31,6 +33,8 @@ pub fn default_highlight_map() -> HashMap { (PackageRelated, gr_colors::WHITE), (Variable, gr_colors::WHITE), (RecordField, from_hsb(258, 50, 90)), + (Import, from_hsb(185, 50, 75)), + (Provides, from_hsb(185, 50, 75)), (Blank, from_hsb(258, 50, 90)), // comment from_hsb(285, 6, 47) or 186, 35, 40 ] diff --git a/editor/src/graphics/lowlevel/buffer.rs b/editor/src/graphics/lowlevel/buffer.rs index f9cf9fc9a4..723e5957ef 100644 --- a/editor/src/graphics/lowlevel/buffer.rs +++ b/editor/src/graphics/lowlevel/buffer.rs @@ -105,7 +105,7 @@ pub fn create_rect_buffers( let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { label: None, size: Vertex::SIZE * 4 * nr_of_rects, - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -114,7 +114,7 @@ pub fn create_rect_buffers( let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { label: None, size: u32_size * 6 * nr_of_rects, - usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -148,7 +148,7 @@ impl StagingBuffer { StagingBuffer { buffer: device.create_buffer_init(&BufferInitDescriptor { contents: bytemuck::cast_slice(data), - usage: wgpu::BufferUsage::COPY_SRC, + usage: wgpu::BufferUsages::COPY_SRC, label: Some("Staging Buffer"), }), size: size_of_slice(data) as wgpu::BufferAddress, diff --git a/editor/src/graphics/lowlevel/ortho.rs b/editor/src/graphics/lowlevel/ortho.rs index 0b5871bb93..2f4577871a 100644 --- a/editor/src/graphics/lowlevel/ortho.rs +++ b/editor/src/graphics/lowlevel/ortho.rs @@ -2,7 +2,7 @@ use cgmath::{Matrix4, Ortho}; use wgpu::util::DeviceExt; use wgpu::{ BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, - ShaderStage, + ShaderStages, }; // orthographic projection is used to transform pixel coords to the coordinate system used by wgpu @@ -45,7 +45,7 @@ pub fn update_ortho_buffer( let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Ortho uniform buffer"), contents: bytemuck::cast_slice(&[new_uniforms]), - usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_SRC, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC, }); // get a command encoder for the current frame @@ -83,14 +83,14 @@ pub fn init_ortho( let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Ortho uniform buffer"), contents: bytemuck::cast_slice(&[uniforms]), - usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); // bind groups consist of extra resources that are provided to the shaders let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, - visibility: ShaderStage::VERTEX, + visibility: ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, diff --git a/editor/src/graphics/lowlevel/pipelines.rs b/editor/src/graphics/lowlevel/pipelines.rs index 810766ccfd..5bc768a33f 100644 --- a/editor/src/graphics/lowlevel/pipelines.rs +++ b/editor/src/graphics/lowlevel/pipelines.rs @@ -9,9 +9,9 @@ pub struct RectResources { pub fn make_rect_pipeline( gpu_device: &wgpu::Device, - swap_chain_descr: &wgpu::SwapChainDescriptor, + surface_config: &wgpu::SurfaceConfiguration, ) -> RectResources { - let ortho = init_ortho(swap_chain_descr.width, swap_chain_descr.height, gpu_device); + let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device); let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { bind_group_layouts: &[&ortho.bind_group_layout], @@ -21,11 +21,10 @@ pub fn make_rect_pipeline( let pipeline = create_render_pipeline( gpu_device, &pipeline_layout, - swap_chain_descr.format, + surface_config.format, &wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))), - flags: wgpu::ShaderFlags::all(), }, ); @@ -61,7 +60,7 @@ pub fn create_render_pipeline( }, alpha: wgpu::BlendComponent::REPLACE, }), - write_mask: wgpu::ColorWrite::ALL, + write_mask: wgpu::ColorWrites::ALL, }], }), primitive: wgpu::PrimitiveState::default(), diff --git a/editor/src/graphics/lowlevel/vertex.rs b/editor/src/graphics/lowlevel/vertex.rs index 64cf9fac1f..f17a840bb2 100644 --- a/editor/src/graphics/lowlevel/vertex.rs +++ b/editor/src/graphics/lowlevel/vertex.rs @@ -18,7 +18,7 @@ impl Vertex { pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { array_stride: Self::SIZE, - step_mode: wgpu::InputStepMode::Vertex, + step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ // position wgpu::VertexAttribute { diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs index 69fb7f4d56..3fffb3cdad 100644 --- a/editor/src/lang/ast.rs +++ b/editor/src/lang/ast.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::hash::BuildHasherDefault; +use crate::editor::ed_error::{EdResult, UnexpectedASTNode}; use crate::lang::pattern::{Pattern2, PatternId}; use crate::lang::pool::Pool; use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; @@ -222,6 +223,17 @@ pub enum Expr2 { RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), } +// A top level definition, not inside a function. For example: `main = "Hello, world!"` +#[derive(Debug)] +pub enum Def2 { + // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" + ValueDef { + identifier_id: NodeId, + expr_id: NodeId, + }, + Blank, +} + #[derive(Debug)] pub enum ValueDef { WithAnnotation { @@ -267,6 +279,48 @@ impl ShallowClone for ValueDef { } } +impl ValueDef { + pub fn get_expr_id(&self) -> ExprId { + match self { + ValueDef::WithAnnotation { expr_id, .. } => *expr_id, + ValueDef::NoAnnotation { expr_id, .. } => *expr_id, + } + } + + pub fn get_pattern_id(&self) -> NodeId { + match self { + ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, + ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, + } + } +} + +pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { + match val_def { + ValueDef::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => { + format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) + } + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + format!( + "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", + pool.get(*pattern_id), + expr2_to_string(*expr_id, pool), + expr_var + ) + } + } +} + #[derive(Debug)] pub enum FunctionDef { WithAnnotation { @@ -402,7 +456,11 @@ pub struct WhenBranch { // TODO make the inner types private? pub type ExprId = NodeId; +pub type DefId = NodeId; + use RecordField::*; + +use super::parse::ASTNodeId; impl RecordField { pub fn get_record_field_var(&self) -> &Variable { match self { @@ -437,6 +495,13 @@ impl RecordField { } } +pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { + match node_id { + ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), + ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), + } +} + pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { let mut full_string = String::new(); let expr2 = pool.get(node_id); @@ -550,16 +615,118 @@ fn expr2_to_string_helper( Expr2::SmallInt { text, .. } => { out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); } + Expr2::LetValue { + def_id, body_id, .. + } => { + out_string.push_str(&format!( + "LetValue(def_id: >>{:?}), body_id: >>{:?})", + value_def_to_string(pool.get(*def_id), pool), + pool.get(*body_id) + )); + } other => todo!("Implement for {:?}", other), } out_string.push('\n'); } +pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { + let mut full_string = String::new(); + let def2 = pool.get(node_id); + + match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + full_string.push_str(&format!( + "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", + pool.get(*identifier_id), + expr2_to_string(*expr_id, pool) + )); + } + Def2::Blank => { + full_string.push_str("Def2::Blank"); + } + } + + full_string +} + fn var_to_string(some_var: &Variable, indent_level: usize) -> String { format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) } +// get string from SmallStr or Str +pub fn get_string_from_expr2(node_id: ExprId, pool: &Pool) -> EdResult { + match pool.get(node_id) { + Expr2::SmallStr(arr_string) => Ok(arr_string.as_str().to_string()), + Expr2::Str(pool_str) => Ok(pool_str.as_str(pool).to_owned()), + other => UnexpectedASTNode { + required_node_type: "SmallStr or Str", + encountered_node_type: format!("{:?}", other), + } + .fail()?, + } +} + +pub fn update_str_expr( + node_id: ExprId, + new_char: char, + insert_index: usize, + pool: &mut Pool, +) -> EdResult<()> { + let str_expr = pool.get_mut(node_id); + + enum Either { + MyString(String), + MyPoolStr(PoolStr), + Done, + } + + let insert_either = match str_expr { + Expr2::SmallStr(arr_string) => { + let insert_res = arr_string.try_insert(insert_index as u8, new_char); + + match insert_res { + Ok(_) => Either::Done, + _ => { + let mut new_string = arr_string.as_str().to_string(); + new_string.insert(insert_index, new_char); + + Either::MyString(new_string) + } + } + } + Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), + other => UnexpectedASTNode { + required_node_type: "SmallStr or Str", + encountered_node_type: format!("{:?}", other), + } + .fail()?, + }; + + match insert_either { + Either::MyString(new_string) => { + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::MyPoolStr(old_pool_str) => { + let mut new_string = old_pool_str.as_str(pool).to_owned(); + + new_string.insert(insert_index, new_char); + + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::Done => (), + } + + Ok(()) +} + #[test] fn size_of_expr() { assert_eq!(std::mem::size_of::(), crate::lang::pool::NODE_BYTES); diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs index 2321870946..a50c315f94 100644 --- a/editor/src/lang/expr.rs +++ b/editor/src/lang/expr.rs @@ -3,10 +3,11 @@ #![allow(unused_imports)] use bumpalo::{collections::Vec as BumpVec, Bump}; use std::collections::HashMap; +use std::iter::FromIterator; use crate::lang::ast::{ - expr2_to_string, ClosureExtra, Expr2, ExprId, FloatVal, IntStyle, IntVal, RecordField, - WhenBranch, + expr2_to_string, value_def_to_string, ClosureExtra, Def2, Expr2, ExprId, FloatVal, IntStyle, + IntVal, RecordField, ValueDef, WhenBranch, }; use crate::lang::def::{ canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, @@ -288,6 +289,25 @@ pub fn to_expr_id<'a>( (env.add(expr, region), output) } +pub fn str_to_def2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> Result, SyntaxError<'a>> { + match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { + Ok(vec_loc_def) => Ok(defs_to_defs2( + arena, + env, + scope, + arena.alloc(vec_loc_def), + region, + )), + Err(fail) => Err(fail), + } +} + pub fn str_to_expr2<'a>( arena: &'a Bump, input: &'a str, @@ -296,20 +316,23 @@ pub fn str_to_expr2<'a>( region: Region, ) -> Result<(Expr2, self::Output), SyntaxError<'a>> { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => { - let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); - - Ok(to_expr2( - env, - scope, - arena.alloc(desugared_loc_expr.value), - region, - )) - } + Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), Err(fail) => Err(fail), } } +fn loc_expr_to_expr2<'a>( + arena: &'a Bump, + loc_expr: Located>, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> (Expr2, self::Output) { + let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); + + to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) +} + pub fn to_expr2<'a>( env: &mut Env<'a>, scope: &mut Scope, @@ -967,6 +990,74 @@ pub fn to_expr2<'a>( } } +pub fn defs_to_defs2<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_defs: &'a BumpVec>>, + region: Region, +) -> Vec { + use roc_parse::ast::Expr::*; + + parsed_defs + .iter() + .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) + .collect() +} + +pub fn to_def2_from_def<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_def: &'a roc_parse::ast::Def<'a>, + region: Region, +) -> Def2 { + use roc_parse::ast::Def::*; + + match parsed_def { + SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + Body(&loc_pattern, &loc_expr) => { + // TODO loc_pattern use identifier + let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; + let expr_id = env.pool.add(expr2); + + use roc_parse::ast::Pattern::*; + + match loc_pattern.value { + Identifier(_) => { + let (_, pattern2) = to_pattern2( + env, + scope, + PatternType::TopLevelDef, + &loc_pattern.value, + region, + ); + let pattern_id = env.pool.add(pattern2); + + // TODO support with annotation + Def2::ValueDef { + identifier_id: pattern_id, + expr_id, + } + } + other => { + unimplemented!( + "I don't yet know how to convert the pattern {:?} into an expr2", + other + ) + } + } + } + other => { + unimplemented!( + "I don't know how to make an expr2 from this def yet: {:?}", + other + ) + } + } +} + fn flatten_str_literal<'a>( env: &mut Env<'a>, scope: &mut Scope, @@ -1412,6 +1503,7 @@ fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret Def::AnnotationOnly { .. } => todo!(), Def::Value(value_def) => { let def_id = pool.add(value_def); + let body_id = pool.add(ret); Expr2::LetValue { diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs index dcb9c6dae1..fa747386e1 100644 --- a/editor/src/lang/mod.rs +++ b/editor/src/lang/mod.rs @@ -3,7 +3,8 @@ pub mod constrain; mod def; pub mod expr; mod module; -mod pattern; +pub mod parse; +pub mod pattern; pub mod pool; pub mod roc_file; pub mod scope; diff --git a/editor/src/lang/parse.rs b/editor/src/lang/parse.rs new file mode 100644 index 0000000000..9c7253bb75 --- /dev/null +++ b/editor/src/lang/parse.rs @@ -0,0 +1,98 @@ +use std::fmt::Debug; + +use crate::{ + editor::ed_error::ASTNodeIdWithoutExprId, editor::ed_error::EdResult, lang::scope::Scope, +}; +use bumpalo::Bump; +use roc_parse::parser::SyntaxError; +use roc_region::all::Region; + +use super::{ + ast::{DefId, Expr2, ExprId}, + expr::{str_to_def2, Env}, +}; + +#[derive(Debug)] +pub struct AST { + pub header: AppHeader, + pub def_ids: Vec, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ASTNodeId { + ADefId(DefId), + AExprId(ExprId), +} + +impl ASTNodeId { + pub fn to_expr_id(&self) -> EdResult { + match self { + ASTNodeId::AExprId(expr_id) => Ok(*expr_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } + + pub fn to_def_id(&self) -> EdResult { + match self { + ASTNodeId::ADefId(def_id) => Ok(*def_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } +} + +#[derive(Debug)] +pub struct AppHeader { + pub app_name: String, + pub packages_base: String, + pub imports: Vec, + pub provides: Vec, + pub ast_node_id: ExprId, // TODO probably want to use HeaderId +} + +impl AST { + pub fn parse_from_string<'a>( + code_str: &'a str, + env: &mut Env<'a>, + ast_arena: &'a Bump, + ) -> Result> { + let blank_line_indx = code_str + .find("\n\n") + .expect("I was expecting a double newline to split header and rest of code."); + + let header_str = &code_str[0..blank_line_indx]; + let tail_str = &code_str[blank_line_indx..]; + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + let region = Region::new(0, 0, 0, 0); + + let mut def_ids = Vec::::new(); + + let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; + + for def2 in def2_vec { + let def_id = env.pool.add(def2); + + def_ids.push(def_id); + } + + let ast_node_id = env.pool.add(Expr2::Blank); + + Ok(AST { + header: AppHeader::parse_from_string(header_str, ast_node_id), + def_ids, + }) + } +} + +impl AppHeader { + // TODO don't use mock struct and actually parse string + pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> Self { + AppHeader { + app_name: "\"untitled-app\"".to_owned(), + packages_base: "\"platform\"".to_owned(), + imports: vec![], + provides: vec!["main".to_owned()], + ast_node_id, + } + } +} diff --git a/editor/src/lang/pattern.rs b/editor/src/lang/pattern.rs index 03ed3c821f..aae7797e8a 100644 --- a/editor/src/lang/pattern.rs +++ b/editor/src/lang/pattern.rs @@ -1,6 +1,7 @@ #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] +use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant}; use crate::lang::ast::{ExprId, FloatVal, IntVal}; use crate::lang::expr::{to_expr_id, Env, Output}; use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; @@ -9,7 +10,7 @@ use bumpalo::collections::Vec as BumpVec; use roc_can::expr::unescape_char; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_collections::all::BumpMap; -use roc_module::symbol::Symbol; +use roc_module::symbol::{Interns, Symbol}; use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; @@ -482,6 +483,17 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { symbols } +pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult { + match pattern { + Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), + other => UnexpectedPattern2Variant { + required_pattern2: "Identifier".to_string(), + encountered_pattern2: format!("{:?}", other), + } + .fail()?, + } +} + pub fn symbols_and_variables_from_pattern( pool: &Pool, initial: &Pattern2, diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f94ac4ccc8..f6f3053840 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -17,6 +17,6 @@ mod window; use std::io; use std::path::Path; -pub fn launch(filepaths: &[&Path]) -> io::Result<()> { - editor::main::launch(filepaths) +pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> { + editor::main::launch(project_dir_path_opt) } diff --git a/editor/src/ui/text/big_text_area.rs b/editor/src/ui/text/big_text_area.rs index 0ebd964e33..c1189e84ae 100644 --- a/editor/src/ui/text/big_text_area.rs +++ b/editor/src/ui/text/big_text_area.rs @@ -9,23 +9,19 @@ use crate::ui::text::{ selection::{validate_raw_sel, RawSelection, Selection}, text_pos::TextPos, }; -use crate::ui::ui_error::{ - OutOfBounds, - UIError::{FileOpenFailed, TextBufReadFailed}, - UIResult, -}; +use crate::ui::ui_error::{OutOfBounds, UIResult}; use crate::ui::util::is_newline; use crate::window::keyboard_input::{no_mods, Modifiers}; -use bumpalo::collections::String as BumpString; use bumpalo::Bump; -use ropey::Rope; use snafu::ensure; -use std::{fmt, fs::File, io, path::Path}; +use std::{fmt, path::Path}; use winit::event::{VirtualKeyCode, VirtualKeyCode::*}; +use super::text_buffer::TextBuffer; + pub struct BigTextArea { pub caret_w_select: CaretWSelect, - text_rope: Rope, + text_buffer: TextBuffer, pub path_str: String, arena: Bump, } @@ -33,84 +29,37 @@ pub struct BigTextArea { impl BigTextArea { fn check_bounds(&self, char_indx: usize) -> UIResult<()> { ensure!( - char_indx <= self.text_rope.len_chars(), + char_indx <= self.text_buffer.nr_of_chars(), OutOfBounds { index: char_indx, - collection_name: "Rope", - len: self.text_rope.len_chars() + collection_name: "TextBuffer", + len: self.text_buffer.nr_of_chars() } ); Ok(()) } - - fn pos_to_char_indx(&self, pos: TextPos) -> usize { - self.text_rope.line_to_char(pos.line) + pos.column - } - - fn char_indx_to_pos(&self, char_indx: usize) -> TextPos { - let line = self.text_rope.char_to_line(char_indx); - - let char_idx_line_start = self.pos_to_char_indx(TextPos { line, column: 0 }); - - let column = char_indx - char_idx_line_start; - - TextPos { line, column } - } - - fn sel_to_tup(&self, val_sel: Selection) -> (usize, usize) { - let start_char_indx = self.pos_to_char_indx(val_sel.start_pos); - let end_char_indx = self.pos_to_char_indx(val_sel.end_pos); - - (start_char_indx, end_char_indx) - } } impl Lines for BigTextArea { - fn get_line(&self, line_nr: usize) -> UIResult<&str> { - ensure!( - line_nr < self.nr_of_lines(), - OutOfBounds { - index: line_nr, - collection_name: "BigTextArea", - len: self.nr_of_lines(), - } - ); - - let rope_slice = self.text_rope.line(line_nr); - - if let Some(line_str_ref) = rope_slice.as_str() { - Ok(line_str_ref) - } else { - // happens very rarely - let line_str = rope_slice.chunks().collect::(); - let arena_str_ref = self.arena.alloc(line_str); - Ok(arena_str_ref) - } + fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> { + self.text_buffer.get_line_ref(line_nr) } fn line_len(&self, line_nr: usize) -> UIResult { - self.get_line(line_nr).map(|line| line.len()) + self.get_line_ref(line_nr).map(|line| line.len()) } fn nr_of_lines(&self) -> usize { - self.text_rope.len_lines() + self.text_buffer.nr_of_lines() } fn nr_of_chars(&self) -> usize { - self.text_rope.len_chars() + self.text_buffer.nr_of_chars() } - // TODO use pool allocation here - // expensive function, don't use it if it can be done with a specialized, more efficient function - fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { - let mut lines = BumpString::with_capacity_in(self.text_rope.len_chars(), arena); - - for line in self.text_rope.lines() { - lines.extend(line.as_str()); - } - - lines + fn all_lines_as_string(&self) -> String { + self.text_buffer.all_lines_ref().join("\n") } fn is_last_line(&self, line_nr: usize) -> bool { @@ -118,7 +67,7 @@ impl Lines for BigTextArea { } fn last_char(&self, line_nr: usize) -> UIResult> { - Ok(self.get_line(line_nr)?.chars().last()) + Ok(self.get_line_ref(line_nr)?.chars().last()) } } @@ -177,17 +126,7 @@ impl SelectableLines for BigTextArea { fn get_selected_str(&self) -> UIResult> { if let Some(val_sel) = self.caret_w_select.selection_opt { - let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel); - - self.check_bounds(end_char_indx)?; - - let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx); - - if let Some(line_str_ref) = rope_slice.as_str() { - Ok(Some(line_str_ref.to_string())) - } else { - Ok(Some(rope_slice.chunks().collect::())) - } + Ok(Some(self.text_buffer.get_selected_str(val_sel)?)) } else { Ok(None) } @@ -223,7 +162,13 @@ impl SelectableLines for BigTextArea { } fn last_text_pos(&self) -> UIResult { - Ok(self.char_indx_to_pos(self.nr_of_chars())) + let line_nr = self.nr_of_lines() - 1; + let last_col = self.line_len(line_nr)?; + + Ok(TextPos { + line: line_nr, + column: last_col, + }) } fn handle_key_down( @@ -279,7 +224,7 @@ impl MutSelectableLines for BigTextArea { // On Linux, '\u{8}' is backspace, // on macOS '\u{7f}'. - self.pop_char()? + self.backspace()? } '\u{1}' // Ctrl + A @@ -303,27 +248,21 @@ impl MutSelectableLines for BigTextArea { fn insert_str(&mut self, new_str: &str) -> UIResult<()> { let caret_pos = self.caret_w_select.caret_pos; - let char_indx = self.pos_to_char_indx(caret_pos); - self.check_bounds(char_indx)?; - - self.text_rope.insert(char_indx, new_str); + self.text_buffer.insert_str(caret_pos, new_str)?; Ok(()) } - fn pop_char(&mut self) -> UIResult<()> { + fn backspace(&mut self) -> UIResult<()> { if self.is_selection_active() { self.del_selection()?; } else { let old_caret_pos = self.caret_w_select.caret_pos; - let char_indx = self.pos_to_char_indx(old_caret_pos); self.move_caret_left(&no_mods())?; - if (char_indx > 0) && char_indx <= self.text_rope.len_chars() { - self.text_rope.remove((char_indx - 1)..char_indx); - } + self.text_buffer.backspace_char(old_caret_pos)?; } Ok(()) @@ -331,11 +270,7 @@ impl MutSelectableLines for BigTextArea { fn del_selection(&mut self) -> UIResult<()> { if let Some(selection) = self.caret_w_select.selection_opt { - let (start_char_indx, end_char_indx) = self.sel_to_tup(selection); - - self.check_bounds(end_char_indx)?; - - self.text_rope.remove(start_char_indx..end_char_indx); + self.text_buffer.del_selection(selection)?; self.set_caret(selection.start_pos); @@ -349,13 +284,13 @@ impl MutSelectableLines for BigTextArea { impl Default for BigTextArea { fn default() -> Self { let caret_w_select = CaretWSelect::default(); - let text_rope = Rope::from_str(""); + let text_buffer = TextBuffer { lines: Vec::new() }; let path_str = "".to_owned(); let arena = Bump::new(); Self { caret_w_select, - text_rope, + text_buffer, path_str, arena, } @@ -363,11 +298,11 @@ impl Default for BigTextArea { } pub fn from_path(path: &Path) -> UIResult { - let text_rope = rope_from_path(path)?; + let text_buffer = TextBuffer::from_path(path)?; let path_str = path_to_string(path); Ok(BigTextArea { - text_rope, + text_buffer, path_str, ..Default::default() }) @@ -375,11 +310,9 @@ pub fn from_path(path: &Path) -> UIResult { #[allow(dead_code)] // used by tests but will also be used in the future -pub fn from_str(text: &str) -> BigTextArea { - let text_rope = Rope::from_str(text); - +pub fn from_str_vec(lines: Vec) -> BigTextArea { BigTextArea { - text_rope, + text_buffer: TextBuffer { lines }, ..Default::default() } } @@ -391,31 +324,12 @@ fn path_to_string(path: &Path) -> String { path_str } -fn rope_from_path(path: &Path) -> UIResult { - match File::open(path) { - Ok(file) => { - let buf_reader = &mut io::BufReader::new(file); - match Rope::from_reader(buf_reader) { - Ok(rope) => Ok(rope), - Err(e) => Err(TextBufReadFailed { - path_str: path_to_string(path), - err_msg: e.to_string(), - }), - } - } - Err(e) => Err(FileOpenFailed { - path_str: path_to_string(path), - err_msg: e.to_string(), - }), - } -} - // need to explicitly omit arena impl fmt::Debug for BigTextArea { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BigTextArea") .field("caret_w_select", &self.caret_w_select) - .field("text_rope", &self.text_rope) + .field("text_buffer", &self.text_buffer) .field("path_str", &self.path_str) .finish() } @@ -426,7 +340,6 @@ pub mod test_big_sel_text { use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::{ - big_text_area::from_str, big_text_area::BigTextArea, lines::{Lines, MutSelectableLines, SelectableLines}, text_pos::TextPos, @@ -436,6 +349,8 @@ pub mod test_big_sel_text { use snafu::OptionExt; use std::slice::SliceIndex; + use super::from_str_vec; + fn shift_pressed() -> Modifiers { Modifiers { shift: true, @@ -467,12 +382,11 @@ pub mod test_big_sel_text { } pub fn big_text_from_dsl_str(lines: &[String]) -> BigTextArea { - from_str( - &lines + from_str_vec( + lines .iter() .map(|line| line.replace(&['❮', '❯', '┃'][..], "")) - .collect::>() - .join(""), + .collect::>(), ) } @@ -480,7 +394,7 @@ pub mod test_big_sel_text { let mut lines: Vec = Vec::new(); for i in 0..big_sel_text.nr_of_lines() { - lines.push(big_sel_text.get_line(i).unwrap().to_string()); + lines.push(big_sel_text.get_line_ref(i).unwrap().to_string()); } lines @@ -520,10 +434,9 @@ pub mod test_big_sel_text { assert_insert(&["┃"], &[" ┃"], ' ')?; assert_insert(&["a┃"], &["aa┃"], 'a')?; assert_insert(&["a┃"], &["a ┃"], ' ')?; - assert_insert(&["a┃\n", ""], &["ab┃\n", ""], 'b')?; - assert_insert(&["a┃\n", ""], &["ab┃\n", ""], 'b')?; - assert_insert(&["a\n", "┃"], &["a\n", "b┃"], 'b')?; - assert_insert(&["a\n", "b\n", "c┃"], &["a\n", "b\n", "cd┃"], 'd')?; + assert_insert(&["a┃", ""], &["ab┃", ""], 'b')?; + assert_insert(&["a", "┃"], &["a", "b┃"], 'b')?; + assert_insert(&["a", "b", "c┃"], &["a", "b", "cd┃"], 'd')?; Ok(()) } @@ -532,9 +445,9 @@ pub mod test_big_sel_text { fn insert_new_char_mid() -> Result<(), String> { assert_insert(&["ab┃d"], &["abc┃d"], 'c')?; assert_insert(&["a┃cd"], &["ab┃cd"], 'b')?; - assert_insert(&["abc\n", "┃e"], &["abc\n", "d┃e"], 'd')?; - assert_insert(&["abc\n", "def\n", "┃ "], &["abc\n", "def\n", "g┃ "], 'g')?; - assert_insert(&["abc\n", "def\n", "┃ "], &["abc\n", "def\n", " ┃ "], ' ')?; + assert_insert(&["abc", "┃e"], &["abc", "d┃e"], 'd')?; + assert_insert(&["abc", "def", "┃ "], &["abc", "def", "g┃ "], 'g')?; + assert_insert(&["abc", "def", "┃ "], &["abc", "def", " ┃ "], ' ')?; Ok(()) } @@ -545,11 +458,11 @@ pub mod test_big_sel_text { assert_insert(&[" ┃"], &["┃"], '\u{8}')?; assert_insert(&["a┃"], &["┃"], '\u{8}')?; assert_insert(&["ab┃"], &["a┃"], '\u{8}')?; - assert_insert(&["a┃\n", ""], &["┃\n", ""], '\u{8}')?; - assert_insert(&["ab┃\n", ""], &["a┃\n", ""], '\u{8}')?; - assert_insert(&["a\n", "┃"], &["a┃"], '\u{8}')?; - assert_insert(&["a\n", "b\n", "c┃"], &["a\n", "b\n", "┃"], '\u{8}')?; - assert_insert(&["a\n", "b\n", "┃"], &["a\n", "b┃"], '\u{8}')?; + assert_insert(&["a┃", ""], &["┃", ""], '\u{8}')?; + assert_insert(&["ab┃", ""], &["a┃", ""], '\u{8}')?; + assert_insert(&["a", "┃"], &["a┃"], '\u{8}')?; + assert_insert(&["a", "b", "c┃"], &["a", "b", "┃"], '\u{8}')?; + assert_insert(&["a", "b", "┃"], &["a", "b┃"], '\u{8}')?; Ok(()) } @@ -560,32 +473,28 @@ pub mod test_big_sel_text { assert_insert(&["a❮a❯┃"], &["a┃"], '\u{8}')?; assert_insert(&["❮aa❯┃"], &["┃"], '\u{8}')?; assert_insert(&["a❮b c❯┃"], &["a┃"], '\u{8}')?; - assert_insert(&["❮abc❯┃\n", ""], &["┃\n", ""], '\u{8}')?; - assert_insert(&["a\n", "❮abc❯┃"], &["a\n", "┃"], '\u{8}')?; - assert_insert(&["❮a\n", "abc❯┃"], &["┃"], '\u{8}')?; - assert_insert(&["a❮b\n", "cdef ghij❯┃"], &["a┃"], '\u{8}')?; - assert_insert(&["❮a\n", "b\n", "c❯┃"], &["┃"], '\u{8}')?; - assert_insert(&["a\n", "❮b\n", "❯┃"], &["a\n", "┃"], '\u{8}')?; + assert_insert(&["❮abc❯┃", ""], &["┃", ""], '\u{8}')?; + assert_insert(&["a", "❮abc❯┃"], &["a", "┃"], '\u{8}')?; + assert_insert(&["❮a", "abc❯┃"], &["┃"], '\u{8}')?; + assert_insert(&["a❮b", "cdef ghij❯┃"], &["a┃"], '\u{8}')?; + assert_insert(&["❮a", "b", "c❯┃"], &["┃"], '\u{8}')?; + assert_insert(&["a", "❮b", "❯┃"], &["a", "┃"], '\u{8}')?; assert_insert( - &["abc\n", "d❮ef\n", "ghi❯┃\n", "jkl"], - &["abc\n", "d┃\n", "jkl"], + &["abc", "d❮ef", "ghi❯┃", "jkl"], + &["abc", "d┃", "jkl"], '\u{8}', )?; assert_insert( - &["abc\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "┃\n", "jkl"], + &["abc", "❮def", "ghi❯┃", "jkl"], + &["abc", "┃", "jkl"], '\u{8}', )?; assert_insert( - &["abc\n", "\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "\n", "┃\n", "jkl"], - '\u{8}', - )?; - assert_insert( - &["❮abc\n", "\n", "def\n", "ghi\n", "jkl❯┃"], - &["┃"], + &["abc", "", "❮def", "ghi❯┃", "jkl"], + &["abc", "", "┃", "jkl"], '\u{8}', )?; + assert_insert(&["❮abc", "", "def", "ghi", "jkl❯┃"], &["┃"], '\u{8}')?; Ok(()) } @@ -596,28 +505,24 @@ pub mod test_big_sel_text { assert_insert(&["a❮a❯┃"], &["az┃"], 'z')?; assert_insert(&["❮aa❯┃"], &["z┃"], 'z')?; assert_insert(&["a❮b c❯┃"], &["az┃"], 'z')?; - assert_insert(&["❮abc❯┃\n", ""], &["z┃\n", ""], 'z')?; - assert_insert(&["a\n", "❮abc❯┃"], &["a\n", "z┃"], 'z')?; - assert_insert(&["❮a\n", "abc❯┃"], &["z┃"], 'z')?; - assert_insert(&["a❮b\n", "cdef ghij❯┃"], &["az┃"], 'z')?; - assert_insert(&["❮a\n", "b\n", "c❯┃"], &["z┃"], 'z')?; - assert_insert(&["a\n", "❮b\n", "❯┃"], &["a\n", "z┃"], 'z')?; + assert_insert(&["❮abc❯┃", ""], &["z┃", ""], 'z')?; + assert_insert(&["a", "❮abc❯┃"], &["a", "z┃"], 'z')?; + assert_insert(&["❮a", "abc❯┃"], &["z┃"], 'z')?; + assert_insert(&["a❮b", "cdef ghij❯┃"], &["az┃"], 'z')?; + assert_insert(&["❮a", "b", "c❯┃"], &["z┃"], 'z')?; + assert_insert(&["a", "❮b", "❯┃"], &["a", "z┃"], 'z')?; assert_insert( - &["abc\n", "d❮ef\n", "ghi❯┃\n", "jkl"], - &["abc\n", "dz┃\n", "jkl"], + &["abc", "d❮ef", "ghi❯┃", "jkl"], + &["abc", "dz┃", "jkl"], 'z', )?; + assert_insert(&["abc", "❮def", "ghi❯┃", "jkl"], &["abc", "z┃", "jkl"], 'z')?; assert_insert( - &["abc\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "z┃\n", "jkl"], + &["abc", "", "❮def", "ghi❯┃", "jkl"], + &["abc", "", "z┃", "jkl"], 'z', )?; - assert_insert( - &["abc\n", "\n", "❮def\n", "ghi❯┃\n", "jkl"], - &["abc\n", "\n", "z┃\n", "jkl"], - 'z', - )?; - assert_insert(&["❮abc\n", "\n", "def\n", "ghi\n", "jkl❯┃"], &["z┃"], 'z')?; + assert_insert(&["❮abc", "", "def", "ghi", "jkl❯┃"], &["z┃"], 'z')?; Ok(()) } @@ -647,16 +552,16 @@ pub mod test_big_sel_text { assert_select_all(&["❮a❯┃"], &["❮a❯┃"])?; assert_select_all(&["┃❮a❯"], &["❮a❯┃"])?; assert_select_all(&["┃❮abc def ghi❯"], &["❮abc def ghi❯┃"])?; - assert_select_all(&["a\n", "❮b\n", "❯┃"], &["❮a\n", "b\n", "❯┃"])?; - assert_select_all(&["a\n", "❮b❯┃\n", ""], &["❮a\n", "b\n", "❯┃"])?; - assert_select_all(&["a\n", "┃❮b\n", "❯"], &["❮a\n", "b\n", "❯┃"])?; + assert_select_all(&["a", "❮b", "❯┃"], &["❮a", "b", "❯┃"])?; + assert_select_all(&["a", "❮b❯┃", ""], &["❮a", "b", "❯┃"])?; + assert_select_all(&["a", "┃❮b", "❯"], &["❮a", "b", "❯┃"])?; assert_select_all( - &["abc\n", "def\n", "gh┃i\n", "jkl"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["abc", "def", "gh┃i", "jkl"], + &["❮abc", "def", "ghi", "jkl❯┃"], )?; assert_select_all( - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["┃❮abc", "def", "ghi", "jkl❯"], + &["❮abc", "def", "ghi", "jkl❯┃"], )?; Ok(()) @@ -680,7 +585,10 @@ pub mod test_big_sel_text { move_fun(&mut big_text, modifiers)?; - let lines_vec = all_lines_vec(&big_text); + let lines_vec = all_lines_vec(&big_text) + .iter() + .map(|l| l.replace("\n", "")) + .collect(); let post_lines_res = convert_selection_to_dsl(big_text.caret_w_select, lines_vec); match post_lines_res { @@ -704,57 +612,47 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["abc┃"], &no_mods(), move_caret_right)?; assert_move(&["┃ abc"], &[" ┃abc"], &no_mods(), move_caret_right)?; assert_move(&["abc┃ "], &["abc ┃"], &no_mods(), move_caret_right)?; + assert_move(&["abc┃", "d"], &["abc", "┃d"], &no_mods(), move_caret_right)?; + assert_move(&["abc┃", ""], &["abc", "┃"], &no_mods(), move_caret_right)?; assert_move( - &["abc┃\n", "d"], - &["abc\n", "┃d"], + &["abc", "┃def"], + &["abc", "d┃ef"], &no_mods(), move_caret_right, )?; assert_move( - &["abc┃\n", ""], - &["abc\n", "┃"], + &["abc", "def┃ "], + &["abc", "def ┃"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "d┃ef"], + &["abc", "def ┃", "ghi"], + &["abc", "def ", "┃ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def┃ "], - &["abc\n", "def ┃"], + &["abc", "def┃", ""], + &["abc", "def", "┃"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def \n", "┃ghi"], + &["abc", "def", "ghi┃", "jkl"], + &["abc", "def", "ghi", "┃jkl"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def┃\n", ""], - &["abc\n", "def\n", "┃"], + &["abc", "def", "┃ghi", "jkl"], + &["abc", "def", "g┃hi", "jkl"], &no_mods(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &["abc\n", "def\n", "ghi\n", "┃jkl"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "gh┃i\n", "jkl"], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "gh┃i", "jkl"], &no_mods(), move_caret_right, )?; @@ -774,57 +672,47 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["ab┃c"], &no_mods(), move_caret_left)?; assert_move(&[" ┃abc"], &["┃ abc"], &no_mods(), move_caret_left)?; assert_move(&["abc ┃"], &["abc┃ "], &no_mods(), move_caret_left)?; + assert_move(&["abc", "┃d"], &["abc┃", "d"], &no_mods(), move_caret_left)?; + assert_move(&["abc", "┃"], &["abc┃", ""], &no_mods(), move_caret_left)?; assert_move( - &["abc\n", "┃d"], - &["abc┃\n", "d"], + &["abc", "d┃ef"], + &["abc", "┃def"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "┃"], - &["abc┃\n", ""], + &["abc", "def ┃"], + &["abc", "def┃ "], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "┃def"], + &["abc", "def ", "┃ghi"], + &["abc", "def ┃", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def ┃"], - &["abc\n", "def┃ "], + &["abc", "def", "┃"], + &["abc", "def┃", ""], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def \n", "┃ghi"], - &["abc\n", "def ┃\n", "ghi"], + &["abc", "def", "ghi", "┃jkl"], + &["abc", "def", "ghi┃", "jkl"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "┃"], - &["abc\n", "def┃\n", ""], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "┃ghi", "jkl"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi\n", "┃jkl"], - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &no_mods(), - move_caret_left, - )?; - assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &no_mods(), - move_caret_left, - )?; - assert_move( - &["abc\n", "def\n", "gh┃i\n", "jkl"], - &["abc\n", "def\n", "g┃hi\n", "jkl"], + &["abc", "def", "gh┃i", "jkl"], + &["abc", "def", "g┃hi", "jkl"], &no_mods(), move_caret_left, )?; @@ -843,158 +731,143 @@ pub mod test_big_sel_text { assert_move(&["ab┃c"], &["┃abc"], &no_mods(), move_caret_up)?; assert_move(&["abc┃"], &["┃abc"], &no_mods(), move_caret_up)?; assert_move( - &["┃abc\n", "def"], - &["┃abc\n", "def"], + &["┃abc", "def"], + &["┃abc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "┃def"], - &["┃abc\n", "def"], + &["abc", "┃def"], + &["┃abc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d┃ef"], - &["a┃bc\n", "def"], + &["abc", "d┃ef"], + &["a┃bc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃f"], - &["ab┃c\n", "def"], + &["abc", "de┃f"], + &["ab┃c", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def┃"], - &["abc┃\n", "def"], + &["abc", "def┃"], + &["abc┃", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "┃ghi"], - &["abc\n", "┃def \n", "ghi"], + &["abc", "def ", "┃ghi"], + &["abc", "┃def ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "g┃hi"], - &["abc\n", "d┃ef \n", "ghi"], + &["abc", "def ", "g┃hi"], + &["abc", "d┃ef ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "gh┃i"], - &["abc\n", "de┃f \n", "ghi"], + &["abc", "def ", "gh┃i"], + &["abc", "de┃f ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "ghi┃"], - &["abc\n", "def┃ \n", "ghi"], + &["abc", "def ", "ghi┃"], + &["abc", "def┃ ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de\n", "ghi┃"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "de", "ghi┃"], + &["abc", "de┃", "ghi"], + &no_mods(), + move_caret_up, + )?; + assert_move(&["abc", "de┃"], &["ab┃c", "de"], &no_mods(), move_caret_up)?; + assert_move(&["abc", "d┃e"], &["a┃bc", "de"], &no_mods(), move_caret_up)?; + assert_move(&["abc", "┃de"], &["┃abc", "de"], &no_mods(), move_caret_up)?; + assert_move( + &["ab", "cdef", "ghijkl", "mnopqrst┃"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃"], - &["ab┃c\n", "de"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d┃e"], - &["a┃bc\n", "de"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "┃de"], - &["┃abc\n", "de"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], + &["ab", "cde┃f", "ghijkl", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], + &["abcdefgh", "ijklmn", "op┃qr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijkl┃mn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdef┃gh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cde┃f\n", "ghijkl\n", "mnopqrst"], - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], - &["abcdefgh\n", "ijklmn\n", "op┃qr\n", "st"], + &["abcdefg┃h", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijkl┃mn\n", "opqr\n", "st"], + &["a┃bcdefgh", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdef┃gh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["abcdefg┃h\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["a┃bcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &no_mods(), - move_caret_up, - )?; - assert_move( - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; @@ -1038,170 +911,170 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["abc┃"], &no_mods(), move_caret_down)?; assert_move(&["abc┃ "], &["abc ┃"], &no_mods(), move_caret_down)?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "def┃"], + &["abc", "┃def"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "def┃"], + &["abc", "d┃ef"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f"], - &["abc\n", "def┃"], + &["abc", "de┃f"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def┃"], - &["abc\n", "def┃"], + &["abc", "def┃"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["┃abc\n", "def"], - &["abc\n", "┃def"], + &["┃abc", "def"], + &["abc", "┃def"], &no_mods(), move_caret_down, )?; assert_move( - &["a┃bc\n", "def"], - &["abc\n", "d┃ef"], + &["a┃bc", "def"], + &["abc", "d┃ef"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃c\n", "def"], - &["abc\n", "de┃f"], + &["ab┃c", "def"], + &["abc", "de┃f"], &no_mods(), move_caret_down, )?; assert_move( - &["abc┃\n", "def"], - &["abc\n", "def┃"], + &["abc┃", "def"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "┃def \n", "ghi"], - &["abc\n", "def \n", "┃ghi"], + &["abc", "┃def ", "ghi"], + &["abc", "def ", "┃ghi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef \n", "ghi"], - &["abc\n", "def \n", "g┃hi"], + &["abc", "d┃ef ", "ghi"], + &["abc", "def ", "g┃hi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f \n", "ghi"], - &["abc\n", "def \n", "gh┃i"], + &["abc", "de┃f ", "ghi"], + &["abc", "def ", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def┃ \n", "ghi"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def┃ ", "ghi"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def ┃", "ghi"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃\n", "ghi"], - &["abc\n", "de\n", "gh┃i"], + &["abc", "de┃", "ghi"], + &["abc", "de", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc┃\n", "de"], - &["abc\n", "de┃"], + &["abc┃", "de"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃c\n", "de"], - &["abc\n", "de┃"], + &["ab┃c", "de"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["a┃bc\n", "de"], - &["abc\n", "d┃e"], + &["a┃bc", "de"], + &["abc", "d┃e"], &no_mods(), move_caret_down, )?; assert_move( - &["┃abc\n", "de"], - &["abc\n", "┃de"], + &["┃abc", "de"], + &["abc", "┃de"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cd┃ef\n", "ghijkl\n", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], + &["ab", "cd┃ef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghij┃kl\n", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], + &["ab", "cdef", "ghij┃kl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], &no_mods(), move_caret_down, )?; assert_move( - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "┃cdef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["ab", "┃cdef", "ghijkl", "mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "┃st"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr", "┃st"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; @@ -1272,80 +1145,80 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "de┃\n", "ghi"], - &["abc\n", "┃de\n", "ghi"], + &["abc", "de┃", "ghi"], + &["abc", "┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " d┃e\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["abc", " d┃e", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "┃ de\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["abc", "┃ de", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " ┃de\n", "ghi"], - &["abc\n", "┃ de\n", "ghi"], + &["abc", " ┃de", "ghi"], + &["abc", "┃ de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc┃\n", "de\n", "ghi"], - &["┃abc\n", "de\n", "ghi"], + &["abc┃", "de", "ghi"], + &["┃abc", "de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &[" ┃abc\n", "de\n", "ghi"], - &["┃ abc\n", "de\n", "ghi"], + &[" ┃abc", "de", "ghi"], + &["┃ abc", "de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["┃ abc\n", "de\n", "ghi"], - &[" ┃abc\n", "de\n", "ghi"], + &["┃ abc", "de", "ghi"], + &[" ┃abc", "de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "de\n", "ghi┃"], - &["abc\n", "de\n", "┃ghi"], + &["abc", "de", "ghi┃"], + &["abc", "de", "┃ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "de\n", " ┃ghi"], - &["abc\n", "de\n", "┃ ghi"], + &["abc", "de", " ┃ghi"], + &["abc", "de", "┃ ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", "de\n", "┃ ghi"], - &["abc\n", "de\n", " ┃ghi"], + &["abc", "de", "┃ ghi"], + &["abc", "de", " ┃ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc \n", "de \n", "┃ghi "], - &["abc \n", "de \n", "┃ghi "], + &["abc ", "de ", "┃ghi "], + &["abc ", "de ", "┃ghi "], &no_mods(), move_caret_home, )?; assert_move( - &["abc \n", "┃de \n", "ghi "], - &["abc \n", "┃de \n", "ghi "], + &["abc ", "┃de ", "ghi "], + &["abc ", "┃de ", "ghi "], &no_mods(), move_caret_home, )?; assert_move( - &["┃abc \n", "de \n", "ghi "], - &["┃abc \n", "de \n", "ghi "], + &["┃abc ", "de ", "ghi "], + &["┃abc ", "de ", "ghi "], &no_mods(), move_caret_home, )?; @@ -1370,56 +1243,56 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "┃de\n", "ghi"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "┃de", "ghi"], + &["abc", "de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["abc\n", " d┃e\n", "ghi"], - &["abc\n", " de┃\n", "ghi"], + &["abc", " d┃e", "ghi"], + &["abc", " de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["┃abc\n", "de\n", "ghi"], - &["abc┃\n", "de\n", "ghi"], + &["┃abc", "de", "ghi"], + &["abc┃", "de", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["abc\n", "de\n", "g┃hi"], - &["abc\n", "de\n", "ghi┃"], + &["abc", "de", "g┃hi"], + &["abc", "de", "ghi┃"], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "de \n", "ghi┃ "], - &["abc \n", "de \n", "ghi ┃"], + &["abc ", "de ", "ghi┃ "], + &["abc ", "de ", "ghi ┃"], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "┃de \n", "ghi "], - &["abc \n", "de ┃\n", "ghi "], + &["abc ", "┃de ", "ghi "], + &["abc ", "de ┃", "ghi "], &no_mods(), move_caret_end, )?; assert_move( - &["abc ┃\n", "de \n", "ghi "], - &["abc ┃\n", "de \n", "ghi "], + &["abc ┃", "de ", "ghi "], + &["abc ┃", "de ", "ghi "], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "de ┃\n", "ghi "], - &["abc \n", "de ┃\n", "ghi "], + &["abc ", "de ┃", "ghi "], + &["abc ", "de ┃", "ghi "], &no_mods(), move_caret_end, )?; assert_move( - &["abc \n", "de \n", "ghi ┃"], - &["abc \n", "de \n", "ghi ┃"], + &["abc ", "de ", "ghi ┃"], + &["abc ", "de ", "ghi ┃"], &no_mods(), move_caret_end, )?; @@ -1440,56 +1313,56 @@ pub mod test_big_sel_text { assert_move(&["┃ abc"], &["❮ ❯┃abc"], &shift_pressed(), move_caret_right)?; assert_move(&["abc┃ "], &["abc❮ ❯┃"], &shift_pressed(), move_caret_right)?; assert_move( - &["abc┃\n", "d"], - &["abc❮\n", "❯┃d"], + &["abc┃", "d"], + &["abc❮", "❯┃d"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc┃\n", ""], - &["abc❮\n", "❯┃"], + &["abc┃", ""], + &["abc❮", "❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "❮d❯┃ef"], + &["abc", "┃def"], + &["abc", "❮d❯┃ef"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def┃ "], - &["abc\n", "def❮ ❯┃"], + &["abc", "def┃ "], + &["abc", "def❮ ❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def ❮\n", "❯┃ghi"], + &["abc", "def ┃", "ghi"], + &["abc", "def ❮", "❯┃ghi"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def┃\n", ""], - &["abc\n", "def❮\n", "❯┃"], + &["abc", "def┃", ""], + &["abc", "def❮", "❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], + &["abc", "def", "ghi┃", "jkl"], + &["abc", "def", "ghi❮", "❯┃jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &["abc\n", "def\n", "❮g❯┃hi\n", "jkl"], + &["abc", "def", "┃ghi", "jkl"], + &["abc", "def", "❮g❯┃hi", "jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "g❮h❯┃i\n", "jkl"], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "g❮h❯┃i", "jkl"], &shift_pressed(), move_caret_right, )?; @@ -1510,68 +1383,68 @@ pub mod test_big_sel_text { assert_move(&[" ┃abc"], &["┃❮ ❯abc"], &shift_pressed(), move_caret_left)?; assert_move(&["abc ┃"], &["abc┃❮ ❯"], &shift_pressed(), move_caret_left)?; assert_move( - &["abc┃\n", "d"], - &["ab┃❮c❯\n", "d"], + &["abc┃", "d"], + &["ab┃❮c❯", "d"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "┃d"], - &["abc┃❮\n", "❯d"], + &["abc", "┃d"], + &["abc┃❮", "❯d"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "┃"], - &["abc┃❮\n", "❯"], + &["abc", "┃"], + &["abc┃❮", "❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", " ┃def"], - &["abc\n", "┃❮ ❯def"], + &["abc", " ┃def"], + &["abc", "┃❮ ❯def"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "┃❮d❯ef"], + &["abc", "d┃ef"], + &["abc", "┃❮d❯ef"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "de┃f "], - &["abc\n", "d┃❮e❯f "], + &["abc", "de┃f "], + &["abc", "d┃❮e❯f "], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "┃"], - &["abc\n", "def┃❮\n", "❯"], + &["abc", "def", "┃"], + &["abc", "def┃❮", "❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "┃ghi\n", "jkl"], - &["abc\n", "def┃❮\n", "❯ghi\n", "jkl"], + &["abc", "def", "┃ghi", "jkl"], + &["abc", "def┃❮", "❯ghi", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "g┃hi\n", "jkl"], - &["abc\n", "def\n", "┃❮g❯hi\n", "jkl"], + &["abc", "def", "g┃hi", "jkl"], + &["abc", "def", "┃❮g❯hi", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "gh┃i\n", "jkl"], - &["abc\n", "def\n", "g┃❮h❯i\n", "jkl"], + &["abc", "def", "gh┃i", "jkl"], + &["abc", "def", "g┃❮h❯i", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi┃\n", "jkl"], - &["abc\n", "def\n", "gh┃❮i❯\n", "jkl"], + &["abc", "def", "ghi┃", "jkl"], + &["abc", "def", "gh┃❮i❯", "jkl"], &shift_pressed(), move_caret_left, )?; @@ -1591,170 +1464,170 @@ pub mod test_big_sel_text { assert_move(&["abc┃"], &["abc┃"], &shift_pressed(), move_caret_down)?; assert_move(&["abc┃ "], &["abc❮ ❯┃"], &shift_pressed(), move_caret_down)?; assert_move( - &["abc\n", "┃def"], - &["abc\n", "❮def❯┃"], + &["abc", "┃def"], + &["abc", "❮def❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef"], - &["abc\n", "d❮ef❯┃"], + &["abc", "d┃ef"], + &["abc", "d❮ef❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f"], - &["abc\n", "de❮f❯┃"], + &["abc", "de┃f"], + &["abc", "de❮f❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "def┃"], - &["abc\n", "def┃"], + &["abc", "def┃"], + &["abc", "def┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["┃abc\n", "def"], - &["❮abc\n", "❯┃def"], + &["┃abc", "def"], + &["❮abc", "❯┃def"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a┃bc\n", "def"], - &["a❮bc\n", "d❯┃ef"], + &["a┃bc", "def"], + &["a❮bc", "d❯┃ef"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃c\n", "def"], - &["ab❮c\n", "de❯┃f"], + &["ab┃c", "def"], + &["ab❮c", "de❯┃f"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc┃\n", "def"], - &["abc❮\n", "def❯┃"], + &["abc┃", "def"], + &["abc❮", "def❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "┃def \n", "ghi"], - &["abc\n", "❮def \n", "❯┃ghi"], + &["abc", "┃def ", "ghi"], + &["abc", "❮def ", "❯┃ghi"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "d┃ef \n", "ghi"], - &["abc\n", "d❮ef \n", "g❯┃hi"], + &["abc", "d┃ef ", "ghi"], + &["abc", "d❮ef ", "g❯┃hi"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃f \n", "ghi"], - &["abc\n", "de❮f \n", "gh❯┃i"], + &["abc", "de┃f ", "ghi"], + &["abc", "de❮f ", "gh❯┃i"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "def┃ \n", "ghi"], - &["abc\n", "def❮ \n", "ghi❯┃"], + &["abc", "def┃ ", "ghi"], + &["abc", "def❮ ", "ghi❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "def ┃\n", "ghi"], - &["abc\n", "def ❮\n", "ghi❯┃"], + &["abc", "def ┃", "ghi"], + &["abc", "def ❮", "ghi❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃\n", "ghi"], - &["abc\n", "de❮\n", "gh❯┃i"], + &["abc", "de┃", "ghi"], + &["abc", "de❮", "gh❯┃i"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc┃\n", "de"], - &["abc❮\n", "de❯┃"], + &["abc┃", "de"], + &["abc❮", "de❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃c\n", "de"], - &["ab❮c\n", "de❯┃"], + &["ab┃c", "de"], + &["ab❮c", "de❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a┃bc\n", "de"], - &["a❮bc\n", "d❯┃e"], + &["a┃bc", "de"], + &["a❮bc", "d❯┃e"], &shift_pressed(), move_caret_down, )?; assert_move( - &["┃abc\n", "de"], - &["❮abc\n", "❯┃de"], + &["┃abc", "de"], + &["❮abc", "❯┃de"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], - &["ab❮\n", "cd❯┃ef\n", "ghijkl\n", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], + &["ab❮", "cd❯┃ef", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cdef❮\n", "ghij❯┃kl\n", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], + &["ab", "cdef❮", "ghij❯┃kl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghijkl❮\n", "mnopqr❯┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef", "ghijkl❮", "mnopqr❯┃st"], &shift_pressed(), move_caret_down, )?; assert_move( - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], - &[" ❮ab\n", " ❯┃cdef\n", "ghijkl\n", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], + &[" ❮ab", " ❯┃cdef", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "┃cdef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "❮cdef\n", "❯┃ghijkl\n", "mnopqrst"], + &["ab", "┃cdef", "ghijkl", "mnopqrst"], + &["ab", "❮cdef", "❯┃ghijkl", "mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "❮ghijkl\n", "❯┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], + &["ab", "cdef", "❮ghijkl", "❯┃mnopqrst"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["abcdefgh❮\n", "ijklmn❯┃\n", "opqr\n", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["abcdefgh❮", "ijklmn❯┃", "opqr", "st"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn❮\n", "opqr❯┃\n", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdefgh", "ijklmn❮", "opqr❯┃", "st"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr❮\n", "st❯┃"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijklmn", "opqr❮", "st❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "┃st"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "❮st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "┃st"], + &["abcdefgh", "ijklmn", "opqr", "❮st❯┃"], &shift_pressed(), move_caret_down, )?; @@ -1803,158 +1676,158 @@ pub mod test_big_sel_text { assert_move(&["ab┃c"], &["┃❮ab❯c"], &shift_pressed(), move_caret_up)?; assert_move(&["abc┃"], &["┃❮abc❯"], &shift_pressed(), move_caret_up)?; assert_move( - &["┃abc\n", "def"], - &["┃abc\n", "def"], + &["┃abc", "def"], + &["┃abc", "def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "┃def"], - &["┃❮abc\n", "❯def"], + &["abc", "┃def"], + &["┃❮abc", "❯def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "d┃ef"], - &["a┃❮bc\n", "d❯ef"], + &["abc", "d┃ef"], + &["a┃❮bc", "d❯ef"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de┃f"], - &["ab┃❮c\n", "de❯f"], + &["abc", "de┃f"], + &["ab┃❮c", "de❯f"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def┃"], - &["abc┃❮\n", "def❯"], + &["abc", "def┃"], + &["abc┃❮", "def❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "┃ghi"], - &["abc\n", "┃❮def \n", "❯ghi"], + &["abc", "def ", "┃ghi"], + &["abc", "┃❮def ", "❯ghi"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "g┃hi"], - &["abc\n", "d┃❮ef \n", "g❯hi"], + &["abc", "def ", "g┃hi"], + &["abc", "d┃❮ef ", "g❯hi"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "gh┃i"], - &["abc\n", "de┃❮f \n", "gh❯i"], + &["abc", "def ", "gh┃i"], + &["abc", "de┃❮f ", "gh❯i"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def \n", "ghi┃"], - &["abc\n", "def┃❮ \n", "ghi❯"], + &["abc", "def ", "ghi┃"], + &["abc", "def┃❮ ", "ghi❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de\n", "ghi┃"], - &["abc\n", "de┃❮\n", "ghi❯"], + &["abc", "de", "ghi┃"], + &["abc", "de┃❮", "ghi❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de┃"], - &["ab┃❮c\n", "de❯"], + &["abc", "de┃"], + &["ab┃❮c", "de❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "d┃e"], - &["a┃❮bc\n", "d❯e"], + &["abc", "d┃e"], + &["a┃❮bc", "d❯e"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "┃de"], - &["┃❮abc\n", "❯de"], + &["abc", "┃de"], + &["┃❮abc", "❯de"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "mnopqrst❯"], + &["ab", "cdef", "ghijkl", "mnopqrst┃"], + &["ab", "cdef", "ghijkl┃❮", "mnopqrst❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], - &["ab\n", "cdef┃❮\n", "ghijkl❯\n", "mnopqrst"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], + &["ab", "cdef┃❮", "ghijkl❯", "mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], - &["ab\n", "cdef\n", "┃❮ghijkl\n", "❯mnopqrst"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], + &["ab", "cdef", "┃❮ghijkl", "❯mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ┃❮ab\n", " ❯cdef\n", "ghijkl\n", "mnopqrst"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], + &[" ┃❮ab", " ❯cdef", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "mnopqr❯st"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], + &["ab", "cdef", "ghijkl┃❮", "mnopqr❯st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cde┃f\n", "ghijkl\n", "mnopqrst"], - &["ab┃❮\n", "cde❯f\n", "ghijkl\n", "mnopqrst"], + &["ab", "cde┃f", "ghijkl", "mnopqrst"], + &["ab┃❮", "cde❯f", "ghijkl", "mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], - &["abcdefgh\n", "ijklmn\n", "op┃❮qr\n", "st❯"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], + &["abcdefgh", "ijklmn", "op┃❮qr", "st❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], - &["abcdefgh\n", "ijkl┃❮mn\n", "opqr❯\n", "st"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], + &["abcdefgh", "ijkl┃❮mn", "opqr❯", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], - &["abcdef┃❮gh\n", "ijklmn❯\n", "opqr\n", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], + &["abcdef┃❮gh", "ijklmn❯", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], - &["┃❮abcdefgh❯\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], + &["┃❮abcdefgh❯", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefg┃h\n", "ijklmn\n", "opqr\n", "st"], - &["┃❮abcdefg❯h\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefg┃h", "ijklmn", "opqr", "st"], + &["┃❮abcdefg❯h", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["a┃bcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃❮a❯bcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["a┃bcdefgh", "ijklmn", "opqr", "st"], + &["┃❮a❯bcdefgh", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; assert_move( - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], - &["┃abcdefgh\n", "ijklmn\n", "opqr\n", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], + &["┃abcdefgh", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_up, )?; @@ -2031,74 +1904,74 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "def\n", "ghi┃"], - &["abc\n", "def\n", "┃❮ghi❯"], + &["abc", "def", "ghi┃"], + &["abc", "def", "┃❮ghi❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def┃\n", "ghi"], - &["abc\n", "┃❮def❯\n", "ghi"], + &["abc", "def┃", "ghi"], + &["abc", "┃❮def❯", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc┃\n", "def\n", "ghi"], - &["┃❮abc❯\n", "def\n", "ghi"], + &["abc┃", "def", "ghi"], + &["┃❮abc❯", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["┃abc\n", "def\n", "ghi"], - &["┃abc\n", "def\n", "ghi"], + &["┃abc", "def", "ghi"], + &["┃abc", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "┃def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["abc", "┃def", "ghi"], + &["abc", "┃def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", "┃ghi"], - &["abc\n", "def\n", "┃ghi"], + &["abc", "def", "┃ghi"], + &["abc", "def", "┃ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &[" ┃abc\n", "def\n", "ghi"], - &["┃❮ ❯abc\n", "def\n", "ghi"], + &[" ┃abc", "def", "ghi"], + &["┃❮ ❯abc", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", " ┃def\n", "ghi"], - &["abc\n", "┃❮ ❯def\n", "ghi"], + &["abc", " ┃def", "ghi"], + &["abc", "┃❮ ❯def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", " ┃ghi"], - &["abc\n", "def\n", "┃❮ ❯ghi"], + &["abc", "def", " ┃ghi"], + &["abc", "def", "┃❮ ❯ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["┃ abc\n", "def\n", "ghi"], - &["❮ ❯┃abc\n", "def\n", "ghi"], + &["┃ abc", "def", "ghi"], + &["❮ ❯┃abc", "def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "┃ def\n", "ghi"], - &["abc\n", "❮ ❯┃def\n", "ghi"], + &["abc", "┃ def", "ghi"], + &["abc", "❮ ❯┃def", "ghi"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", "┃ ghi"], - &["abc\n", "def\n", "❮ ❯┃ghi"], + &["abc", "def", "┃ ghi"], + &["abc", "def", "❮ ❯┃ghi"], &shift_pressed(), move_caret_home, )?; @@ -2121,38 +1994,38 @@ pub mod test_big_sel_text { assert_move(&[" abc┃ "], &[" abc❮ ❯┃"], &shift_pressed(), move_caret_end)?; assert_move(&[" ab┃c"], &[" ab❮c❯┃"], &shift_pressed(), move_caret_end)?; assert_move( - &["abc\n", "def\n", "┃ghi"], - &["abc\n", "def\n", "❮ghi❯┃"], + &["abc", "def", "┃ghi"], + &["abc", "def", "❮ghi❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "┃def\n", "ghi"], - &["abc\n", "❮def❯┃\n", "ghi"], + &["abc", "┃def", "ghi"], + &["abc", "❮def❯┃", "ghi"], &shift_pressed(), move_caret_end, )?; assert_move( - &["┃abc\n", "def\n", "ghi"], - &["❮abc❯┃\n", "def\n", "ghi"], + &["┃abc", "def", "ghi"], + &["❮abc❯┃", "def", "ghi"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "def\n", "┃ghi "], - &["abc\n", "def\n", "❮ghi ❯┃"], + &["abc", "def", "┃ghi "], + &["abc", "def", "❮ghi ❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "┃def \n", "ghi"], - &["abc\n", "❮def ❯┃\n", "ghi"], + &["abc", "┃def ", "ghi"], + &["abc", "❮def ❯┃", "ghi"], &shift_pressed(), move_caret_end, )?; assert_move( - &["┃abc \n", "def\n", "ghi"], - &["❮abc ❯┃\n", "def\n", "ghi"], + &["┃abc ", "def", "ghi"], + &["❮abc ❯┃", "def", "ghi"], &shift_pressed(), move_caret_end, )?; @@ -2172,94 +2045,89 @@ pub mod test_big_sel_text { assert_move(&["┃❮ ❯abc"], &[" ┃abc"], &no_mods(), move_caret_right)?; assert_move(&["a┃❮b❯c"], &["ab┃c"], &no_mods(), move_caret_right)?; assert_move( - &["abc❮\n", "❯┃d"], - &["abc\n", "┃d"], + &["abc❮", "❯┃d"], + &["abc", "┃d"], &no_mods(), move_caret_right, )?; assert_move( - &["abc┃❮\n", "❯d"], - &["abc\n", "┃d"], + &["abc┃❮", "❯d"], + &["abc", "┃d"], + &no_mods(), + move_caret_right, + )?; + assert_move(&["abc┃❮", "❯"], &["abc", "┃"], &no_mods(), move_caret_right)?; + assert_move( + &["abc", "❮d❯┃ef"], + &["abc", "d┃ef"], &no_mods(), move_caret_right, )?; assert_move( - &["abc┃❮\n", "❯"], - &["abc\n", "┃"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "❮d❯┃ef"], - &["abc\n", "d┃ef"], - &no_mods(), - move_caret_right, - )?; - assert_move( - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], - &["abc\n", "def\n", "ghi\n", "┃jkl"], + &["abc", "def", "ghi❮", "❯┃jkl"], + &["abc", "def", "ghi", "┃jkl"], &no_mods(), move_caret_right, )?; assert_move(&["❮ab❯┃c"], &["ab┃c"], &no_mods(), move_caret_right)?; assert_move(&["❮abc❯┃"], &["abc┃"], &no_mods(), move_caret_right)?; assert_move( - &["ab┃❮c\n", "❯def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["ab┃❮c", "❯def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["ab❮c\n", "❯┃def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["ab❮c", "❯┃def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["a┃❮bc\n", "❯def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["a┃❮bc", "❯def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["┃❮abc\n", "❯def\n", "ghi"], - &["abc\n", "┃def\n", "ghi"], + &["┃❮abc", "❯def", "ghi"], + &["abc", "┃def", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["a┃❮bc\n", "d❯ef\n", "ghi"], - &["abc\n", "d┃ef\n", "ghi"], + &["a┃❮bc", "d❯ef", "ghi"], + &["abc", "d┃ef", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["┃❮abc\n", "def❯\n", "ghi"], - &["abc\n", "def┃\n", "ghi"], + &["┃❮abc", "def❯", "ghi"], + &["abc", "def┃", "ghi"], &no_mods(), move_caret_right, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["ab", "cdef", "ghijkl", "mnopqrst┃"], &no_mods(), move_caret_right, )?; assert_move( - &["┃❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqrst┃"], + &["┃❮ab", "cdef", "ghijkl", "mnopqrst❯"], + &["ab", "cdef", "ghijkl", "mnopqrst┃"], &no_mods(), move_caret_right, )?; assert_move( - &["ab\n", "c❮def\n", "ghijkl\n", "mno❯┃pqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "mno┃pqrst"], + &["ab", "c❮def", "ghijkl", "mno❯┃pqrst"], + &["ab", "cdef", "ghijkl", "mno┃pqrst"], &no_mods(), move_caret_right, )?; assert_move( - &["ab\n", "c┃❮def\n", "ghijkl\n", "mno❯pqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "mno┃pqrst"], + &["ab", "c┃❮def", "ghijkl", "mno❯pqrst"], + &["ab", "cdef", "ghijkl", "mno┃pqrst"], &no_mods(), move_caret_right, )?; @@ -2279,94 +2147,94 @@ pub mod test_big_sel_text { assert_move(&["┃❮ ❯abc"], &["┃ abc"], &no_mods(), move_caret_left)?; assert_move(&["a┃❮b❯c"], &["a┃bc"], &no_mods(), move_caret_left)?; assert_move( - &["abc❮\n", "❯┃d"], - &["abc┃\n", "d"], + &["abc❮", "❯┃d"], + &["abc┃", "d"], &no_mods(), move_caret_left, )?; assert_move( - &["abc┃❮\n", "❯d"], - &["abc┃\n", "d"], + &["abc┃❮", "❯d"], + &["abc┃", "d"], &no_mods(), move_caret_left, )?; assert_move( - &["abc┃❮\n", "❯"], - &["abc┃\n", ""], + &["abc┃❮", "❯"], + &["abc┃", ""], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "❮d❯┃ef"], - &["abc\n", "┃def"], + &["abc", "❮d❯┃ef"], + &["abc", "┃def"], &no_mods(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], - &["abc\n", "def\n", "ghi┃\n", "jkl"], + &["abc", "def", "ghi❮", "❯┃jkl"], + &["abc", "def", "ghi┃", "jkl"], &no_mods(), move_caret_left, )?; assert_move(&["❮ab❯┃c"], &["┃abc"], &no_mods(), move_caret_left)?; assert_move(&["❮abc❯┃"], &["┃abc"], &no_mods(), move_caret_left)?; assert_move( - &["ab┃❮c\n", "❯def\n", "ghi"], - &["ab┃c\n", "def\n", "ghi"], + &["ab┃❮c", "❯def", "ghi"], + &["ab┃c", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["ab❮c\n", "❯┃def\n", "ghi"], - &["ab┃c\n", "def\n", "ghi"], + &["ab❮c", "❯┃def", "ghi"], + &["ab┃c", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["a┃❮bc\n", "❯def\n", "ghi"], - &["a┃bc\n", "def\n", "ghi"], + &["a┃❮bc", "❯def", "ghi"], + &["a┃bc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["┃❮abc\n", "❯def\n", "ghi"], - &["┃abc\n", "def\n", "ghi"], + &["┃❮abc", "❯def", "ghi"], + &["┃abc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["a┃❮bc\n", "d❯ef\n", "ghi"], - &["a┃bc\n", "def\n", "ghi"], + &["a┃❮bc", "d❯ef", "ghi"], + &["a┃bc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["┃❮abc\n", "def❯\n", "ghi"], - &["┃abc\n", "def\n", "ghi"], + &["┃❮abc", "def❯", "ghi"], + &["┃abc", "def", "ghi"], &no_mods(), move_caret_left, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["┃ab\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["┃ab", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?; assert_move( - &["┃❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯"], - &["┃ab\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["┃❮ab", "cdef", "ghijkl", "mnopqrst❯"], + &["┃ab", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?; assert_move( - &["ab\n", "c❮def\n", "ghijkl\n", "mno❯┃pqrst"], - &["ab\n", "c┃def\n", "ghijkl\n", "mnopqrst"], + &["ab", "c❮def", "ghijkl", "mno❯┃pqrst"], + &["ab", "c┃def", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?; assert_move( - &["ab\n", "c┃❮def\n", "ghijkl\n", "mno❯pqrst"], - &["ab\n", "c┃def\n", "ghijkl\n", "mnopqrst"], + &["ab", "c┃❮def", "ghijkl", "mno❯pqrst"], + &["ab", "c┃def", "ghijkl", "mnopqrst"], &no_mods(), move_caret_left, )?;*/ @@ -2384,164 +2252,164 @@ pub mod test_big_sel_text { assert_move(&["ab❮c❯┃"], &["abc┃"], &no_mods(), move_caret_down)?; assert_move(&["abc┃❮ ❯"], &["abc ┃"], &no_mods(), move_caret_down)?; assert_move( - &["abc\n", "┃❮def❯"], - &["abc\n", "def┃"], + &["abc", "┃❮def❯"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d┃❮ef❯"], - &["abc\n", "def┃"], + &["abc", "d┃❮ef❯"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["abc\n", "def┃"], + &["abc", "de┃❮f❯"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["❮abc\n", "❯┃def"], - &["abc\n", "┃def"], + &["❮abc", "❯┃def"], + &["abc", "┃def"], &no_mods(), move_caret_down, )?; assert_move( - &["a❮bc\n", "d❯┃ef"], - &["abc\n", "d┃ef"], + &["a❮bc", "d❯┃ef"], + &["abc", "d┃ef"], &no_mods(), move_caret_down, )?; assert_move( - &["ab┃❮c\n", "de❯f"], - &["abc\n", "de┃f"], + &["ab┃❮c", "de❯f"], + &["abc", "de┃f"], &no_mods(), move_caret_down, )?; assert_move( - &["abc❮\n", "def❯┃"], - &["abc\n", "def┃"], + &["abc❮", "def❯┃"], + &["abc", "def┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "┃❮def \n", "❯ghi"], - &["abc\n", "def \n", "┃ghi"], + &["abc", "┃❮def ", "❯ghi"], + &["abc", "def ", "┃ghi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "d❮ef \n", "g❯┃hi"], - &["abc\n", "def \n", "g┃hi"], + &["abc", "d❮ef ", "g❯┃hi"], + &["abc", "def ", "g┃hi"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de❮f \n", "gh❯┃i"], - &["abc\n", "def \n", "gh┃i"], + &["abc", "de❮f ", "gh❯┃i"], + &["abc", "def ", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def❮ \n", "ghi❯┃"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def❮ ", "ghi❯┃"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "def ❮\n", "ghi❯┃"], - &["abc\n", "def \n", "ghi┃"], + &["abc", "def ❮", "ghi❯┃"], + &["abc", "def ", "ghi┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abc\n", "de❮\n", "gh❯┃i"], - &["abc\n", "de\n", "gh┃i"], + &["abc", "de❮", "gh❯┃i"], + &["abc", "de", "gh┃i"], &no_mods(), move_caret_down, )?; assert_move( - &["abc┃❮\n", "de❯"], - &["abc\n", "de┃"], + &["abc┃❮", "de❯"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["ab❮c\n", "de❯┃"], - &["abc\n", "de┃"], + &["ab❮c", "de❯┃"], + &["abc", "de┃"], &no_mods(), move_caret_down, )?; assert_move( - &["a┃❮bc\n", "d❯e"], - &["abc\n", "d┃e"], + &["a┃❮bc", "d❯e"], + &["abc", "d┃e"], &no_mods(), move_caret_down, )?; assert_move( - &["❮abc\n", "❯┃de"], - &["abc\n", "┃de"], + &["❮abc", "❯┃de"], + &["abc", "┃de"], &no_mods(), move_caret_down, )?; assert_move( - &["ab❮\n", "cd❯┃ef\n", "ghijkl\n", "mnopqrst"], - &["ab\n", "cd┃ef\n", "ghijkl\n", "mnopqrst"], + &["ab❮", "cd❯┃ef", "ghijkl", "mnopqrst"], + &["ab", "cd┃ef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef┃❮\n", "ghij❯kl\n", "mnopqrst"], - &["ab\n", "cdef\n", "ghij┃kl\n", "mnopqrst"], + &["ab", "cdef┃❮", "ghij❯kl", "mnopqrst"], + &["ab", "cdef", "ghij┃kl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl❮\n", "mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl\n", "mnopqr┃st"], + &["ab", "cdef", "ghijkl❮", "mnopqr❯┃st"], + &["ab", "cdef", "ghijkl", "mnopqr┃st"], &no_mods(), move_caret_down, )?; assert_move( - &[" ❮ab\n", " ❯┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ab\n", " ┃cdef\n", "ghijkl\n", "mnopqrst"], + &[" ❮ab", " ❯┃cdef", "ghijkl", "mnopqrst"], + &[" ab", " ┃cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "┃❮cdef\n", "❯ghijkl\n", "mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["ab", "┃❮cdef", "❯ghijkl", "mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "❮ghijkl\n", "❯┃mnopqrst"], - &["ab\n", "cdef\n", "ghijkl\n", "┃mnopqrst"], + &["ab", "cdef", "❮ghijkl", "❯┃mnopqrst"], + &["ab", "cdef", "ghijkl", "┃mnopqrst"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh❮\n", "ijklmn❯┃\n", "opqr\n", "st"], - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], + &["abcdefgh❮", "ijklmn❯┃", "opqr", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn❮\n", "opqr❯┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], + &["abcdefgh", "ijklmn❮", "opqr❯┃", "st"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr❮\n", "st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr❮", "st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "❮st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "st┃"], + &["abcdefgh", "ijklmn", "opqr", "❮st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "st┃"], &no_mods(), move_caret_down, )?; @@ -2583,164 +2451,164 @@ pub mod test_big_sel_text { assert_move(&["ab❮c❯┃"], &["ab┃c"], &no_mods(), move_caret_up)?; assert_move(&["abc┃❮ ❯"], &["abc┃ "], &no_mods(), move_caret_up)?; assert_move( - &["abc\n", "┃❮def❯"], - &["abc\n", "┃def"], + &["abc", "┃❮def❯"], + &["abc", "┃def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d┃❮ef❯"], - &["abc\n", "d┃ef"], + &["abc", "d┃❮ef❯"], + &["abc", "d┃ef"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["abc\n", "de┃f"], + &["abc", "de┃❮f❯"], + &["abc", "de┃f"], &no_mods(), move_caret_up, )?; assert_move( - &["❮abc\n", "❯┃def"], - &["┃abc\n", "def"], + &["❮abc", "❯┃def"], + &["┃abc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["a❮bc\n", "d❯┃ef"], - &["a┃bc\n", "def"], + &["a❮bc", "d❯┃ef"], + &["a┃bc", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["ab┃❮c\n", "de❯f"], - &["ab┃c\n", "def"], + &["ab┃❮c", "de❯f"], + &["ab┃c", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc❮\n", "def❯┃"], - &["abc┃\n", "def"], + &["abc❮", "def❯┃"], + &["abc┃", "def"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "┃❮def \n", "❯ghi"], - &["abc\n", "┃def \n", "ghi"], + &["abc", "┃❮def ", "❯ghi"], + &["abc", "┃def ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "d❮ef \n", "g❯┃hi"], - &["abc\n", "d┃ef \n", "ghi"], + &["abc", "d❮ef ", "g❯┃hi"], + &["abc", "d┃ef ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de┃❮f \n", "gh❯i"], - &["abc\n", "de┃f \n", "ghi"], + &["abc", "de┃❮f ", "gh❯i"], + &["abc", "de┃f ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def❮ \n", "ghi❯┃"], - &["abc\n", "def┃ \n", "ghi"], + &["abc", "def❮ ", "ghi❯┃"], + &["abc", "def┃ ", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "def ❮\n", "ghi❯┃"], - &["abc\n", "def ┃\n", "ghi"], + &["abc", "def ❮", "ghi❯┃"], + &["abc", "def ┃", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc\n", "de❮\n", "gh❯┃i"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "de❮", "gh❯┃i"], + &["abc", "de┃", "ghi"], &no_mods(), move_caret_up, )?; assert_move( - &["abc┃❮\n", "de❯"], - &["abc┃\n", "de"], + &["abc┃❮", "de❯"], + &["abc┃", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["ab❮c\n", "de❯┃"], - &["ab┃c\n", "de"], + &["ab❮c", "de❯┃"], + &["ab┃c", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["a┃❮bc\n", "d❯e"], - &["a┃bc\n", "de"], + &["a┃❮bc", "d❯e"], + &["a┃bc", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["❮abc\n", "❯┃de"], - &["┃abc\n", "de"], + &["❮abc", "❯┃de"], + &["┃abc", "de"], &no_mods(), move_caret_up, )?; assert_move( - &["ab❮\n", "cd❯┃ef\n", "ghijkl\n", "mnopqrst"], - &["ab┃\n", "cdef\n", "ghijkl\n", "mnopqrst"], + &["ab❮", "cd❯┃ef", "ghijkl", "mnopqrst"], + &["ab┃", "cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef┃❮\n", "ghij❯kl\n", "mnopqrst"], - &["ab\n", "cdef┃\n", "ghijkl\n", "mnopqrst"], + &["ab", "cdef┃❮", "ghij❯kl", "mnopqrst"], + &["ab", "cdef┃", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl❮\n", "mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl┃\n", "mnopqrst"], + &["ab", "cdef", "ghijkl❮", "mnopqr❯┃st"], + &["ab", "cdef", "ghijkl┃", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &[" ❮ab\n", " ❯┃cdef\n", "ghijkl\n", "mnopqrst"], - &[" ┃ab\n", " cdef\n", "ghijkl\n", "mnopqrst"], + &[" ❮ab", " ❯┃cdef", "ghijkl", "mnopqrst"], + &[" ┃ab", " cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "┃❮cdef\n", "❯ghijkl\n", "mnopqrst"], - &["ab\n", "┃cdef\n", "ghijkl\n", "mnopqrst"], + &["ab", "┃❮cdef", "❯ghijkl", "mnopqrst"], + &["ab", "┃cdef", "ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "❮ghijkl\n", "❯┃mnopqrst"], - &["ab\n", "cdef\n", "┃ghijkl\n", "mnopqrst"], + &["ab", "cdef", "❮ghijkl", "❯┃mnopqrst"], + &["ab", "cdef", "┃ghijkl", "mnopqrst"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh❮\n", "ijklmn❯┃\n", "opqr\n", "st"], - &["abcdefgh┃\n", "ijklmn\n", "opqr\n", "st"], + &["abcdefgh❮", "ijklmn❯┃", "opqr", "st"], + &["abcdefgh┃", "ijklmn", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn❮\n", "opqr❯┃\n", "st"], - &["abcdefgh\n", "ijklmn┃\n", "opqr\n", "st"], + &["abcdefgh", "ijklmn❮", "opqr❯┃", "st"], + &["abcdefgh", "ijklmn┃", "opqr", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr❮\n", "st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr┃\n", "st"], + &["abcdefgh", "ijklmn", "opqr❮", "st❯┃"], + &["abcdefgh", "ijklmn", "opqr┃", "st"], &no_mods(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "opqr\n", "❮st❯┃"], - &["abcdefgh\n", "ijklmn\n", "opqr\n", "┃st"], + &["abcdefgh", "ijklmn", "opqr", "❮st❯┃"], + &["abcdefgh", "ijklmn", "opqr", "┃st"], &no_mods(), move_caret_up, )?; @@ -2804,32 +2672,32 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "d❮e❯┃\n", "ghi"], - &["abc\n", "┃de\n", "ghi"], + &["abc", "d❮e❯┃", "ghi"], + &["abc", "┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " ❮d❯┃e\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["abc", " ❮d❯┃e", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["❮abc\n", "❯┃ de\n", "ghi"], - &["abc\n", " ┃de\n", "ghi"], + &["❮abc", "❯┃ de", "ghi"], + &["abc", " ┃de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc\n", " ┃❮de\n❯", "ghi"], - &["abc\n", "┃ de\n", "ghi"], + &["abc", " ┃❮de❯", "ghi"], + &["abc", "┃ de", "ghi"], &no_mods(), move_caret_home, )?; assert_move( - &["abc┃❮\n", "de\n", "ghi❯"], - &["┃abc\n", "de\n", "ghi"], + &["abc┃❮", "de", "ghi❯"], + &["┃abc", "de", "ghi"], &no_mods(), move_caret_home, )?; @@ -2856,26 +2724,26 @@ pub mod test_big_sel_text { )?; assert_move( - &["abc\n", "┃❮de\n", "ghi❯"], - &["abc\n", "de┃\n", "ghi"], + &["abc", "┃❮de", "ghi❯"], + &["abc", "de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["❮abc\n", " d❯┃e\n", "ghi"], - &["abc\n", " de┃\n", "ghi"], + &["❮abc", " d❯┃e", "ghi"], + &["abc", " de┃", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["┃❮abc\n", "de\n", "ghi❯"], - &["abc┃\n", "de\n", "ghi"], + &["┃❮abc", "de", "ghi❯"], + &["abc┃", "de", "ghi"], &no_mods(), move_caret_end, )?; assert_move( - &["abc\n", "de\n", "g┃❮hi❯"], - &["abc\n", "de\n", "ghi┃"], + &["abc", "de", "g┃❮hi❯"], + &["abc", "de", "ghi┃"], &no_mods(), move_caret_end, )?; @@ -2900,50 +2768,50 @@ pub mod test_big_sel_text { assert_move(&["a❮bc❯┃"], &["a❮bc❯┃"], &shift_pressed(), move_caret_right)?; assert_move(&["ab❮c❯┃"], &["ab❮c❯┃"], &shift_pressed(), move_caret_right)?; assert_move( - &["abc❮\n", "❯┃d"], - &["abc❮\n", "d❯┃"], + &["abc❮", "❯┃d"], + &["abc❮", "d❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c❯┃\n", ""], - &["ab❮c\n", "❯┃"], + &["ab❮c❯┃", ""], + &["ab❮c", "❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c❯┃\n", "d"], - &["ab❮c\n", "❯┃d"], + &["ab❮c❯┃", "d"], + &["ab❮c", "❯┃d"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], - &["abc\n", "def\n", "ghi❮\n", "j❯┃kl"], + &["abc", "def", "ghi❮", "❯┃jkl"], + &["abc", "def", "ghi❮", "j❯┃kl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c\n", "def\n", "ghi\n", "❯┃jkl"], - &["ab❮c\n", "def\n", "ghi\n", "j❯┃kl"], + &["ab❮c", "def", "ghi", "❯┃jkl"], + &["ab❮c", "def", "ghi", "j❯┃kl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["ab❮c\n", "def\n", "❯┃ghi\n", "jkl"], - &["ab❮c\n", "def\n", "g❯┃hi\n", "jkl"], + &["ab❮c", "def", "❯┃ghi", "jkl"], + &["ab❮c", "def", "g❯┃hi", "jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jk❯┃l"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["❮abc", "def", "ghi", "jk❯┃l"], + &["❮abc", "def", "ghi", "jkl❯┃"], &shift_pressed(), move_caret_right, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi", "jkl❯┃"], &shift_pressed(), move_caret_right, )?; @@ -2967,38 +2835,38 @@ pub mod test_big_sel_text { move_caret_left, )?; assert_move( - &["abc┃❮\n", "❯d"], - &["ab┃❮c\n", "❯d"], + &["abc┃❮", "❯d"], + &["ab┃❮c", "❯d"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "┃❮d❯"], - &["abc┃❮\n", "d❯"], + &["abc", "┃❮d❯"], + &["abc┃❮", "d❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["ab┃❮c\n", "❯"], - &["a┃❮bc\n", "❯"], + &["ab┃❮c", "❯"], + &["a┃❮bc", "❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def┃❮\n", "ghi\n", "j❯kl"], - &["abc\n", "de┃❮f\n", "ghi\n", "j❯kl"], + &["abc", "def┃❮", "ghi", "j❯kl"], + &["abc", "de┃❮f", "ghi", "j❯kl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["a┃❮bc\n", "def\n", "ghi\n", "jkl❯"], - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], + &["a┃❮bc", "def", "ghi", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def\n", "ghi\n", "┃❮jkl❯"], - &["abc\n", "def\n", "ghi┃❮\n", "jkl❯"], + &["abc", "def", "ghi", "┃❮jkl❯"], + &["abc", "def", "ghi┃❮", "jkl❯"], &shift_pressed(), move_caret_left, )?; @@ -3020,44 +2888,44 @@ pub mod test_big_sel_text { assert_move(&["❮a❯┃"], &["┃a"], &shift_pressed(), move_caret_up)?; assert_move(&["❮a❯┃bc"], &["┃abc"], &shift_pressed(), move_caret_up)?; assert_move( - &["❮a❯┃bc\n", "d"], - &["┃abc\n", "d"], + &["❮a❯┃bc", "d"], + &["┃abc", "d"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de❮f❯┃"], - &["abc┃❮\n", "de❯f"], + &["abc", "de❮f❯┃"], + &["abc┃❮", "de❯f"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["ab┃❮c\n", "def❯"], + &["abc", "de┃❮f❯"], + &["ab┃❮c", "def❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab┃❮c\n", "def❯"], - &["┃❮abc\n", "def❯"], + &["ab┃❮c", "def❯"], + &["┃❮abc", "def❯"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "❯mnopqrst"], + &["ab", "cdef", "ghijkl", "❮mnopqr❯┃st"], + &["ab", "cdef", "ghijkl┃❮", "❯mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqrs❯┃t"], - &["ab\n", "cdef\n", "ghijkl┃❮\n", "❯mnopqrst"], + &["ab", "cdef", "ghijkl", "❮mnopqrs❯┃t"], + &["ab", "cdef", "ghijkl┃❮", "❯mnopqrst"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "┃❮o❯pqr\n", "st"], - &["abcdefgh\n", "┃❮ijklmn\n", "o❯pqr\n", "st"], + &["abcdefgh", "ijklmn", "┃❮o❯pqr", "st"], + &["abcdefgh", "┃❮ijklmn", "o❯pqr", "st"], &shift_pressed(), move_caret_up, )?; @@ -3075,56 +2943,56 @@ pub mod test_big_sel_text { assert_move(&["┃❮ab❯c"], &["ab❮c❯┃"], &shift_pressed(), move_caret_down)?; assert_move(&["┃❮a❯bc"], &["a❮bc❯┃"], &shift_pressed(), move_caret_down)?; assert_move( - &["❮a❯┃bc\n", "d"], - &["❮abc\n", "d❯┃"], + &["❮a❯┃bc", "d"], + &["❮abc", "d❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮a❯┃bc\n", "de"], - &["❮abc\n", "d❯┃e"], + &["❮a❯┃bc", "de"], + &["❮abc", "d❯┃e"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮abc\n", "d❯┃e"], - &["❮abc\n", "de❯┃"], + &["❮abc", "d❯┃e"], + &["❮abc", "de❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮a❯┃bc\n", ""], - &["❮abc\n", "❯┃"], + &["❮a❯┃bc", ""], + &["❮abc", "❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqrst❯┃"], + &["ab", "cdef", "ghijkl", "❮mnopqr❯┃st"], + &["ab", "cdef", "ghijkl", "❮mnopqrst❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqr❯┃st"], - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["a❮b", "cdef", "ghijkl", "mnopqr❯┃st"], + &["a❮b", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcd❮efgh❯┃\n", "ijklmn\n", "opqr\n", "st"], - &["abcd❮efgh\n", "ijklmn❯┃\n", "opqr\n", "st"], + &["abcd❮efgh❯┃", "ijklmn", "opqr", "st"], + &["abcd❮efgh", "ijklmn❯┃", "opqr", "st"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcd❮e❯┃fgh\n", "ijklmn\n", "opqr\n", "st"], - &["abcd❮efgh\n", "ijklm❯┃n\n", "opqr\n", "st"], + &["abcd❮e❯┃fgh", "ijklmn", "opqr", "st"], + &["abcd❮efgh", "ijklm❯┃n", "opqr", "st"], &shift_pressed(), move_caret_down, )?; @@ -3149,50 +3017,50 @@ pub mod test_big_sel_text { )?; assert_move(&["ab❮c❯┃"], &["┃❮ab❯c"], &shift_pressed(), move_caret_home)?; assert_move( - &["abc\n", "de❮f❯┃"], - &["abc\n", "┃❮de❯f"], + &["abc", "de❮f❯┃"], + &["abc", "┃❮de❯f"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "de┃❮f❯"], - &["abc\n", "┃❮def❯"], + &["abc", "de┃❮f❯"], + &["abc", "┃❮def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &["ab┃❮c\n", "def❯"], - &["┃❮abc\n", "def❯"], + &["ab┃❮c", "def❯"], + &["┃❮abc", "def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &[" ab┃❮c\n", "def❯"], - &[" ┃❮abc\n", "def❯"], + &[" ab┃❮c", "def❯"], + &[" ┃❮abc", "def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &[" ┃❮abc\n", "def❯"], - &["┃❮ abc\n", "def❯"], + &[" ┃❮abc", "def❯"], + &["┃❮ abc", "def❯"], &shift_pressed(), move_caret_home, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "┃❮mnopqr❯st"], - &["ab\n", "cdef\n", "ghijkl\n", "┃❮mnopqr❯st"], + &["ab", "cdef", "ghijkl", "┃❮mnopqr❯st"], + &["ab", "cdef", "ghijkl", "┃❮mnopqr❯st"], &shift_pressed(), move_caret_home, )?; assert_move( - &["ab\n", "cdef\n", "gh┃❮ijkl❯\n", "mnopqrst"], - &["ab\n", "cdef\n", "┃❮ghijkl❯\n", "mnopqrst"], + &["ab", "cdef", "gh┃❮ijkl❯", "mnopqrst"], + &["ab", "cdef", "┃❮ghijkl❯", "mnopqrst"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abcdefgh\n", "ijklmn\n", "op❮qr❯┃\n", "st"], - &["abcdefgh\n", "ijklmn\n", "┃❮op❯qr\n", "st"], + &["abcdefgh", "ijklmn", "op❮qr❯┃", "st"], + &["abcdefgh", "ijklmn", "┃❮op❯qr", "st"], &shift_pressed(), move_caret_home, )?; @@ -3210,44 +3078,44 @@ pub mod test_big_sel_text { assert_move(&["┃❮ab❯c"], &["ab❮c❯┃"], &shift_pressed(), move_caret_end)?; assert_move(&["┃❮a❯bc"], &["a❮bc❯┃"], &shift_pressed(), move_caret_end)?; assert_move( - &["❮a❯┃bc\n", "d"], - &["❮abc❯┃\n", "d"], + &["❮a❯┃bc", "d"], + &["❮abc❯┃", "d"], &shift_pressed(), move_caret_end, )?; assert_move( - &["❮a❯┃bc \n", "de"], - &["❮abc ❯┃\n", "de"], + &["❮a❯┃bc ", "de"], + &["❮abc ❯┃", "de"], &shift_pressed(), move_caret_end, )?; assert_move( - &["❮abc\n", "d❯┃e"], - &["❮abc\n", "de❯┃"], + &["❮abc", "d❯┃e"], + &["❮abc", "de❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqr❯┃st"], - &["ab\n", "cdef\n", "ghijkl\n", "❮mnopqrst❯┃"], + &["ab", "cdef", "ghijkl", "❮mnopqr❯┃st"], + &["ab", "cdef", "ghijkl", "❮mnopqrst❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqr❯┃st"], - &["a❮b\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["a❮b", "cdef", "ghijkl", "mnopqr❯┃st"], + &["a❮b", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], - &["❮ab\n", "cdef\n", "ghijkl\n", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], + &["❮ab", "cdef", "ghijkl", "mnopqrst❯┃"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abcd❮e❯┃fgh\n", "ijklmn\n", "opqr\n", "st"], - &["abcd❮efgh❯┃\n", "ijklmn\n", "opqr\n", "st"], + &["abcd❮e❯┃fgh", "ijklmn", "opqr", "st"], + &["abcd❮efgh❯┃", "ijklmn", "opqr", "st"], &shift_pressed(), move_caret_end, )?; @@ -3263,20 +3131,20 @@ pub mod test_big_sel_text { assert_move(&["a┃❮bc❯"], &["ab┃❮c❯"], &shift_pressed(), move_caret_right)?; assert_move(&["┃❮abc❯"], &["a┃❮bc❯"], &shift_pressed(), move_caret_right)?; assert_move( - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], - &["a┃❮bc\n", "def\n", "ghi\n", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], + &["a┃❮bc", "def", "ghi", "jkl❯"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "d┃❮ef\n", "❯ghi\n", "jkl"], - &["abc\n", "de┃❮f\n", "❯ghi\n", "jkl"], + &["abc", "d┃❮ef", "❯ghi", "jkl"], + &["abc", "de┃❮f", "❯ghi", "jkl"], &shift_pressed(), move_caret_right, )?; assert_move( - &["abc\n", "de┃❮f❯\n", "ghi\n", "jkl"], - &["abc\n", "def┃\n", "ghi\n", "jkl"], + &["abc", "de┃❮f❯", "ghi", "jkl"], + &["abc", "def┃", "ghi", "jkl"], &shift_pressed(), move_caret_right, )?; @@ -3292,26 +3160,26 @@ pub mod test_big_sel_text { assert_move(&["a❮bc❯┃"], &["a❮b❯┃c"], &shift_pressed(), move_caret_left)?; assert_move(&["❮abc❯┃"], &["❮ab❯┃c"], &shift_pressed(), move_caret_left)?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi\n", "jk❯┃l"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi", "jk❯┃l"], &shift_pressed(), move_caret_left, )?; assert_move( - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], - &["┃❮abc\n", "def\n", "ghi\n", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], + &["┃❮abc", "def", "ghi", "jkl❯"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "def❮\n", "❯┃ghi\n", "jkl"], - &["abc\n", "def┃\n", "ghi\n", "jkl"], + &["abc", "def❮", "❯┃ghi", "jkl"], + &["abc", "def┃", "ghi", "jkl"], &shift_pressed(), move_caret_left, )?; assert_move( - &["abc\n", "d❮ef\n", "gh❯┃i\n", "jkl"], - &["abc\n", "d❮ef\n", "g❯┃hi\n", "jkl"], + &["abc", "d❮ef", "gh❯┃i", "jkl"], + &["abc", "d❮ef", "g❯┃hi", "jkl"], &shift_pressed(), move_caret_left, )?; @@ -3328,38 +3196,38 @@ pub mod test_big_sel_text { assert_move(&["❮a❯┃bc"], &["┃abc"], &shift_pressed(), move_caret_up)?; assert_move(&["┃abc"], &["┃abc"], &shift_pressed(), move_caret_up)?; assert_move( - &["❮abc\n", "def❯┃"], - &["❮abc❯┃\n", "def"], + &["❮abc", "def❯┃"], + &["❮abc❯┃", "def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["❮abc\n", "de❯┃f"], - &["❮ab❯┃c\n", "def"], + &["❮abc", "de❯┃f"], + &["❮ab❯┃c", "def"], &shift_pressed(), move_caret_up, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi❯┃\n", "jkl"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi❯┃", "jkl"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "jkl❯┃"], - &["abc\n", "def\n", "ghi┃\n", "jkl"], + &["abc", "def", "ghi❮", "jkl❯┃"], + &["abc", "def", "ghi┃", "jkl"], &shift_pressed(), move_caret_up, )?; assert_move( - &["abc\n", "d❮ef\n", "ghi\n", "jk❯┃l"], - &["abc\n", "d❮ef\n", "gh❯┃i\n", "jkl"], + &["abc", "d❮ef", "ghi", "jk❯┃l"], + &["abc", "d❮ef", "gh❯┃i", "jkl"], &shift_pressed(), move_caret_up, )?; assert_move( - &["❮abc\n", "d❯┃ef\n", "ghi\n", "jkl"], - &["❮a❯┃bc\n", "def\n", "ghi\n", "jkl"], + &["❮abc", "d❯┃ef", "ghi", "jkl"], + &["❮a❯┃bc", "def", "ghi", "jkl"], &shift_pressed(), move_caret_up, )?; @@ -3373,50 +3241,50 @@ pub mod test_big_sel_text { assert_move(&["┃❮abc❯"], &["abc┃"], &shift_pressed(), move_caret_down)?; assert_move( - &["┃❮abc\n", "def❯"], - &["abc\n", "┃❮def❯"], + &["┃❮abc", "def❯"], + &["abc", "┃❮def❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["a┃❮bc\n", "def❯"], - &["abc\n", "d┃❮ef❯"], + &["a┃❮bc", "def❯"], + &["abc", "d┃❮ef❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["┃❮abc\n", "def\n", "ghi❯"], - &["abc\n", "┃❮def\n", "ghi❯"], + &["┃❮abc", "def", "ghi❯"], + &["abc", "┃❮def", "ghi❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃❮c\n", "def\n", "ghi❯"], - &["abc\n", "de┃❮f\n", "ghi❯"], + &["ab┃❮c", "def", "ghi❯"], + &["abc", "de┃❮f", "ghi❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abc\n", "de┃❮f\n", "ghi❯"], - &["abc\n", "def\n", "gh┃❮i❯"], + &["abc", "de┃❮f", "ghi❯"], + &["abc", "def", "gh┃❮i❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcdef┃❮\n", "ghij\n", "kl❯"], - &["abcdef\n", "ghij┃❮\n", "kl❯"], + &["abcdef┃❮", "ghij", "kl❯"], + &["abcdef", "ghij┃❮", "kl❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["abcde┃❮f\n", "ghij\n", "kl❯"], - &["abcdef\n", "ghij┃❮\n", "kl❯"], + &["abcde┃❮f", "ghij", "kl❯"], + &["abcdef", "ghij┃❮", "kl❯"], &shift_pressed(), move_caret_down, )?; assert_move( - &["ab┃❮cdef\n", "ghij\n", "kl❯"], - &["abcdef\n", "gh┃❮ij\n", "kl❯"], + &["ab┃❮cdef", "ghij", "kl❯"], + &["abcdef", "gh┃❮ij", "kl❯"], &shift_pressed(), move_caret_down, )?; @@ -3439,44 +3307,44 @@ pub mod test_big_sel_text { assert_move(&["a❮bc❯┃"], &["┃❮a❯bc"], &shift_pressed(), move_caret_home)?; assert_move( - &["❮abc\n", "def❯┃"], - &["❮abc\n", "❯┃def"], + &["❮abc", "def❯┃"], + &["❮abc", "❯┃def"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", " de❯┃f"], - &["❮abc\n", " ❯┃def"], + &["❮abc", " de❯┃f"], + &["❮abc", " ❯┃def"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", "def\n", "ghi\n", "jkl❯┃"], - &["❮abc\n", "def\n", "ghi\n", "❯┃jkl"], + &["❮abc", "def", "ghi", "jkl❯┃"], + &["❮abc", "def", "ghi", "❯┃jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "def\n", "ghi❮\n", "jkl❯┃"], - &["abc\n", "def\n", "ghi❮\n", "❯┃jkl"], + &["abc", "def", "ghi❮", "jkl❯┃"], + &["abc", "def", "ghi❮", "❯┃jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["abc\n", "d❮ef\n", " ghi\n", " jk❯┃l"], - &["abc\n", "d❮ef\n", " ghi\n", " ❯┃jkl"], + &["abc", "d❮ef", " ghi", " jk❯┃l"], + &["abc", "d❮ef", " ghi", " ❯┃jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", "d❯┃ef\n", "ghi\n", "jkl"], - &["❮abc\n", "❯┃def\n", "ghi\n", "jkl"], + &["❮abc", "d❯┃ef", "ghi", "jkl"], + &["❮abc", "❯┃def", "ghi", "jkl"], &shift_pressed(), move_caret_home, )?; assert_move( - &["❮abc\n", "d❯┃ef\n", "ghi\n", "jkl"], - &["❮abc\n", "❯┃def\n", "ghi\n", "jkl"], + &["❮abc", "d❯┃ef", "ghi", "jkl"], + &["❮abc", "❯┃def", "ghi", "jkl"], &shift_pressed(), move_caret_home, )?; @@ -3490,50 +3358,50 @@ pub mod test_big_sel_text { assert_move(&["┃❮abc❯"], &["abc┃"], &shift_pressed(), move_caret_end)?; assert_move( - &["┃❮abc\n", "def❯"], - &["abc┃❮\n", "def❯"], + &["┃❮abc", "def❯"], + &["abc┃❮", "def❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["a┃❮bc\n", "def❯"], - &["abc┃❮\n", "def❯"], + &["a┃❮bc", "def❯"], + &["abc┃❮", "def❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["a┃❮bc\n", "def\n", "ghi❯"], - &["abc┃❮\n", "def\n", "ghi❯"], + &["a┃❮bc", "def", "ghi❯"], + &["abc┃❮", "def", "ghi❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["ab┃❮c\n", "def\n", "ghi❯"], - &["abc┃❮\n", "def\n", "ghi❯"], + &["ab┃❮c", "def", "ghi❯"], + &["abc┃❮", "def", "ghi❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abc\n", "de┃❮f\n", "ghi❯"], - &["abc\n", "def┃❮\n", "ghi❯"], + &["abc", "de┃❮f", "ghi❯"], + &["abc", "def┃❮", "ghi❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abcdef┃❮\n", "ghij\n", "kl❯"], - &["abcdef┃❮\n", "ghij\n", "kl❯"], + &["abcdef┃❮", "ghij", "kl❯"], + &["abcdef┃❮", "ghij", "kl❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["┃❮ abcdef\n", "ghij\n", "kl❯"], - &[" abcdef┃❮\n", "ghij\n", "kl❯"], + &["┃❮ abcdef", "ghij", "kl❯"], + &[" abcdef┃❮", "ghij", "kl❯"], &shift_pressed(), move_caret_end, )?; assert_move( - &["abcdef\n", "ghij\n", "┃❮kl❯"], - &["abcdef\n", "ghij\n", "kl┃"], + &["abcdef", "ghij", "┃❮kl❯"], + &["abcdef", "ghij", "kl┃"], &shift_pressed(), move_caret_end, )?; diff --git a/editor/src/ui/text/caret_w_select.rs b/editor/src/ui/text/caret_w_select.rs index a9e5b9b2a7..b88acada7d 100644 --- a/editor/src/ui/text/caret_w_select.rs +++ b/editor/src/ui/text/caret_w_select.rs @@ -12,6 +12,12 @@ pub struct CaretWSelect { pub selection_opt: Option, } +pub enum CaretPos { + Start, + Exact(TextPos), + End, +} + fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult> { if start_pos == end_pos { Ok(None) @@ -146,7 +152,7 @@ pub mod test_caret_w_select { // Retrieve selection and position from formatted string pub fn convert_dsl_to_selection(lines: &[String]) -> Result { - let lines_str: String = lines.join(""); + let lines_str: String = lines.join("\n"); let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str) .expect("Selection test DSL parsing failed"); diff --git a/editor/src/ui/text/lines.rs b/editor/src/ui/text/lines.rs index 3fb16b5842..37e1d67452 100644 --- a/editor/src/ui/text/lines.rs +++ b/editor/src/ui/text/lines.rs @@ -13,14 +13,12 @@ use crate::ui::text::{ use crate::ui::ui_error::UIResult; use crate::ui::util::is_newline; use crate::window::keyboard_input::Modifiers; -use bumpalo::collections::String as BumpString; -use bumpalo::Bump; use std::cmp::max; use std::cmp::min; use winit::event::VirtualKeyCode; pub trait Lines { - fn get_line(&self, line_nr: usize) -> UIResult<&str>; + fn get_line_ref(&self, line_nr: usize) -> UIResult<&str>; fn line_len(&self, line_nr: usize) -> UIResult; @@ -28,7 +26,7 @@ pub trait Lines { fn nr_of_chars(&self) -> usize; - fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>; + fn all_lines_as_string(&self) -> String; fn is_last_line(&self, line_nr: usize) -> bool; @@ -83,7 +81,7 @@ pub trait MutSelectableLines { fn insert_str(&mut self, new_str: &str) -> UIResult<()>; - fn pop_char(&mut self) -> UIResult<()>; + fn backspace(&mut self) -> UIResult<()>; fn del_selection(&mut self) -> UIResult<()>; } @@ -114,7 +112,7 @@ pub fn move_caret_left( } else { let curr_line_len = lines.line_len(old_line_nr - 1)?; - (old_line_nr - 1, curr_line_len - 1) + (old_line_nr - 1, curr_line_len) } } else { (old_line_nr, old_col_nr - 1) @@ -185,7 +183,7 @@ pub fn move_caret_right( let is_last_line = lines.is_last_line(old_line_nr); if !is_last_line { - if old_col_nr + 1 > curr_line_len - 1 { + if old_col_nr + 1 > curr_line_len { (old_line_nr + 1, 0) } else { (old_line_nr, old_col_nr + 1) @@ -263,7 +261,9 @@ pub fn move_caret_up( let prev_line_len = lines.line_len(old_line_nr - 1)?; if prev_line_len <= old_col_nr { - (old_line_nr - 1, prev_line_len - 1) + let new_column = if prev_line_len > 0 { prev_line_len } else { 0 }; + + (old_line_nr - 1, new_column) } else { (old_line_nr - 1, old_col_nr) } @@ -331,7 +331,9 @@ pub fn move_caret_down( if next_line_len <= old_col_nr { if !is_last_line { - (old_line_nr + 1, next_line_len - 1) + let new_column = if next_line_len > 0 { next_line_len } else { 0 }; + + (old_line_nr + 1, new_column) } else { (old_line_nr + 1, next_line_len) } @@ -382,7 +384,7 @@ pub fn move_caret_home( let curr_line_nr = caret_w_select.caret_pos.line; let old_col_nr = caret_w_select.caret_pos.column; - let curr_line_str = lines.get_line(curr_line_nr)?; + let curr_line_str = lines.get_line_ref(curr_line_nr)?; let line_char_iter = curr_line_str.chars(); let mut first_no_space_char_col = 0; diff --git a/editor/src/ui/text/mod.rs b/editor/src/ui/text/mod.rs index 39616a47cd..feaa7946f8 100644 --- a/editor/src/ui/text/mod.rs +++ b/editor/src/ui/text/mod.rs @@ -2,4 +2,5 @@ pub mod big_text_area; pub mod caret_w_select; pub mod lines; pub mod selection; +mod text_buffer; pub mod text_pos; diff --git a/editor/src/ui/text/text_buffer.rs b/editor/src/ui/text/text_buffer.rs new file mode 100644 index 0000000000..b846acb391 --- /dev/null +++ b/editor/src/ui/text/text_buffer.rs @@ -0,0 +1,193 @@ +use std::path::Path; + +use crate::ui::{ + ui_error::{OutOfBounds, TextBufReadFailed, UIResult}, + util::{path_to_string, reader_from_path}, +}; +use snafu::ensure; + +use super::{selection::Selection, text_pos::TextPos}; +use std::io::BufRead; + +// Do not use for large amounts of text. +// This should become a trait in the future and be implemented by a SmallTextBuffer and Rope(for large amounts of text) +#[derive(Debug)] +pub struct TextBuffer { + pub lines: Vec, +} + +impl TextBuffer { + pub fn from_path(path: &Path) -> UIResult { + let buf_reader = reader_from_path(path)?; + let mut lines: Vec = Vec::new(); + + for line in buf_reader.lines() { + match line { + Ok(line_str) => lines.push(line_str), + Err(e) => { + TextBufReadFailed { + path_str: path_to_string(path), + err_msg: e.to_string(), + } + .fail()?; + } + } + } + + Ok(TextBuffer { lines }) + } + + pub fn nr_of_chars(&self) -> usize { + let mut nr_of_chars = 0; + + for line in self.lines.iter() { + nr_of_chars += line.len(); + } + + nr_of_chars + } + + pub fn nr_of_lines(&self) -> usize { + self.lines.len() + } + + pub fn get_line_ref(&self, line_nr: usize) -> UIResult<&str> { + self.ensure_bounds(line_nr)?; + // safe unwrap because we checked the length + Ok(self.lines.get(line_nr).unwrap()) + } + + pub fn line_len(&self, line_nr: usize) -> UIResult { + Ok(self.get_line_ref(line_nr)?.len()) + } + + fn ensure_bounds(&self, line_nr: usize) -> UIResult<()> { + ensure!( + line_nr < self.nr_of_lines(), + OutOfBounds { + index: line_nr, + collection_name: "TextBuffer", + len: self.nr_of_lines(), + } + ); + + Ok(()) + } + + fn ensure_bounds_txt_pos(&self, txt_pos: TextPos) -> UIResult<()> { + ensure!( + txt_pos.line < self.nr_of_lines(), + OutOfBounds { + index: txt_pos.line, + collection_name: "TextBuffer", + len: self.nr_of_lines(), + } + ); + + let line_ref = self.get_line_ref(txt_pos.line)?; + let line_len = line_ref.len(); + + ensure!( + txt_pos.column <= line_len, + OutOfBounds { + index: txt_pos.column, + collection_name: format!("Line in TextBuffer: {}", line_ref), + len: line_len, + } + ); + + Ok(()) + } + + pub fn all_lines_ref(&self) -> &[String] { + &self.lines + } + + pub fn get_selected_str(&self, selection: Selection) -> UIResult { + let start_line_nr = selection.start_pos.line; + let start_col_nr = selection.start_pos.column; + + let end_line_nr = selection.end_pos.line; + let end_col_nr = selection.end_pos.column; + + let mut selected_str = String::new(); + + if end_line_nr > start_line_nr { + selected_str.push_str(&self.get_line_ref(start_line_nr)?[start_col_nr..]); + + for line_nr in start_line_nr + 1..end_line_nr - 1 { + selected_str.push_str(self.get_line_ref(line_nr)?); + } + + selected_str.push_str(&self.get_line_ref(end_line_nr)?[..end_col_nr]); + } else { + // start_line_nr == end_line_nr + selected_str.push_str(&self.get_line_ref(start_line_nr)?[start_col_nr..end_col_nr]); + } + + Ok(selected_str) + } + + pub fn insert_str(&mut self, txt_pos: TextPos, new_str: &str) -> UIResult<()> { + self.ensure_bounds_txt_pos(txt_pos)?; + + // safe unwrap because we checked the length + self.lines + .get_mut(txt_pos.line) + .unwrap() + .insert_str(txt_pos.column, new_str); + + Ok(()) + } + + pub fn backspace_char(&mut self, txt_pos: TextPos) -> UIResult<()> { + if txt_pos.column > 0 { + let prev_col_pos = TextPos { + line: txt_pos.line, + column: txt_pos.column - 1, + }; + + self.ensure_bounds_txt_pos(prev_col_pos)?; + + let line_ref = self.lines.get_mut(prev_col_pos.line).unwrap(); // safe because of earlier bounds check + + line_ref.remove(prev_col_pos.column); + } else if txt_pos.line > 0 { + self.lines.remove(txt_pos.line); + } + + Ok(()) + } + + pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> { + self.ensure_bounds_txt_pos(selection.start_pos)?; + self.ensure_bounds_txt_pos(selection.end_pos)?; + + let start_line_nr = selection.start_pos.line; + let start_col_nr = selection.start_pos.column; + let end_line_nr = selection.end_pos.line; + let end_col_nr = selection.end_pos.column; + + if end_line_nr > start_line_nr { + // remove in reverse to prevent shifting indices + if end_col_nr == self.line_len(end_line_nr)? { + self.lines.remove(end_line_nr); + } else { + let line_ref = self.lines.get_mut(end_line_nr).unwrap(); // safe because of earlier bounds check + line_ref.replace_range(..end_col_nr, ""); + } + + self.lines.drain(start_line_nr + 1..end_line_nr); + + let line_ref = self.lines.get_mut(start_line_nr).unwrap(); // safe because of earlier bounds check + line_ref.replace_range(start_col_nr.., "") + } else { + // selection.end_pos.line == selection.start_pos.line + let line_ref = self.lines.get_mut(selection.start_pos.line).unwrap(); // safe because of earlier bounds check + + line_ref.replace_range(selection.start_pos.column..selection.end_pos.column, "") + } + + Ok(()) + } +} diff --git a/editor/src/ui/ui_error.rs b/editor/src/ui/ui_error.rs index ec422d1663..9f4d1a96b0 100644 --- a/editor/src/ui/ui_error.rs +++ b/editor/src/ui/ui_error.rs @@ -1,3 +1,5 @@ +use std::io; + use snafu::{Backtrace, Snafu}; //import errors as follows: @@ -8,6 +10,16 @@ use snafu::{Backtrace, Snafu}; #[derive(Debug, Snafu)] #[snafu(visibility(pub))] pub enum UIError { + #[snafu(display( + "LineInsertionFailed: line_nr ({}) needs to be <= nr_of_lines ({}).", + line_nr, + nr_of_lines + ))] + LineInsertionFailed { + line_nr: usize, + nr_of_lines: usize, + backtrace: Backtrace, + }, #[snafu(display( "OutOfBounds: index {} was out of bounds for {} with length {}.", index, @@ -34,6 +46,13 @@ pub enum UIError { ))] FileOpenFailed { path_str: String, err_msg: String }, + #[snafu(display( + "FileWriteFailed: failed to write to file with path {}, I got this IO error: {}.", + path_str, + source + ))] + FileWriteFailed { source: io::Error, path_str: String }, + #[snafu(display("TextBufReadFailed: the file {} could be opened but we encountered the following error while trying to read it: {}.", path_str, err_msg))] TextBufReadFailed { path_str: String, err_msg: String }, diff --git a/editor/src/ui/util.rs b/editor/src/ui/util.rs index d33d8da38e..97c51a0dd8 100644 --- a/editor/src/ui/util.rs +++ b/editor/src/ui/util.rs @@ -1,6 +1,6 @@ -use super::ui_error::{OutOfBounds, UIResult}; -use snafu::OptionExt; -use std::slice::SliceIndex; +use super::ui_error::{FileOpenFailed, FileWriteFailed, OutOfBounds, UIResult}; +use snafu::{OptionExt, ResultExt}; +use std::{fs::File, io::BufReader, path::Path, slice::SliceIndex}; pub fn is_newline(char_ref: &char) -> bool { let newline_codes = vec!['\u{d}', '\n']; @@ -33,3 +33,27 @@ pub fn slice_get_mut( Ok(elt_ref) } + +pub fn reader_from_path(path: &Path) -> UIResult> { + match File::open(path) { + Ok(file) => Ok(BufReader::new(file)), + Err(e) => FileOpenFailed { + path_str: path_to_string(path), + err_msg: e.to_string(), + } + .fail()?, + } +} + +pub fn path_to_string(path: &Path) -> String { + let mut path_str = String::new(); + path_str.push_str(&path.to_string_lossy()); + + path_str +} + +pub fn write_to_file(path: &Path, content: &str) -> UIResult<()> { + std::fs::write(path, content).with_context(|| FileWriteFailed { + path_str: path_to_string(path), + }) +} diff --git a/editor/src/window/keyboard_input.rs b/editor/src/window/keyboard_input.rs index 9107e963e5..4848337e00 100644 --- a/editor/src/window/keyboard_input.rs +++ b/editor/src/window/keyboard_input.rs @@ -27,6 +27,17 @@ impl Modifiers { active } + + // returns true if modifiers are active that can be active when the user wants to insert a new char; e.g.: shift+a to make A + pub fn new_char_modifiers(&self) -> bool { + self.no_modifiers() + || (self.shift && !self.ctrl && !self.alt && !self.logo) // e.g.: shift+a to make A + || (self.cmd_or_ctrl() && self.alt) // e.g.: ctrl+alt+2 to make @ on azerty keyboard + } + + fn no_modifiers(&self) -> bool { + !self.shift && !self.ctrl && !self.alt && !self.logo + } } pub fn no_mods() -> Modifiers { diff --git a/editor/tests/README.md b/editor/tests/README.md new file mode 100644 index 0000000000..2e0697587d --- /dev/null +++ b/editor/tests/README.md @@ -0,0 +1,5 @@ + +# Where are the tests? + +We have a lot of tests at the end of source files, this allows us to test functions that are not exposed by the editor itself. +`editor/mvc/ed_update.rs` and `editor/ui/text/big_text_area.rs` have many important tests. \ No newline at end of file diff --git a/editor/tests/selection.pest b/editor/tests/selection.pest index d347767bff..6c70079a54 100644 --- a/editor/tests/selection.pest +++ b/editor/tests/selection.pest @@ -1,4 +1,4 @@ -text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" )* } +text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" | "=" )* } caret = {"┃"} diff --git a/examples/hello-web/platform/host.js b/examples/hello-web/platform/host.js index 0d953eabff..a90ff4187b 100644 --- a/examples/hello-web/platform/host.js +++ b/examples/hello-web/platform/host.js @@ -11,6 +11,12 @@ async function roc_web_platform_run(wasm_filename, callback) { const importObj = { wasi_snapshot_preview1: { + proc_exit: (code) => { + if (code !== 0) { + console.error(`Exited with code ${code}`); + } + exit_code = code; + }, roc_panic: (_pointer, _tag_id) => { throw 'Roc panicked!'; } diff --git a/examples/hello-web/platform/host.zig b/examples/hello-web/platform/host.zig index 5d588d6912..bfd519e9de 100644 --- a/examples/hello-web/platform/host.zig +++ b/examples/hello-web/platform/host.zig @@ -48,25 +48,21 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocCallResult) void; - -const RocCallResult = extern struct { flag: u64, content: RocStr }; +extern fn roc__mainForHost_1_exposed(*RocStr) void; const Unit = extern struct {}; extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void; pub fn main() u8 { - // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() }; - // actually call roc to populate the callresult + var callresult = RocStr.empty(); roc__mainForHost_1_exposed(&callresult); // display the result using JavaScript - js_display_roc_string(callresult.content.str_bytes, callresult.content.str_len); + js_display_roc_string(callresult.asU8ptr(), callresult.len()); - callresult.content.deinit(); + callresult.deinit(); return 0; } diff --git a/nightly_benches/benches/events_bench.rs b/nightly_benches/benches/events_bench.rs index c722eeae7c..be889672f2 100644 --- a/nightly_benches/benches/events_bench.rs +++ b/nightly_benches/benches/events_bench.rs @@ -1,6 +1,6 @@ // Keep this benchmark. It's commented because it requires nightly rust. use cli_utils::bench_utils::{ - bench_cfold, bench_deriv, bench_nqueens, bench_rbtree_ck, bench_rbtree_delete, bench_quicksort + bench_cfold, bench_deriv, bench_nqueens, bench_quicksort, bench_rbtree_ck, bench_rbtree_delete, }; use criterion_perf_events::Perf; use perfcnt::linux::HardwareEventType as Hardware; @@ -18,7 +18,7 @@ fn bench_group(c: &mut Criterion, hw_event_str: &str) { bench_cfold, bench_deriv, bench_rbtree_ck, - bench_rbtree_delete, + // bench_rbtree_delete, bench_quicksort, ]; diff --git a/vendor/morphic_lib/Cargo.toml b/vendor/morphic_lib/Cargo.toml index 5406cf3647..594ad4c408 100644 --- a/vendor/morphic_lib/Cargo.toml +++ b/vendor/morphic_lib/Cargo.toml @@ -5,6 +5,7 @@ authors = ["William Brandon", "Wilson Berkow", "Frank Dai", "Benjamin Driscoll"] edition = "2018" [dependencies] -thiserror = "1.0.24" +thiserror = "1.0" sha2 = "0.9.4" -smallvec = "1.6.1" +smallvec = "1.6" +typed-arena = "2.0" diff --git a/vendor/morphic_lib/src/analyze.rs b/vendor/morphic_lib/src/analyze.rs new file mode 100644 index 0000000000..fbb69785ef --- /dev/null +++ b/vendor/morphic_lib/src/analyze.rs @@ -0,0 +1,1770 @@ +use smallvec::SmallVec; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::TryInto; +use typed_arena::Arena; + +use crate::api; +use crate::ir; +use crate::name_cache::{EntryPointId, FuncId}; +use crate::type_cache::{TypeCache, TypeData, TypeId}; +use crate::util::flat_slices::FlatSlices; +use crate::util::id_type::Count; +use crate::util::id_vec::IdVec; +use crate::util::norm_pair::NormPair; +use crate::util::op_graph; +use crate::util::replace_none::replace_none; +use crate::util::strongly_connected::{strongly_connected, SccKind}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct SubSlots { + end_indices: SmallVec<[u32; 10]>, +} + +impl SubSlots { + fn from_slot_counts(slot_counts: impl Iterator) -> Self { + let mut total = 0; + let end_indices = slot_counts + .map(|count| { + total += count; + total + }) + .collect(); + Self { end_indices } + } + + fn slot_count(&self) -> u32 { + self.end_indices.last().cloned().unwrap_or(0) + } + + /// Returns bounds `a, b` for a range of slot indices `a..b` + fn sub_slots(&self, index: u32) -> (u32, u32) { + let start = if index == 0 { + 0 + } else { + self.end_indices[index as usize - 1] + }; + let end = self.end_indices[index as usize]; + (start, end) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum TypeSlots { + Named, + Tuple { field_slots: SubSlots }, + Union { variant_slots: SubSlots }, + HeapCell, + Bag { item_slots: u32 }, +} + +impl TypeSlots { + fn slot_count(&self) -> u32 { + match self { + TypeSlots::Named => 1, + TypeSlots::Tuple { field_slots } => field_slots.slot_count(), + TypeSlots::Union { variant_slots } => variant_slots.slot_count(), + TypeSlots::HeapCell => 1, + TypeSlots::Bag { item_slots } => *item_slots, + } + } +} + +#[derive(Clone, Debug)] +struct SlotCache { + type_cache: TypeCache, + slots: IdVec, +} + +impl SlotCache { + fn new(type_cache: TypeCache) -> Self { + let mut slots: IdVec<_, TypeSlots> = IdVec::new(); + // NOTE: This only works because 'type_cache.types' is guaranteed to assign ids in + // topological order. + for (id, type_) in type_cache.types.iter() { + let this_slots = match type_ { + TypeData::Named { named: _ } => TypeSlots::Named, + TypeData::Tuple { fields } => { + let field_slots = SubSlots::from_slot_counts( + fields.iter().map(|field| slots[field].slot_count()), + ); + TypeSlots::Tuple { field_slots } + } + TypeData::Union { variants } => { + let variant_slots = SubSlots::from_slot_counts( + variants.iter().map(|variant| slots[variant].slot_count()), + ); + TypeSlots::Union { variant_slots } + } + TypeData::HeapCell => TypeSlots::HeapCell, + TypeData::Bag { item } => { + let item_slots = slots[item].slot_count(); + TypeSlots::Bag { item_slots } + } + }; + let pushed_id = slots.push(this_slots); + debug_assert_eq!(pushed_id, id); + } + Self { type_cache, slots } + } + + fn type_cache(&self) -> &TypeCache { + &self.type_cache + } + + fn slots(&self) -> &IdVec { + &self.slots + } +} + +id_type! { + HeapCellId(u32); +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum QueryPoint { + Update(api::UpdateModeVarId), + EntryArg(u32), + CallArg(api::CalleeSpecVarId, u32), + CallRet(api::CalleeSpecVarId, u32), +} + +#[derive(Clone, Debug)] +struct BackRefState { + // TODO: use a more efficient representation + overlay: HashMap>, + parents: Vec, +} + +id_type! { + BackRefStateVersionId(u32); +} + +#[derive(Clone, Copy, Debug)] +struct CallInfo { + callee: FuncId, + ret_slots: u32, +} + +#[derive(Clone)] +struct ForwardState<'a> { + slots_arena: &'a Arena, + value_slots: IdVec>, + // Represents value slots for "iteration n - 1" in an SCC + // TODO: Should this be array-of-structs instead of struct-of-arrays? + value_slots_inductive: IdVec>, + call_arg_aliases: IdVec>>>, + call_arg_origins: IdVec>>, + // TODO: Find a better place to store the data mapping in `calls` + calls: IdVec>, + update_origins: IdVec>, + arg_slots: Option<&'a [HeapCellId]>, + heap_cells: IdVec, + back_ref_states: IdVec, + block_versions: IdVec>, + block_versions_inductive: IdVec>, + entry_version: BackRefStateVersionId, + fates: HashMap, +} + +type Set = HashSet; + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Origin { + /// This heap cell might have been obtained from a `const_ref` op. + /// In this case we don't care what arg slots it might have also been obtained from, because we + /// definitely can't mutate it. + FromConst, + /// This heap cell was definitely not obtained from a `const_ref` op. + /// In this case we care about the (potentially empty) set of arg slots we might have obtained + /// it from. + FromArgSlots(Set), +} + +impl Origin { + pub fn union_with(&mut self, other: &Origin) { + match (&mut *self, other) { + (Origin::FromConst, _) => {} + (Origin::FromArgSlots(_), Origin::FromConst) => *self = Origin::FromConst, + (Origin::FromArgSlots(slots1), Origin::FromArgSlots(slots2)) => slots1.extend(slots2), + } + } +} + +impl Default for Origin { + fn default() -> Self { + Origin::FromArgSlots(Set::new()) + } +} + +#[derive(Clone, Debug)] +struct ForwardData { + origin: Origin, + // invariant: does not contain current heap cell id (all heap cells implicitly alias themselves, + // so storing reflexive alias edges would be redundant). + // invariant: aliases are symmetric; if we alias another heap cell, that heap cell should also + // alias us. + aliases: Set, +} + +fn result_slot_count(sc: &mut SlotCache, val: &op_graph::Node) -> u32 { + sc.slots()[val.op.result_type].slot_count() +} + +fn id_result_slot_count(sc: &mut SlotCache, graph: &ir::Graph, val_id: ir::ValueId) -> u32 { + sc.slots()[graph.values().node(val_id).op.result_type].slot_count() +} + +type HeapCellSlotMapping = HashMap>; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ForwardSccSlotSummary { + pre_aliases: Set, + inductive_aliases: Set<(ir::ValueId, u32)>, + internal_aliases: Set<(ir::ValueId, u32)>, + back_refs: HashSet, +} + +type ForwardSccSummary = HashMap>; + +fn block_values_inclusive( + graph: &ir::Graph, + block: ir::BlockId, +) -> impl Iterator + '_ { + graph + .blocks() + .block_info(block) + .param + .iter() // iterator impl on Option + .cloned() + .chain(graph.blocks().block_values(block)) +} + +impl<'a> ForwardState<'a> { + fn add_heap_cell(&mut self) -> HeapCellId { + self.heap_cells.push(ForwardData { + origin: Origin::default(), + aliases: Set::new(), + }) + } + + // Factored into a separate function to allow use when other (disjoint) fields of 'self' are borrowed + fn back_refs_in_states( + back_ref_states: &mut IdVec, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + ) -> &mut HashSet { + // TODO: Optimize this so that it does not traverse the whole parent chain when the heap + // cell is guaranteed to not have any back ref annotations before a certain point (i.e., + // when we have some information about when the heap cell was created). + // TODO: Remove query points from back ref sets when they are set to 'DirectTouch'. + if back_ref_states[version].overlay.contains_key(&heap_cell) { + return back_ref_states[version] + .overlay + .get_mut(&heap_cell) + .unwrap(); + } + let back_refs = match &back_ref_states[version].parents as &[_] { + &[parent] => Self::back_refs_in_states(back_ref_states, parent, heap_cell).clone(), + parents => { + let num_parents = parents.len(); + let mut back_refs = HashSet::new(); + for parent_i in 0..num_parents { + let parent = back_ref_states[version].parents[parent_i]; + let parent_back_refs = + Self::back_refs_in_states(back_ref_states, parent, heap_cell); + back_refs.extend(parent_back_refs.iter()); + } + back_refs + } + }; + back_ref_states[version] + .overlay + .entry(heap_cell) + .or_insert(back_refs) // always inserts + } + + fn back_refs( + &mut self, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + ) -> &mut HashSet { + Self::back_refs_in_states(&mut self.back_ref_states, version, heap_cell) + } + + fn add_heap_cells(&mut self, n: u32) -> &'a mut [HeapCellId] { + self.slots_arena + .alloc_extend(std::iter::repeat_with(|| self.add_heap_cell()).take(n as usize)) + } + + fn copy_heap_cell(&mut self, cell: HeapCellId, n: u32) -> &'a mut [HeapCellId] { + self.slots_arena + .alloc_extend(std::iter::repeat(cell).take(n as usize)) + } + + fn add_alias(&mut self, cell1: HeapCellId, cell2: HeapCellId) { + if cell1 == cell2 { + return; + } + self.heap_cells[cell1].aliases.insert(cell2); + self.heap_cells[cell2].aliases.insert(cell1); + } + + fn copy_aliases(&mut self, src: HeapCellId, dst: HeapCellId) { + self.add_alias(src, dst); + + // A trick so that we can iterate over `aliases` and call `add_alias` at the same time. At + // the end of the function we put `src_aliases` back in place. + // + // TODO: revist this if we start using "small" sets + let src_aliases = std::mem::take(&mut self.heap_cells[src].aliases); + for &other in &src_aliases { + debug_assert_ne!(other, src); + self.add_alias(other, dst); + } + self.heap_cells[src].aliases = src_aliases; + } + + fn copy_non_alias_data( + &mut self, + src_version: BackRefStateVersionId, + src: HeapCellId, + dst_version: BackRefStateVersionId, + dst: HeapCellId, + ) { + if src == dst { + return; + } + + let (src_data, dst_data) = self.heap_cells.get2_mut(src, dst).unwrap(); + src_data.origin.union_with(&dst_data.origin); + + let src_back_refs = std::mem::take(self.back_refs(src_version, src)); + let dst_back_refs = self.back_refs(dst_version, dst); + dst_back_refs.extend(src_back_refs.iter()); + debug_assert!(self.back_refs(src_version, src).is_empty()); + *self.back_refs(src_version, src) = src_back_refs; + } + + fn copy_data( + &mut self, + src_version: BackRefStateVersionId, + src: HeapCellId, + dst_version: BackRefStateVersionId, + dst: HeapCellId, + ) { + self.copy_non_alias_data(src_version, src, dst_version, dst); + self.copy_aliases(src, dst); + } + + fn touch(&mut self, version: BackRefStateVersionId, heap_cell: HeapCellId) { + let back_refs = std::mem::take(self.back_refs(version, heap_cell)); + for &query_point in &back_refs { + self.fates.insert(query_point, Fate::DirectTouch); + } + *self.back_refs(version, heap_cell) = back_refs; + } + + fn recursive_touch(&mut self, version: BackRefStateVersionId, heap_cells: &[HeapCellId]) { + for &heap_cell in heap_cells { + self.touch(version, heap_cell); + } + } + + fn add_back_refs( + &mut self, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + query_point: QueryPoint, + mut other_filter: impl FnMut(HeapCellId) -> bool, + ) { + let aliases = std::mem::take(&mut self.heap_cells[heap_cell].aliases); + for other in std::iter::once(heap_cell).chain(aliases.iter().cloned()) { + if other_filter(other) { + self.back_refs(version, other).insert(query_point); + } + } + self.heap_cells[heap_cell].aliases = aliases; + } + + fn analyze_value( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + version: BackRefStateVersionId, + val_id: ir::ValueId, + ) { + let val_node = graph.values().node(val_id); + let input_slot_arrs: SmallVec<[_; 16]> = val_node + .inputs + .iter() + .map(|input| { + self.value_slots[input].expect("values should be processed in topological order") + }) + .collect(); + let op = match &val_node.op.kind { + ir::ValueKind::Op(op) => op, + ir::ValueKind::BlockParam => { + unreachable!("block param should never appear in the values of a block") + } + }; + let ret_slots: &[_] = match op { + ir::OpKind::UnknownWith => { + let new_cell = self.add_heap_cell(); + self.heap_cells[new_cell].origin = Origin::FromConst; + for input_slots in input_slot_arrs { + self.recursive_touch(version, input_slots); + for &input_cell in input_slots { + self.copy_aliases(input_cell, new_cell); + } + } + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(new_cell, slot_count) + } + + ir::OpKind::Call { + callee_spec_var, + callee, + } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let arg_slots = input_slot_arrs[0]; + // TODO: optimize this entire case! + let mut heap_cell_slots = HashMap::>::new(); + for (slot_i, &heap_cell) in arg_slots.iter().enumerate() { + heap_cell_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push(slot_i.try_into().unwrap()); + } + let mut arg_aliases = HashSet::new(); + for (heap_cell, slot_indices) in &heap_cell_slots { + // Wire up to ocurrences of the same heap cell in the argument slots + for (i, &slot_i) in slot_indices.iter().enumerate() { + for &slot_j in &slot_indices[..i] { + arg_aliases.insert(NormPair::new(slot_i, slot_j)); + } + } + // Wire up to distinct aliased heap cells in the argument slots + for &other in &self.heap_cells[heap_cell].aliases { + if let Some(other_slot_indices) = heap_cell_slots.get(&other) { + for &this_slot_i in slot_indices { + for &other_slot_i in other_slot_indices { + arg_aliases.insert(NormPair::new(this_slot_i, other_slot_i)); + } + } + } + } + } + + let ret_slots: &[_] = self.add_heap_cells(result_slot_count(sc, &val_node)); + + if let Some(basic_analysis) = ctx.get_analysis(sc, *callee, None) { + for (arg_slot_i, slot_analysis) in basic_analysis.arg_slots.iter().enumerate() { + if matches!(slot_analysis.fate, Fate::DirectTouch) { + self.touch(version, arg_slots[arg_slot_i]); + } + } + for (ret_slot_i, slot_analysis) in basic_analysis.ret_slots.iter().enumerate() { + let ret_heap_cell = ret_slots[ret_slot_i]; + if slot_analysis.from_const { + self.heap_cells[ret_heap_cell].origin = Origin::FromConst; + } + // Temporarily violate symmetry invariant + for &arg_slot_i in &slot_analysis.arg_aliases { + let arg_heap_cell = arg_slots[arg_slot_i as usize]; + self.heap_cells[ret_heap_cell].aliases.insert(arg_heap_cell); + let (arg_heap_cell_data, ret_heap_cell_data) = self + .heap_cells + .get2_mut(arg_heap_cell, ret_heap_cell) + .unwrap(); + for &alias_of_arg in &arg_heap_cell_data.aliases { + ret_heap_cell_data.aliases.insert(alias_of_arg); + } + self.copy_non_alias_data( + version, + arg_heap_cell, + version, + ret_heap_cell, + ); + } + for &other_ret_slot_i in &slot_analysis.ret_aliases { + self.heap_cells[ret_heap_cell] + .aliases + .insert(ret_slots[other_ret_slot_i as usize]); + } + } + } + + for &arg_alias in &arg_aliases { + if let Some(part_analysis) = ctx.get_analysis(sc, *callee, Some(arg_alias)) { + for (arg_slot_i, slot_analysis) in + part_analysis.arg_slots.iter().enumerate() + { + if matches!(slot_analysis.fate, Fate::DirectTouch) { + self.touch(version, arg_slots[arg_slot_i]); + } + } + for (ret_slot_i, slot_analysis) in + part_analysis.ret_slots.iter().enumerate() + { + // Temporarily violate symmetry invariant + let ret_heap_cell = ret_slots[ret_slot_i]; + for &arg_slot_i in &slot_analysis.arg_aliases { + let arg_heap_cell = arg_slots[arg_slot_i as usize]; + self.heap_cells[ret_heap_cell].aliases.insert(arg_heap_cell); + } + for &other_ret_slot_i in &slot_analysis.ret_aliases { + self.heap_cells[ret_heap_cell] + .aliases + .insert(ret_slots[other_ret_slot_i as usize]); + } + } + } + } + + // Restore symmetry invariant + for &ret_heap_cell in ret_slots { + let aliases = std::mem::take(&mut self.heap_cells[ret_heap_cell].aliases); + for &other in &aliases { + debug_assert_ne!(other, ret_heap_cell); + self.heap_cells[other].aliases.insert(ret_heap_cell); + } + debug_assert!(self.heap_cells[ret_heap_cell].aliases.is_empty()); + self.heap_cells[ret_heap_cell].aliases = aliases; + } + + // We don't use 'replace_none' here because we may write these values multiple times + // during fixed-point iteration. + self.call_arg_aliases[callee_spec_var] = Some(arg_aliases); + self.call_arg_origins[callee_spec_var] = Some( + arg_slots + .iter() + .map(|heap_cell| self.heap_cells[heap_cell].origin.clone()) + .collect(), + ); + self.calls[callee_spec_var] = Some(CallInfo { + callee: *callee, + ret_slots: ret_slots.len().try_into().unwrap(), + }); + + for (arg_slot_i, &arg_heap_cell) in arg_slots.iter().enumerate() { + self.add_back_refs( + version, + arg_heap_cell, + QueryPoint::CallArg(*callee_spec_var, arg_slot_i.try_into().unwrap()), + // TODO: don't use a linear search here + |other| !ret_slots.contains(&other), + ); + } + for (ret_slot_i, &ret_heap_cell) in ret_slots.iter().enumerate() { + self.add_back_refs( + version, + ret_heap_cell, + QueryPoint::CallRet(*callee_spec_var, ret_slot_i.try_into().unwrap()), + // TODO: don't use a linear search here + |other| !arg_slots.contains(&other), + ); + } + + ret_slots + } + + ir::OpKind::ConstRef { const_: _ } => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let slot_count = result_slot_count(sc, &val_node); + let new_heap_cells: &[_] = self.add_heap_cells(slot_count); + for heap_cell in new_heap_cells { + self.heap_cells[heap_cell].origin = Origin::FromConst; + } + new_heap_cells + } + + ir::OpKind::NewHeapCell => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let new_cell = self.add_heap_cell(); + std::slice::from_ref(self.slots_arena.alloc(new_cell)) + } + + ir::OpKind::RecursiveTouch => { + debug_assert_eq!(input_slot_arrs.len(), 1); + self.recursive_touch(version, input_slot_arrs[0]); + &[] + } + + ir::OpKind::UpdateWriteOnly { update_mode_var } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + debug_assert_eq!(input_slot_arrs[0].len(), 1); + let heap_cell = input_slot_arrs[0][0]; + // We don't use 'replace_none' here because we may write this value multiple times + // during fixed-point iteration. + self.update_origins[*update_mode_var] = + Some(self.heap_cells[heap_cell].origin.clone()); + self.add_back_refs( + version, + heap_cell, + QueryPoint::Update(*update_mode_var), + |_| true, + ); + &[] + } + + ir::OpKind::EmptyBag => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let slot_count = result_slot_count(sc, &val_node); + self.add_heap_cells(slot_count) + } + + ir::OpKind::BagInsert => { + debug_assert_eq!(input_slot_arrs.len(), 2); + let slot_count = result_slot_count(sc, &val_node); + let slots = self.add_heap_cells(slot_count); + for input_slots in input_slot_arrs { + for (&input_cell, &new_cell) in input_slots.iter().zip(slots.iter()) { + self.copy_data(version, input_cell, version, new_cell); + } + } + slots + } + + ir::OpKind::BagGet => { + debug_assert_eq!(input_slot_arrs.len(), 1); + input_slot_arrs[0] + } + + ir::OpKind::BagRemove => { + debug_assert_eq!(input_slot_arrs.len(), 1); + self.slots_arena.alloc_extend( + input_slot_arrs[0] + .iter() + .chain(input_slot_arrs[0].iter()) + .cloned(), + ) + } + + ir::OpKind::MakeTuple => self.slots_arena.alloc_extend( + input_slot_arrs + .iter() + .flat_map(|slots| slots.iter().cloned()), + ), + + ir::OpKind::GetTupleField { field_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let input_type = graph.values().node(val_node.inputs[0]).op.result_type; + let field_slots = if let TypeSlots::Tuple { field_slots } = &sc.slots()[input_type] + { + field_slots + } else { + unreachable!() + }; + let (start, end) = field_slots.sub_slots(*field_idx); + &input_slot_arrs[0][start as usize..end as usize] + } + + ir::OpKind::MakeUnion { variant_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let variant_slots = if let TypeSlots::Union { variant_slots } = + &sc.slots()[val_node.op.result_type] + { + variant_slots + } else { + unreachable!() + }; + let (start, end) = variant_slots.sub_slots(*variant_idx); + debug_assert_eq!((end - start) as usize, input_slot_arrs[0].len()); + self.slots_arena + .alloc_extend((0..variant_slots.slot_count()).map(|i| { + if start <= i && i < end { + input_slot_arrs[0][(i - start) as usize] + } else { + self.add_heap_cell() + } + })) + } + + ir::OpKind::UnwrapUnion { variant_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let input_type = graph.values().node(val_node.inputs[0]).op.result_type; + let variant_slots = + if let TypeSlots::Union { variant_slots } = &sc.slots()[input_type] { + variant_slots + } else { + unreachable!() + }; + let (start, end) = variant_slots.sub_slots(*variant_idx); + &input_slot_arrs[0][start as usize..end as usize] + } + + ir::OpKind::MakeNamed => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let new_cell = self.add_heap_cell(); + for &input_cell in input_slot_arrs[0] { + self.copy_data(version, input_cell, version, new_cell); + } + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(new_cell, slot_count) + } + + ir::OpKind::UnwrapNamed => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(input_slot_arrs[0][0], slot_count) + } + }; + replace_none(&mut self.value_slots[val_id], ret_slots).unwrap(); + } + + fn target_arg_slots(&self, graph: &ir::Graph, pred: ir::Predecessor) -> &'a [HeapCellId] { + match pred { + ir::Predecessor::Block(block) => { + self.value_slots[graph.blocks().block_info(block).target_arg.unwrap()].unwrap() + } + ir::Predecessor::Entry => self.arg_slots.unwrap(), + } + } + + fn merge_slots( + &mut self, + new_version: BackRefStateVersionId, + slot_count: u32, + sources: impl Iterator, + ) -> &'a [HeapCellId] + where + F: for<'b> FnMut(&'b Self) -> BackRefStateVersionId, + G: for<'b> FnMut(&'b Self) -> I, + I: Iterator, + { + let min_new_id = self.heap_cells.count(); + let merged_slots: &[_] = self.add_heap_cells(slot_count); + for (mut source_version_fn, mut source) in sources { + let source_version = source_version_fn(self); + // We need to add all of the alias edges up front between slots in the source and slots + // in the value under construction because we might only know that two slots in the + // value under construction alias because of their transitive relationship through + // source slots. For instance, consider the following code: + // + // let x = []; + // let result: (_, _) = choice { (x, x) } or { ([], []) }; + // + // We know that the two slots in result (the value under construction) might alias + // because they alias the first and second tuple elements in the first choice branch + // respectively, and those elements alias. + let mut i = 0; + for source_heap_cell in source(self) { + let merged_heap_cell = merged_slots[i as usize]; + self.copy_data( + source_version, + source_heap_cell, + new_version, + merged_heap_cell, + ); + i += 1; + } + debug_assert_eq!(i, slot_count); + + // Consider the following code: + // + // let x = []; + // let result: (_, _) = choice { (x, []) } or { ([], x) }; + // + // The first and second slots in result (the value under construction) cannot alias. If + // we do not remove the edge between the first slot of result and the first slot of the + // tuple in the first choice branch, then on the next iteration of the loop the + // algorithm will see a transitive alias edge between them. + // + // Note also that we need to remove *all* symmetric edges pointing back from heap cells + // which predate the value under construction, not just symmetric edges pointing back + // from the heap cells which appear directly in the predecessor value. For example, + // consider the following code: + // + // let x = []; + // let y = /* something that aliases x, but has a distinct heap cell */; + // let result: (_, _) = choice { (x, []) } or { ([], y) }; + // + // After processing the first branch of the choice, we need to remove the newly-created + // edges pointing back from both x *and* y. + for &merged_heap_cell in merged_slots { + let merged_aliases = std::mem::take(&mut self.heap_cells[merged_heap_cell].aliases); + for &other in &merged_aliases { + // This removes edges back from heap cells which predate the value under + // construction, but does not remove edges between heap cells in the value under + // construction. Preserving edges within the value under construction is + // important for handling cases like the following example (also discussed + // above) correctly: + // + // let x = []; + // let result: (_, _) = choice { (x, x) } or { ([], []) }; + if other < min_new_id { + // Temporarily violate symmetry invariant + self.heap_cells[other].aliases.remove(&merged_heap_cell); + } + } + debug_assert!(self.heap_cells[merged_heap_cell].aliases.is_empty()); + self.heap_cells[merged_heap_cell].aliases = merged_aliases; + } + } + for &merged_heap_cell in merged_slots { + let merged_aliases = std::mem::take(&mut self.heap_cells[merged_heap_cell].aliases); + for &other in &merged_aliases { + if other < min_new_id { + // Restore symmetry invariant + self.heap_cells[other].aliases.insert(merged_heap_cell); + } + } + debug_assert!(self.heap_cells[merged_heap_cell].aliases.is_empty()); + self.heap_cells[merged_heap_cell].aliases = merged_aliases; + } + merged_slots + } + + fn predecessor_back_refs(&self, pred: ir::Predecessor) -> BackRefStateVersionId { + match pred { + ir::Predecessor::Entry => self.entry_version, + ir::Predecessor::Block(block) => self.block_versions[block].unwrap(), + } + } + + fn analyze_block( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + block: ir::BlockId, + ) { + let block_info = graph.blocks().block_info(block); + let new_version = self.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: block_info + .predecessors + .iter() + .map(|&pred| self.predecessor_back_refs(pred)) + .collect(), + }); + if let Some(param_id) = block_info.param { + let param_slots = if block_info.predecessors.len() == 1 { + self.target_arg_slots(graph, block_info.predecessors[0]) + } else { + let slot_count = id_result_slot_count(sc, graph, param_id); + self.merge_slots( + new_version, + slot_count, + block_info.predecessors.iter().map(|&pred| { + let pred_version = move |this: &Self| this.predecessor_back_refs(pred); + // TODO: is this correct if a block is its own predecessor, without any + // indirection? + let pred_slots = + move |this: &Self| this.target_arg_slots(graph, pred).iter().cloned(); + (pred_version, pred_slots) + }), + ) + }; + replace_none(&mut self.value_slots[param_id], param_slots).unwrap(); + } + for val_id in graph.blocks().block_values(block) { + self.analyze_value(sc, ctx, graph, new_version, val_id); + } + replace_none(&mut self.block_versions[block], new_version).unwrap(); + } + + // TODO: Everything to do with SCC analysis can be significantly optimized + + fn heap_cell_slot_mapping( + &self, + graph: &ir::Graph, + blocks: impl Iterator, + ) -> HeapCellSlotMapping { + let mut heap_cell_to_slots = HashMap::new(); + for block in blocks { + for val_id in block_values_inclusive(graph, block) { + for (i, &heap_cell) in self.value_slots[val_id].unwrap().iter().enumerate() { + heap_cell_to_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push((val_id, i.try_into().unwrap())); + } + } + } + heap_cell_to_slots + } + + fn summarize_scc( + &mut self, + graph: &ir::Graph, + blocks: impl Iterator, + min_new_id: Count, + heap_cell_slots_inductive: &HeapCellSlotMapping, + heap_cell_slots_current: &HeapCellSlotMapping, + ) -> ForwardSccSummary { + let mut summary = ForwardSccSummary::new(); + for block in blocks { + for val_id in block_values_inclusive(graph, block) { + let block_version = self.block_versions[block].unwrap(); + let slot_summaries = self.value_slots[val_id] + .unwrap() + .iter() + .enumerate() + .map(|(slot_i, &heap_cell)| { + let mut val_summary = ForwardSccSlotSummary { + pre_aliases: Set::new(), + inductive_aliases: Set::new(), + internal_aliases: Set::new(), + back_refs: Self::back_refs_in_states( + &mut self.back_ref_states, + block_version, + heap_cell, + ) + .clone(), + }; + let aliased_heap_cells = std::iter::once(heap_cell) + .chain(self.heap_cells[heap_cell].aliases.iter().cloned()); + for aliased in aliased_heap_cells { + if aliased < min_new_id { + val_summary.pre_aliases.insert(aliased); + } + for &aliased_slot in heap_cell_slots_current + .get(&aliased) + .iter() + .cloned() + .flatten() + { + if aliased_slot == (val_id, slot_i.try_into().unwrap()) { + continue; + } + val_summary.internal_aliases.insert(aliased_slot); + } + for &aliased_slot in heap_cell_slots_inductive + .get(&aliased) + .iter() + .cloned() + .flatten() + { + if aliased_slot == (val_id, slot_i.try_into().unwrap()) { + continue; + } + val_summary.inductive_aliases.insert(aliased_slot); + } + } + val_summary + }) + .collect(); + summary.insert(val_id, slot_summaries); + } + } + summary + } + + fn disconnect_heap_cell(&mut self, heap_cell: HeapCellId) { + let aliases = std::mem::take(&mut self.heap_cells[heap_cell].aliases); + for &other in &aliases { + self.heap_cells[other].aliases.remove(&heap_cell); + } + } + + fn analyze_block_scc( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + scc_id: ir::SccId, + ) { + let scc = graph.sccs().get(scc_id); + match scc.info { + SccKind::Acyclic => { + debug_assert!(scc.items.len() == 1); + self.analyze_block(sc, ctx, graph, scc.items[0]); + } + SccKind::Cyclic => { + let init_version_parents = scc + .items + .iter() + .flat_map(|&block| { + graph + .blocks() + .block_info(block) + .predecessors + .iter() + .filter_map(|&pred| match pred { + ir::Predecessor::Entry => Some(self.entry_version), + ir::Predecessor::Block(pred_block) => { + self.block_versions[pred_block] + } + }) + }) + .collect::>() + .into_iter() + .collect::>(); + let init_version = self.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: init_version_parents, + }); + + let min_new_id = self.heap_cells.count(); + for &block in scc.items { + replace_none(&mut self.block_versions[block], init_version).unwrap(); + for val_id in block_values_inclusive(graph, block) { + let slot_count = id_result_slot_count(sc, graph, val_id); + let init_slots = self.add_heap_cells(slot_count); + replace_none(&mut self.value_slots[val_id], init_slots).unwrap(); + } + } + let mut prev_iter_summary = None; + let mut prev_iter_heap_cell_slot_mapping = + self.heap_cell_slot_mapping(graph, scc.items.iter().cloned()); + let mut prev_iter_min_new_id = min_new_id; + loop { + let curr_iter_min_new_id = self.heap_cells.count(); + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores irrelevant data + for &block in scc.items { + let version = std::mem::take(&mut self.block_versions[block]); + debug_assert!(version.is_some()); + self.block_versions_inductive[block] = version; + for val_id in block_values_inclusive(graph, block) { + let slots = std::mem::take(&mut self.value_slots[val_id]); + debug_assert!(slots.is_some()); + self.value_slots_inductive[val_id] = slots; + } + // Now: + // - Main layer stores previous iteration state, except for current block, + // for which it stores 'None' + // - Inductive layer stores a mix of irrelevant data and current iteration + // state, except for current block, for which it stores previous iteration + // state + self.analyze_block(sc, ctx, graph, block); + // Now: + // - Main layer stores previous iteration state, except for current block, + // for which it stores current iteration state + // - Inductive layer stores a mix of irrelevant data and current iteration + // state, except for current block, for which it stores previous iteration + // state + std::mem::swap( + &mut self.block_versions[block], + &mut self.block_versions_inductive[block], + ); + for val_id in block_values_inclusive(graph, block) { + std::mem::swap( + &mut self.value_slots[val_id], + &mut self.value_slots_inductive[val_id], + ); + } + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores a mix of irrelevant data and current iteration + // state. In particular, for all blocks processed so far (including this + // one) it stores current iteration state. + } + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores current iteration state + for &block in scc.items { + std::mem::swap( + &mut self.block_versions[block], + &mut self.block_versions_inductive[block], + ); + for val_id in block_values_inclusive(graph, block) { + std::mem::swap( + &mut self.value_slots[val_id], + &mut self.value_slots_inductive[val_id], + ); + } + } + // Now: + // - Main layer stores current iteration state + // - Inductive layer stores previous iteration state + let curr_iter_heap_cell_slot_mapping = + self.heap_cell_slot_mapping(graph, scc.items.iter().cloned()); + let curr_iter_summary = self.summarize_scc( + graph, + scc.items.iter().cloned(), + min_new_id, + &prev_iter_heap_cell_slot_mapping, + &curr_iter_heap_cell_slot_mapping, + ); + if Some(&curr_iter_summary) == prev_iter_summary.as_ref() { + break; + } + + // Garbage collect connectsions to irrelevant heap cells from previous iteration + for heap_cell in + (prev_iter_min_new_id.0 .0..curr_iter_min_new_id.0 .0).map(HeapCellId) + { + self.disconnect_heap_cell(heap_cell); + } + + prev_iter_summary = Some(curr_iter_summary); + prev_iter_heap_cell_slot_mapping = curr_iter_heap_cell_slot_mapping; + prev_iter_min_new_id = curr_iter_min_new_id; + } + } + } + } + + fn analyze_graph( + slots_arena: &'a Arena, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + arg_alias: Option>, + ) -> (Self, &'a [HeapCellId]) { + let mut heap_cells = IdVec::new(); + let arg_slots = graph + .blocks() + .block_info(graph.entry_block()) + .param + .map(|arg_val_id| { + let slot_count = id_result_slot_count(sc, graph, arg_val_id); + let arg_slots: &[_] = slots_arena.alloc_extend((0..slot_count).map(|i| { + let mut origin_arg_slots = Set::new(); + origin_arg_slots.insert(i); + heap_cells.push(ForwardData { + origin: Origin::FromArgSlots(origin_arg_slots), + aliases: Set::new(), + }) + })); + if let Some(arg_alias) = arg_alias { + let fst = arg_slots[*arg_alias.fst() as usize]; + let snd = arg_slots[*arg_alias.snd() as usize]; + heap_cells[fst].aliases.insert(snd); + heap_cells[snd].aliases.insert(fst); + } + arg_slots + }); + + let mut back_ref_states = IdVec::new(); + let entry_version = back_ref_states.push(BackRefState { + overlay: arg_slots + .iter() + .cloned() + .flatten() + .enumerate() + .map(|(slot_i, &heap_cell)| { + let mut heap_cell_back_refs = HashSet::new(); + heap_cell_back_refs.insert(QueryPoint::EntryArg(slot_i.try_into().unwrap())); + (heap_cell, heap_cell_back_refs) + }) + .collect(), + parents: Vec::new(), + }); + + let mut state = ForwardState { + slots_arena, + value_slots: IdVec::filled_with(graph.values().count(), || None), + value_slots_inductive: IdVec::filled_with(graph.values().count(), || None), + call_arg_aliases: IdVec::filled_with(graph.callee_spec_vars(), || None), + call_arg_origins: IdVec::filled_with(graph.callee_spec_vars(), || None), + calls: IdVec::filled_with(graph.callee_spec_vars(), || None), + update_origins: IdVec::filled_with(graph.update_mode_vars(), || None), + arg_slots, + heap_cells, + back_ref_states, + block_versions: IdVec::filled_with(graph.blocks().block_count(), || None), + block_versions_inductive: IdVec::filled_with(graph.blocks().block_count(), || None), + entry_version, + fates: HashMap::new(), + }; + + for scc_id in graph.sccs().count().iter() { + state.analyze_block_scc(sc, ctx, graph, scc_id); + } + + let exit_version = state.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: graph + .exit_blocks() + .iter() + .map(|&block| state.block_versions[block].unwrap()) + .collect(), + }); + let ret_slot_count = sc.slots()[graph.ret_type()].slot_count(); + let ret_heap_cells = state.merge_slots( + exit_version, + ret_slot_count, + graph.exit_blocks().iter().map(|&block| { + let block_version = move |this: &Self| this.block_versions[block].unwrap(); + let block_slots = move |this: &Self| { + this.target_arg_slots(graph, ir::Predecessor::Block(block)) + .iter() + .cloned() + }; + (block_version, block_slots) + }), + ); + + for (ret_slot_i, &ret_heap_cell) in ret_heap_cells.iter().enumerate() { + for &query_point in + Self::back_refs_in_states(&mut state.back_ref_states, exit_version, ret_heap_cell) + .iter() + { + if let Fate::Other { ret_slots, .. } = state.fates.entry(query_point).or_default() { + ret_slots.insert(ret_slot_i.try_into().unwrap()); + } + } + } + + (state, ret_heap_cells) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Fate { + DirectTouch, + Other { + indirect_touch: bool, + ret_slots: Set, + }, +} + +impl Fate { + fn union_with(&mut self, other: &Fate) { + match (&mut *self, other) { + (Fate::DirectTouch, _) => {} + (Fate::Other { .. }, Fate::DirectTouch) => { + *self = Fate::DirectTouch; + } + ( + Fate::Other { + indirect_touch: indirect_touch_1, + ret_slots: ret_slots_1, + }, + Fate::Other { + indirect_touch: indirect_touch_2, + ret_slots: ret_slots_2, + }, + ) => { + *indirect_touch_1 = *indirect_touch_1 || *indirect_touch_2; + ret_slots_1.extend(ret_slots_2); + } + } + } +} + +impl Default for Fate { + fn default() -> Self { + Fate::Other { + indirect_touch: false, + ret_slots: Set::new(), + } + } +} + +fn analyze_func( + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + func_def: &ir::FuncDef, + arg_alias: Option>, +) -> FuncAnalysis { + let slots_arena = Arena::new(); + let (forward, ret_slots) = + ForwardState::analyze_graph(&slots_arena, sc, ctx, &func_def.graph, arg_alias); + let mut heap_cell_to_arg_slot = HashMap::::new(); + for (slot_i, &heap_cell) in forward.arg_slots.unwrap().iter().enumerate() { + let existing = heap_cell_to_arg_slot.insert(heap_cell, slot_i.try_into().unwrap()); + debug_assert!(existing.is_none()); + } + let mut heap_cell_to_ret_slots = HashMap::>::new(); + for (slot_i, &heap_cell) in ret_slots.iter().enumerate() { + heap_cell_to_ret_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push(slot_i.try_into().unwrap()); + } + let arg_slot_analyses = (0..forward.arg_slots.unwrap().len()) + .map(|arg_slot_i| ArgSlotAnalysis { + fate: forward + .fates + .get(&QueryPoint::EntryArg(arg_slot_i.try_into().unwrap())) + .cloned() + .unwrap_or_default(), + }) + .collect(); + let ret_slot_analyses = ret_slots + .iter() + .enumerate() + .map(|(this_ret_slot_i, &heap_cell)| { + let mut arg_aliases = Set::new(); + let mut ret_aliases = Set::new(); + for other in std::iter::once(heap_cell) + .chain(forward.heap_cells[heap_cell].aliases.iter().cloned()) + { + if let Some(&arg_slot_i) = heap_cell_to_arg_slot.get(&other) { + arg_aliases.insert(arg_slot_i); + } + if let Some(ret_slots_i) = heap_cell_to_ret_slots.get(&other) { + for &ret_slot_i in ret_slots_i { + if ret_slot_i as usize != this_ret_slot_i { + ret_aliases.insert(ret_slot_i); + } + } + } + } + RetSlotAnalysis { + from_const: matches!(forward.heap_cells[heap_cell].origin, Origin::FromConst), + arg_aliases, + ret_aliases, + } + }) + .collect(); + FuncAnalysis { + graph_analysis: GraphAnalysis { + updates: IdVec::filled_with_indexed( + func_def.graph.update_mode_vars(), + |update_mode_var| UpdateAnalysis { + origin: forward.update_origins[update_mode_var] + .as_ref() + .unwrap() + .clone(), + fate: forward + .fates + .get(&QueryPoint::Update(update_mode_var)) + .cloned() + .unwrap_or_default(), + }, + ), + calls: IdVec::filled_with_indexed( + func_def.graph.callee_spec_vars(), + |callee_spec_var| { + // let call_fates = backward.call_fates[callee_spec_var].as_ref().unwrap(); + let call_info = forward.calls[callee_spec_var].unwrap(); + CallAnalysis { + callee: call_info.callee, + arg_aliases: forward.call_arg_aliases[callee_spec_var] + .as_ref() + .unwrap() + .clone(), + arg_slots: forward.call_arg_origins[callee_spec_var] + .as_ref() + .unwrap() + .iter() + .enumerate() + .map(|(arg_slot_i, origin)| ArgAnalysis { + origin: origin.clone(), + fate: forward + .fates + .get(&QueryPoint::CallArg( + callee_spec_var, + arg_slot_i.try_into().unwrap(), + )) + .cloned() + .unwrap_or_default(), + }) + .collect(), + ret_slots: (0..call_info.ret_slots) + .map(|ret_slot_i| { + forward + .fates + .get(&QueryPoint::CallRet(callee_spec_var, ret_slot_i)) + .cloned() + .unwrap_or_default() + }) + .collect(), + } + }, + ), + }, + arg_slots: arg_slot_analyses, + ret_slots: ret_slot_analyses, + } +} + +id_type! { + FuncSccId(u32); +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct UpdateAnalysis { + origin: Origin, + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ArgAnalysis { + origin: Origin, + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct CallAnalysis { + // Find a better place to store the callee + callee: FuncId, + arg_aliases: Set>, + arg_slots: SmallVec<[ArgAnalysis; 4]>, + ret_slots: SmallVec<[Fate; 4]>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct GraphAnalysis { + updates: IdVec, + calls: IdVec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ArgSlotAnalysis { + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct RetSlotAnalysis { + from_const: bool, + arg_aliases: Set, + ret_aliases: Set, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct FuncAnalysis { + graph_analysis: GraphAnalysis, + arg_slots: SmallVec<[ArgSlotAnalysis; 4]>, + ret_slots: SmallVec<[RetSlotAnalysis; 4]>, +} + +#[derive(Clone, Debug)] +struct GlobalAnalysisContext<'a> { + func_defs: &'a IdVec, + sccs: &'a FlatSlices, + func_to_scc: &'a IdVec, + committed: IdVec>, FuncAnalysis>>, +} + +impl<'a> GlobalAnalysisContext<'a> { + fn analyze( + &mut self, + sc: &mut SlotCache, + func: FuncId, + arg_alias: Option>, + ) -> &FuncAnalysis { + debug_assert!(!self.committed[func].contains_key(&arg_alias)); + let scc = self.func_to_scc[func]; + let scc_kind = *self.sccs.get(scc).info; + let mut scc_ctx = SccAnalysisContext { + global: &mut *self, + scc, + prev_iter: HashMap::new(), + curr_iter: HashMap::new(), + }; + match scc_kind { + SccKind::Acyclic => { + scc_ctx.get_analysis(sc, func, arg_alias); + debug_assert_eq!(scc_ctx.curr_iter.len(), 1); + } + SccKind::Cyclic => loop { + scc_ctx.get_analysis(sc, func, arg_alias); + // TODO: only compare "signature" information here, not internal annotations on body + // values. + if scc_ctx.curr_iter == scc_ctx.prev_iter { + break; + } + scc_ctx.prev_iter = std::mem::take(&mut scc_ctx.curr_iter); + }, + }; + let results = scc_ctx.curr_iter; + for ((analyzed_func, analyzed_arg_alias), analysis) in results { + let existing = self.committed[analyzed_func] + .insert(analyzed_arg_alias, analysis.unwrap_complete()); + debug_assert!(existing.is_none()); + } + &self.committed[func][&arg_alias] + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, PartialEq, Eq)] +enum AnalysisState { + Pending, + Complete(FuncAnalysis), +} + +impl AnalysisState { + fn unwrap_complete(self) -> FuncAnalysis { + match self { + AnalysisState::Pending => unreachable!(), + AnalysisState::Complete(analysis) => analysis, + } + } + + fn unwrap_complete_ref(&self) -> &FuncAnalysis { + match self { + AnalysisState::Pending => unreachable!(), + AnalysisState::Complete(analysis) => analysis, + } + } +} + +#[derive(Debug)] +struct SccAnalysisContext<'a, 'b> { + global: &'b mut GlobalAnalysisContext<'a>, + scc: FuncSccId, + // Invariant: 'prev_iter' should contain no 'Pending' analyses + prev_iter: HashMap<(FuncId, Option>), AnalysisState>, + curr_iter: HashMap<(FuncId, Option>), AnalysisState>, +} + +impl<'a, 'b> SccAnalysisContext<'a, 'b> { + fn get_analysis<'c>( + &'c mut self, + sc: &mut SlotCache, + func: FuncId, + arg_alias: Option>, + ) -> Option<&'c FuncAnalysis> { + if self.global.committed[func].contains_key(&arg_alias) { + // TODO: is there a way to avoid the double lookup here while passing the borrow + // checker? + return Some(&self.global.committed[func][&arg_alias]); + } + if self.global.func_to_scc[func] != self.scc { + return Some(self.global.analyze(sc, func, arg_alias)); + } + // TODO: can we resolve this clippy error while passing the borrow checker? + #[allow(clippy::map_entry)] + if self.curr_iter.contains_key(&(func, arg_alias)) { + // TODO: as above, can we avoid the double lookup? + match &self.curr_iter[&(func, arg_alias)] { + AnalysisState::Complete(analysis) => Some(analysis), + AnalysisState::Pending => self + .prev_iter + .get(&(func, arg_alias)) + .map(AnalysisState::unwrap_complete_ref), + } + } else { + self.curr_iter + .insert((func, arg_alias), AnalysisState::Pending); + let analysis = analyze_func(sc, self, &self.global.func_defs[func], arg_alias); + match self.curr_iter.entry((func, arg_alias)) { + std::collections::hash_map::Entry::Occupied(mut occupied) => { + *occupied.get_mut() = AnalysisState::Complete(analysis); + Some(occupied.into_mut().unwrap_complete_ref()) + } + std::collections::hash_map::Entry::Vacant(_) => { + unreachable!() + } + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct Query { + // TODO: improve sparsity of contextual information, prune everything inessential + arg_aliases: BTreeSet>, + // For the purposes of `arg_slots_touched`, an arg slot being 'FromConst' is the same as being + // touched after the call. + arg_slots_touched: SmallVec<[bool; 8]>, + ret_slots_touched: SmallVec<[bool; 8]>, +} + +impl Query { + fn to_spec(&self, func: FuncId) -> api::FuncSpec { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(func.0.to_le_bytes()); + hasher.update((self.arg_aliases.len() as u64).to_le_bytes()); + for arg_alias in &self.arg_aliases { + hasher.update(arg_alias.fst().to_le_bytes()); + hasher.update(arg_alias.snd().to_le_bytes()); + } + hasher.update((self.arg_slots_touched.len() as u64).to_le_bytes()); + for &arg_touched in &self.arg_slots_touched { + hasher.update(&[arg_touched as u8]); + } + hasher.update((self.ret_slots_touched.len() as u64).to_le_bytes()); + for &ret_touched in &self.ret_slots_touched { + hasher.update(&[ret_touched as u8]); + } + api::FuncSpec(hasher.finalize().into()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct FuncSolution { + pub(crate) update_modes: IdVec, + pub(crate) callee_specs: IdVec, +} + +#[derive(Clone, Debug)] +pub(crate) struct FuncSolutions { + pub(crate) solutions: IdVec>>, +} + +fn resolve_origin<'a>(query: &Query, mut origins: impl Iterator) -> bool { + origins.any(|origin| match origin { + Origin::FromConst => true, + Origin::FromArgSlots(arg_slots) => arg_slots + .iter() + .any(|&arg_slot| query.arg_slots_touched[arg_slot as usize]), + }) +} + +fn resolve_fate<'a>(query: &Query, mut fates: impl Iterator) -> bool { + fates.any(|fate| match fate { + Fate::DirectTouch => true, + Fate::Other { + indirect_touch, + ret_slots, + } => { + *indirect_touch + || ret_slots + .iter() + .any(|&ret_slot| query.ret_slots_touched[ret_slot as usize]) + } + }) +} + +impl FuncSolutions { + fn resolve( + &mut self, + analyses: &IdVec>, FuncAnalysis>>, + func: FuncId, + query: &Query, + ) -> api::FuncSpec { + let spec = query.to_spec(func); + if let std::collections::hash_map::Entry::Vacant(vacant) = self.solutions[func].entry(spec) + { + let func_analyses = &analyses[func]; + let basic_analysis = &func_analyses[&None].graph_analysis; + vacant.insert(None); + let query_analyses: SmallVec<[&GraphAnalysis; 8]> = std::iter::once(basic_analysis) + .chain( + query + .arg_aliases + .iter() + .map(|&arg_alias| &func_analyses[&Some(arg_alias)].graph_analysis), + ) + .collect(); + let update_modes = + IdVec::filled_with_indexed(basic_analysis.updates.count(), |update_mode_var| { + let touched = resolve_origin( + query, + query_analyses + .iter() + .map(|analysis| &analysis.updates[update_mode_var].origin), + ) || resolve_fate( + query, + query_analyses + .iter() + .map(|analysis| &analysis.updates[update_mode_var].fate), + ); + if touched { + api::UpdateMode::Immutable + } else { + api::UpdateMode::InPlace + } + }); + let callee_specs = + IdVec::filled_with_indexed(basic_analysis.calls.count(), |callee_spec_var| { + let mut sub_arg_aliases = BTreeSet::new(); + for analysis in &query_analyses { + sub_arg_aliases.extend(&analysis.calls[callee_spec_var].arg_aliases); + } + let num_arg_slots = basic_analysis.calls[callee_spec_var].arg_slots.len(); + let num_ret_slots = basic_analysis.calls[callee_spec_var].ret_slots.len(); + let sub_arg_slots_touched = (0..num_arg_slots) + .map(|arg_slot_i| { + resolve_origin( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].arg_slots[arg_slot_i].origin + }), + ) || resolve_fate( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].arg_slots[arg_slot_i].fate + }), + ) + }) + .collect(); + let sub_ret_slots_touched = (0..num_ret_slots) + .map(|ret_slot_i| { + resolve_fate( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].ret_slots[ret_slot_i] + }), + ) + }) + .collect(); + let sub_query = Query { + arg_aliases: sub_arg_aliases, + arg_slots_touched: sub_arg_slots_touched, + ret_slots_touched: sub_ret_slots_touched, + }; + self.resolve( + analyses, + basic_analysis.calls[callee_spec_var].callee, + &sub_query, + ) + }); + let solution = FuncSolution { + update_modes, + callee_specs, + }; + self.solutions[func].insert(spec, Some(solution)); + } + spec + } +} + +#[derive(Clone, Debug)] +pub(crate) struct ProgramSolutions { + pub(crate) funcs: FuncSolutions, + pub(crate) entry_points: IdVec, +} + +pub(crate) fn analyze(tc: TypeCache, program: &ir::Program) -> ProgramSolutions { + let mut sc = SlotCache::new(tc); + + let func_sccs: FlatSlices = + strongly_connected(program.funcs.count(), |func_id| { + let func_def = &program.funcs[func_id]; + let values = func_def.graph.values(); + values + .count() + .iter() + .filter_map(move |val_id| match &values.node(val_id).op.kind { + ir::ValueKind::Op(ir::OpKind::Call { + callee, + callee_spec_var: _, + }) => Some(*callee), + + _ => None, + }) + }); + + let mut func_to_scc = IdVec::filled_with(program.funcs.count(), || FuncSccId(u32::MAX)); + for scc_id in func_sccs.count().iter() { + for &func in func_sccs.get(scc_id).items { + func_to_scc[func] = scc_id; + } + } + + let mut ctx = GlobalAnalysisContext { + func_defs: &program.funcs, + sccs: &func_sccs, + func_to_scc: &func_to_scc, + committed: IdVec::filled_with(program.funcs.count(), HashMap::new), + }; + + for (_, &func) in &program.entry_points { + if !ctx.committed[func].contains_key(&None) { + ctx.analyze(&mut sc, func, None); + } + } + + let mut func_solutions = FuncSolutions { + solutions: IdVec::filled_with(program.funcs.count(), HashMap::new), + }; + + let entry_point_solutions = program.entry_points.map(|_, &func| { + func_solutions.resolve( + &ctx.committed, + func, + &Query { + arg_aliases: BTreeSet::new(), + arg_slots_touched: SmallVec::new(), + ret_slots_touched: SmallVec::new(), + }, + ) + }); + + ProgramSolutions { + funcs: func_solutions, + entry_points: entry_point_solutions, + } +} diff --git a/vendor/morphic_lib/src/api.rs b/vendor/morphic_lib/src/api.rs index 6dc57d1c6f..893e2a1c48 100644 --- a/vendor/morphic_lib/src/api.rs +++ b/vendor/morphic_lib/src/api.rs @@ -1,7 +1,9 @@ use sha2::{digest::Digest, Sha256}; use smallvec::SmallVec; use std::collections::{btree_map::Entry, BTreeMap}; +use std::rc::Rc; +use crate::analyze; use crate::preprocess; use crate::render_api_ir; use crate::util::blocks::Blocks; @@ -1372,13 +1374,15 @@ pub enum UpdateMode { pub const SPEC_HASH_BYTES: usize = 32; #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FuncSpec(pub [u8; SPEC_HASH_BYTES]); /// The solution table for an individual specialization. pub struct FuncSpecSolutions { - func_def: FuncDef, - callee_specs: IdVec, + // TODO: eliminate the RC here (this will require introducing a lifetime, and is therefore a + // breaking API change) + func_def: Rc, + solution: analyze::FuncSolution, } impl FuncSpecSolutions { @@ -1392,7 +1396,7 @@ impl FuncSpecSolutions { .callee_spec_vars .get_by_val(&var.into()) { - Some(id) => Ok(self.callee_specs[id]), + Some(id) => Ok(self.solution.callee_specs[id]), None => Err(ErrorKind::CalleeSpecVarNotFound(var.into()).into()), } } @@ -1407,7 +1411,7 @@ impl FuncSpecSolutions { .update_mode_vars .get_by_val(&var.into()) { - Some(_id) => Ok(UpdateMode::Immutable), + Some(id) => Ok(self.solution.update_modes[id]), None => Err(ErrorKind::UpdateModeVarNotFound(var.into()).into()), } } @@ -1416,20 +1420,20 @@ impl FuncSpecSolutions { /// Zero or more specializations for a single function, and the solution table for each /// specialization. pub struct FuncSolutions { - spec: FuncSpec, - spec_solutions: FuncSpecSolutions, + spec_solutions: BTreeMap, } impl FuncSolutions { pub fn specs(&self) -> impl Iterator { - std::iter::once(&self.spec) + self.spec_solutions.keys() } pub fn spec(&self, spec: &FuncSpec) -> Result<&FuncSpecSolutions> { - if &self.spec != spec { - return Err(ErrorKind::FuncSpecNotFound(*spec).into()); + if let Some(solution) = self.spec_solutions.get(spec) { + Ok(solution) + } else { + Err(ErrorKind::FuncSpecNotFound(*spec).into()) } - Ok(&self.spec_solutions) } } @@ -1501,7 +1505,7 @@ impl ModSolutions { /// Specializations and solution tables generated for the entire program. pub struct Solutions { mods: BTreeMap, - entry_points: BTreeMap, + entry_points: BTreeMap, } impl Solutions { @@ -1521,9 +1525,8 @@ impl Solutions { // TODO: The clone here is unnecessary -- avoid it! // (might require something like a transmute) match self.entry_points.get(&entry_point.into()) { - Some((mod_name, func_name)) => { - let spec = hash_func_name(mod_name.borrowed(), func_name.borrowed()); - Ok((mod_name.borrowed(), func_name.borrowed(), spec)) + Some((mod_name, func_name, spec)) => { + Ok((mod_name.borrowed(), func_name.borrowed(), *spec)) } None => Err(ErrorKind::EntryPointNotFound(entry_point.into()).into()), } @@ -1552,11 +1555,14 @@ fn populate_specs( results.into_mapped(|_, spec| spec.unwrap()) } -pub fn solve(program: Program) -> Result { - preprocess::preprocess(&program).map_err(ErrorKind::PreprocessError)?; +pub fn solve(api_program: Program) -> Result { + let (nc, tc, program) = + preprocess::preprocess(&api_program).map_err(ErrorKind::PreprocessError)?; + + let mut solutions = analyze::analyze(tc, &program); Ok(Solutions { - mods: program + mods: api_program .mods .into_iter() .map(|(mod_name, mod_def)| { @@ -1565,16 +1571,27 @@ pub fn solve(program: Program) -> Result { .func_defs .into_iter() .map(|(func_name, func_def)| { - let callee_specs = populate_specs( - func_def.builder.expr_builder.callee_spec_vars.count(), - &func_def.builder.expr_builder.vals, - ); + // TODO: avoid the clones here + let func_id = nc + .funcs + .get_by_val(&(mod_name.clone(), func_name.clone())) + .unwrap(); + let func_def = Rc::new(func_def); let func_sols = FuncSolutions { - spec: hash_func_name(mod_name.borrowed(), func_name.borrowed()), - spec_solutions: FuncSpecSolutions { - func_def, - callee_specs, - }, + spec_solutions: std::mem::take( + &mut solutions.funcs.solutions[func_id], + ) + .into_iter() + .map(|(spec, solution)| { + ( + spec, + FuncSpecSolutions { + func_def: func_def.clone(), + solution: solution.unwrap(), + }, + ) + }) + .collect(), }; (func_name, func_sols) }) @@ -1600,7 +1617,15 @@ pub fn solve(program: Program) -> Result { (mod_name, mod_sols) }) .collect(), - entry_points: program.entry_points, + entry_points: api_program + .entry_points + .into_iter() + .map(|(entry_point_name, (mod_name, func_name))| { + let entry_point_id = nc.entry_points.get_by_val(&entry_point_name).unwrap(); + let spec = solutions.entry_points[entry_point_id]; + (entry_point_name, (mod_name, func_name, spec)) + }) + .collect(), }) } diff --git a/vendor/morphic_lib/src/ir.rs b/vendor/morphic_lib/src/ir.rs index b6408ebc98..d283d84bed 100644 --- a/vendor/morphic_lib/src/ir.rs +++ b/vendor/morphic_lib/src/ir.rs @@ -6,10 +6,12 @@ use smallvec::SmallVec; use crate::api::{CalleeSpecVarId, UpdateModeVarId}; -use crate::name_cache::{ConstId, FuncId}; +use crate::name_cache::{ConstId, EntryPointId, FuncId, NamedTypeId}; use crate::type_cache::TypeId; use crate::util::blocks::Blocks; -use crate::util::flat_slices::{FlatSlices, Slice}; +use crate::util::flat_slices::FlatSlices; +use crate::util::id_type::Count; +use crate::util::id_vec::IdVec; use crate::util::op_graph::OpGraph; use crate::util::strongly_connected::{strongly_connected, SccKind}; @@ -123,6 +125,13 @@ pub(crate) enum JumpTarget { Ret, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum Predecessor { + Block(BlockId), + Entry, +} + +pub(crate) const PREDECESSORS_INLINE_COUNT: usize = 8; pub(crate) const JUMP_TARGETS_INLINE_COUNT: usize = 8; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -132,6 +141,8 @@ pub(crate) struct BlockInfo { /// /// Invariant: If `param` is `Some`, it must point to a `BlockParam` value, not an `Op`. pub(crate) param: Option, + /// Blocks which jump to this block + pub(crate) predecessors: SmallVec<[Predecessor; PREDECESSORS_INLINE_COUNT]>, /// List of zero or more jump targets to nondeterministically choose from. pub(crate) jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, /// Optional argument which will be passed to the chosen jump target. @@ -149,6 +160,7 @@ id_type! { pub(crate) struct GraphBuilder { values: OpGraph, blocks: Blocks, + exit_blocks: SmallVec<[BlockId; 1]>, } impl GraphBuilder { @@ -156,6 +168,7 @@ impl GraphBuilder { GraphBuilder { values: OpGraph::new(), blocks: Blocks::new(), + exit_blocks: SmallVec::new(), } } @@ -182,6 +195,7 @@ impl GraphBuilder { self.values.count().0, BlockInfo { param: None, + predecessors: SmallVec::new(), jump_targets: SmallVec::new(), target_arg: None, }, @@ -200,6 +214,7 @@ impl GraphBuilder { self.values.count().0, BlockInfo { param: Some(param_id), + predecessors: SmallVec::new(), jump_targets: SmallVec::new(), target_arg: None, }, @@ -213,8 +228,22 @@ impl GraphBuilder { target_arg: Option, jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, ) { + for target in &jump_targets { + match target { + &JumpTarget::Block(successor) => { + self.blocks + .block_info_mut(successor) + .predecessors + .push(Predecessor::Block(block)); + } + JumpTarget::Ret => { + self.exit_blocks.push(block); + } + } + } let info = self.blocks.block_info_mut(block); info.target_arg = target_arg; + debug_assert!(info.jump_targets.is_empty()); info.jump_targets = jump_targets; } @@ -226,23 +255,37 @@ impl GraphBuilder { &self.blocks } - pub(crate) fn build(self, entry_block: BlockId) -> Graph { + pub(crate) fn build( + mut self, + entry_block: BlockId, + ret_type: TypeId, + update_mode_vars: Count, + callee_spec_vars: Count, + ) -> Graph { debug_assert!(entry_block < self.blocks.block_count()); - let rev_sccs = strongly_connected(self.blocks.block_count(), |block| { + self.blocks + .block_info_mut(entry_block) + .predecessors + .push(Predecessor::Entry); + let sccs = strongly_connected(self.blocks.block_count(), |block| { self.blocks .block_info(block) - .jump_targets + .predecessors .iter() - .filter_map(|&jump_target| match jump_target { - JumpTarget::Ret => None, - JumpTarget::Block(target) => Some(target), + .filter_map(|&pred| match pred { + Predecessor::Entry => None, + Predecessor::Block(pred_block) => Some(pred_block), }) }); Graph { values: self.values, blocks: self.blocks, entry_block, - rev_sccs, + exit_blocks: self.exit_blocks, + ret_type, + sccs, + update_mode_vars, + callee_spec_vars, } } } @@ -252,14 +295,15 @@ pub(crate) struct Graph { values: OpGraph, blocks: Blocks, entry_block: BlockId, - - // Invariant: `rev_sccs` must be stored in *reverse* topological order. If an SCC 'A' can jump - // to an SCC 'B', then 'A' must appear *after* 'B' in `rev_sccs`. - // - // We don't store the SCCs in topological order because control flow graph edges point from - // *source block* to *target block*, so running Tarjan's algorithm on the control flow graph - // gives us a reverse topological sort rather than a topological sort. - rev_sccs: FlatSlices, + // We use an inline capacity of 1 here because, in the current implementation of `preprocess`, + // there is always exactly one exit block per function. However, this is no fundamental reason + // this must be so. + exit_blocks: SmallVec<[BlockId; 1]>, + ret_type: TypeId, + // Invariant: `sccs` is strored in topological order. + sccs: FlatSlices, + update_mode_vars: Count, + callee_spec_vars: Count, } impl Graph { @@ -275,20 +319,24 @@ impl Graph { self.entry_block } - pub(crate) fn rev_sccs(&self) -> &FlatSlices { - &self.rev_sccs + pub(crate) fn exit_blocks(&self) -> &[BlockId] { + &self.exit_blocks } - /// Iterate over sccs in topological order. - /// - /// IF an SCC 'A' can jump to an SCC 'B', then 'A' is guaranteed to appear *before* 'B' in the - /// returned iterator. - pub(crate) fn iter_sccs(&self) -> impl Iterator> + '_ { - self.rev_sccs - .count() - .iter() - .rev() - .map(move |scc_id| self.rev_sccs.get(scc_id)) + pub(crate) fn ret_type(&self) -> TypeId { + self.ret_type + } + + pub(crate) fn sccs(&self) -> &FlatSlices { + &self.sccs + } + + pub(crate) fn update_mode_vars(&self) -> Count { + self.update_mode_vars + } + + pub(crate) fn callee_spec_vars(&self) -> Count { + self.callee_spec_vars } } @@ -301,3 +349,11 @@ pub(crate) struct FuncDef { pub(crate) struct ConstDef { pub(crate) graph: Graph, } + +#[derive(Clone, Debug)] +pub(crate) struct Program { + pub(crate) named_types: IdVec, + pub(crate) funcs: IdVec, + pub(crate) consts: IdVec, + pub(crate) entry_points: IdVec, +} diff --git a/vendor/morphic_lib/src/lib.rs b/vendor/morphic_lib/src/lib.rs index a1470770dc..3b72a6227b 100644 --- a/vendor/morphic_lib/src/lib.rs +++ b/vendor/morphic_lib/src/lib.rs @@ -4,6 +4,7 @@ #[macro_use] mod util; +mod analyze; mod api; mod bindings; mod ir; diff --git a/vendor/morphic_lib/src/preprocess.rs b/vendor/morphic_lib/src/preprocess.rs index f52e0f648b..36eccb21bd 100644 --- a/vendor/morphic_lib/src/preprocess.rs +++ b/vendor/morphic_lib/src/preprocess.rs @@ -265,7 +265,9 @@ struct FuncSig { ret_type: TypeId, } -pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { +pub(crate) fn preprocess( + program: &api::Program, +) -> Result<(NameCache, TypeCache, ir::Program), Error> { let mut nc = NameCache::default(); let mut tc = TypeCache::default(); @@ -348,15 +350,15 @@ pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { const_sigs: &const_sigs, }; - for (func_id, func_def) in &funcs { + let preprocessed_funcs = funcs.try_map(|func_id, func_def| { preprocess_func_def(&mut tc, ctx, func_def, &func_body_types[func_id]) - .map_err(Error::annotate_func_def(&nc, func_id))?; - } + .map_err(Error::annotate_func_def(&nc, func_id)) + })?; - for (const_id, const_def) in &consts { + let preprocessed_consts = consts.try_map(|const_id, const_def| { preprocess_const_def(&mut tc, ctx, const_def, &const_body_types[const_id]) - .map_err(Error::annotate_const_def(&nc, const_id))?; - } + .map_err(Error::annotate_const_def(&nc, const_id)) + })?; let mut entry_points = IdVec::::new(); for (entry_point_name, (mod_, func)) in &program.entry_points { @@ -383,7 +385,16 @@ pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { debug_assert_eq!(nc_id, pushed_id); } - Ok(()) + Ok(( + nc, + tc, + ir::Program { + named_types: typedef_contents, + funcs: preprocessed_funcs, + consts: preprocessed_consts, + entry_points, + }, + )) } #[derive(Clone, Copy, Debug)] @@ -1165,7 +1176,12 @@ fn preprocess_func_def( )?; graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); Ok(ir::FuncDef { - graph: graph_builder.build(entry_block), + graph: graph_builder.build( + entry_block, + body_types[func_def.ret_type], + func_def.builder.expr_builder.update_mode_vars.count(), + func_def.builder.expr_builder.callee_spec_vars.count(), + ), }) } @@ -1199,6 +1215,11 @@ fn preprocess_const_def( )?; graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); Ok(ir::ConstDef { - graph: graph_builder.build(entry_block), + graph: graph_builder.build( + entry_block, + body_types[const_def.type_], + const_def.builder.expr_builder.update_mode_vars.count(), + const_def.builder.expr_builder.callee_spec_vars.count(), + ), }) } diff --git a/vendor/morphic_lib/src/type_cache.rs b/vendor/morphic_lib/src/type_cache.rs index beb16b182b..d9b1b9dd18 100644 --- a/vendor/morphic_lib/src/type_cache.rs +++ b/vendor/morphic_lib/src/type_cache.rs @@ -7,6 +7,8 @@ id_type! { pub TypeId(u32); } +// TODO: Add slot information + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum TypeData { Named { named: NamedTypeId }, diff --git a/vendor/morphic_lib/src/util/blocks.rs b/vendor/morphic_lib/src/util/blocks.rs index 22c182cbfa..56a1ce60a0 100644 --- a/vendor/morphic_lib/src/util/blocks.rs +++ b/vendor/morphic_lib/src/util/blocks.rs @@ -14,6 +14,7 @@ struct BlockFrag { min_val: ValId, /// Exclusive bound max_val: ValId, + prev: Option, next: Option, } @@ -57,6 +58,7 @@ impl Blocks { let frag = BlockFrag { min_val: start_hint.clone(), max_val: start_hint, + prev: None, next: None, }; let frag_id = self.frags.push(frag); @@ -77,6 +79,7 @@ impl Blocks { let new_tail = BlockFrag { min_val: val_id.clone(), max_val: ValId::from_index_or_panic(val_id.to_index() + 1), + prev: Some(block.tail), next: None, }; let new_tail_id = self.frags.push(new_tail); @@ -113,4 +116,24 @@ impl Blocks { Some(this_val) }) } + + pub fn block_values_rev(&self, block_id: BlockId) -> impl Iterator + '_ { + let mut frag = &self.frags[self.blocks[block_id].tail]; + let mut val = frag.max_val.clone(); + std::iter::from_fn(move || { + while val.to_index() <= frag.min_val.to_index() { + match frag.prev { + Some(prev) => { + frag = &self.frags[prev]; + val = frag.max_val.clone(); + } + None => { + return None; + } + } + } + val = ValId::from_index_unchecked(val.to_index() - 1); + Some(val.clone()) + }) + } } diff --git a/vendor/morphic_lib/src/util/flat_slices.rs b/vendor/morphic_lib/src/util/flat_slices.rs index fe72cce7e8..ea56c2a624 100644 --- a/vendor/morphic_lib/src/util/flat_slices.rs +++ b/vendor/morphic_lib/src/util/flat_slices.rs @@ -99,14 +99,16 @@ impl FlatSlices { items: &mut self.flat_data[start..end], } } + + pub fn iter(&self) -> impl Iterator)> { + self.count().iter().map(move |i| (i.clone(), self.get(i))) + } } impl fmt::Debug for FlatSlices { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map() - .entries(self.count().iter().map(|i| (i.clone(), self.get(i)))) - .finish() + f.debug_map().entries(self.iter()).finish() } } diff --git a/vendor/morphic_lib/src/util/get2_mut.rs b/vendor/morphic_lib/src/util/get2_mut.rs new file mode 100644 index 0000000000..8359e118ab --- /dev/null +++ b/vendor/morphic_lib/src/util/get2_mut.rs @@ -0,0 +1,16 @@ +use std::cmp::Ordering; + +// inspired by https://docs.rs/generational-arena/0.2.8/generational_arena/struct.Arena.html#method.get2_mut +pub fn get2_mut(slice: &mut [T], i: usize, j: usize) -> Option<(&mut T, &mut T)> { + match i.cmp(&j) { + Ordering::Less => { + let (l, r) = slice.split_at_mut(j); + Some((&mut l[i], &mut r[0])) + } + Ordering::Greater => { + let (l, r) = slice.split_at_mut(i); + Some((&mut r[0], &mut l[j])) + } + Ordering::Equal => None, + } +} diff --git a/vendor/morphic_lib/src/util/id_bi_map.rs b/vendor/morphic_lib/src/util/id_bi_map.rs index 00b04c2142..876574c49f 100644 --- a/vendor/morphic_lib/src/util/id_bi_map.rs +++ b/vendor/morphic_lib/src/util/id_bi_map.rs @@ -67,4 +67,8 @@ impl IdBiMap { pub fn get_by_val(&self, val: &V) -> Option { self.val_to_key.get(val).cloned() } + + pub fn iter(&self) -> impl Iterator { + self.key_to_val.iter() + } } diff --git a/vendor/morphic_lib/src/util/id_vec.rs b/vendor/morphic_lib/src/util/id_vec.rs index ab222559ea..52c6553502 100644 --- a/vendor/morphic_lib/src/util/id_vec.rs +++ b/vendor/morphic_lib/src/util/id_vec.rs @@ -6,6 +6,7 @@ use std::ops::{Index, IndexMut}; use std::slice; use std::vec; +use crate::util::get2_mut::get2_mut; use crate::util::id_type::{Count, Id}; #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -129,6 +130,13 @@ impl IdVec { } } + pub fn filled_with_indexed(count: Count, mut f: impl FnMut(K) -> V) -> Self { + IdVec { + key: PhantomData, + items: count.iter().map(|k| f(k)).collect(), + } + } + pub fn items(&self) -> &[V] { &self.items } @@ -234,6 +242,10 @@ impl IdVec { } Some(Self::from_items(items)) } + + pub fn get2_mut(&mut self, i: K, j: K) -> Option<(&mut V, &mut V)> { + get2_mut(&mut self.items, i.to_index(), j.to_index()) + } } impl> Index for IdVec { diff --git a/vendor/morphic_lib/src/util/mod.rs b/vendor/morphic_lib/src/util/mod.rs index d5fb572b08..a7f5146c48 100644 --- a/vendor/morphic_lib/src/util/mod.rs +++ b/vendor/morphic_lib/src/util/mod.rs @@ -9,8 +9,10 @@ pub mod forward_trait; pub mod blocks; pub mod flat_slices; +pub mod get2_mut; pub mod id_bi_map; pub mod id_vec; +pub mod norm_pair; pub mod op_graph; pub mod replace_none; pub mod strongly_connected; diff --git a/vendor/morphic_lib/src/util/norm_pair.rs b/vendor/morphic_lib/src/util/norm_pair.rs new file mode 100644 index 0000000000..f370b40cfa --- /dev/null +++ b/vendor/morphic_lib/src/util/norm_pair.rs @@ -0,0 +1,33 @@ +/// A normalized unordered pair, where the first component is always <= the second +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct NormPair(T, T); + +impl NormPair { + pub fn new(fst: T, snd: T) -> Self { + if fst <= snd { + NormPair(fst, snd) + } else { + NormPair(snd, fst) + } + } + + pub fn fst(&self) -> &T { + &self.0 + } + + pub fn snd(&self) -> &T { + &self.1 + } + + pub fn into_fst(self) -> T { + self.0 + } + + pub fn into_snd(self) -> T { + self.1 + } + + pub fn into_tuple(self) -> (T, T) { + (self.0, self.1) + } +} diff --git a/vendor/morphic_lib/tests/recursive.rs b/vendor/morphic_lib/tests/recursive.rs index 9e50172a50..629e19852d 100644 --- a/vendor/morphic_lib/tests/recursive.rs +++ b/vendor/morphic_lib/tests/recursive.rs @@ -1,6 +1,6 @@ use morphic_lib::{ BlockExpr, CalleeSpecVar, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, - ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateModeVar, + ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateMode, UpdateModeVar, }; #[test] @@ -83,7 +83,8 @@ fn test_recursive() { .func_solutions(FuncName(b"rec"))? .spec(&rec_spec)?; - let _update_mode = rec_sol.update_mode(UpdateModeVar(b"mode"))?; + let update_mode = rec_sol.update_mode(UpdateModeVar(b"mode"))?; + assert_eq!(update_mode, UpdateMode::InPlace); Ok(()) } diff --git a/vendor/morphic_lib/tests/structures.rs b/vendor/morphic_lib/tests/structures.rs new file mode 100644 index 0000000000..7143e3f9e1 --- /dev/null +++ b/vendor/morphic_lib/tests/structures.rs @@ -0,0 +1,73 @@ +use morphic_lib::{ + BlockExpr, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, ModDefBuilder, + ModName, ProgramBuilder, TypeContext, UpdateMode, UpdateModeVar, +}; + +#[test] +fn test_structures() { + fn run() -> Result<(), Error> { + let main_def = { + let mut f = FuncDefBuilder::new(); + let b = f.add_block(); + let h1 = f.add_new_heap_cell(b)?; + let h2 = f.add_new_heap_cell(b)?; + let t = f.add_make_tuple(b, &[h1, h2])?; + let heap_cell_type = f.add_heap_cell_type(); + let u1 = f.add_make_union(b, &[heap_cell_type, heap_cell_type], 0, h1)?; + let u2 = f.add_make_union(b, &[heap_cell_type, heap_cell_type], 1, h2)?; + let h3 = f.add_get_tuple_field(b, t, 0)?; + let h4 = f.add_get_tuple_field(b, t, 1)?; + let h5 = f.add_unwrap_union(b, u1, 0)?; + let h6 = f.add_unwrap_union(b, u2, 1)?; + f.add_touch(b, h3)?; + f.add_update_write_only(b, UpdateModeVar(b"mode1"), h1)?; + f.add_update_write_only(b, UpdateModeVar(b"mode2"), h2)?; + f.add_update_write_only(b, UpdateModeVar(b"mode3"), h5)?; + f.add_update_write_only(b, UpdateModeVar(b"mode4"), h6)?; + f.add_touch(b, h4)?; + let unit = f.add_make_tuple(b, &[])?; + let unit_type = f.add_tuple_type(&[])?; + f.build(unit_type, unit_type, BlockExpr(b, unit))? + }; + + let main_mod = { + let mut m = ModDefBuilder::new(); + m.add_func(FuncName(b"main"), main_def)?; + m.build()? + }; + + let program = { + let mut p = ProgramBuilder::new(); + p.add_mod(ModName(b"main"), main_mod)?; + p.add_entry_point(EntryPointName(b"main"), ModName(b"main"), FuncName(b"main"))?; + p.build()? + }; + + let program_sol = morphic_lib::solve(program)?; + + let (_, _, main_spec) = program_sol.entry_point_solution(EntryPointName(b"main"))?; + + let main_mod_sol = program_sol.mod_solutions(ModName(b"main"))?; + + let main_def_sol = main_mod_sol + .func_solutions(FuncName(b"main"))? + .spec(&main_spec)?; + + let mode1 = main_def_sol.update_mode(UpdateModeVar(b"mode1"))?; + let mode2 = main_def_sol.update_mode(UpdateModeVar(b"mode2"))?; + let mode3 = main_def_sol.update_mode(UpdateModeVar(b"mode3"))?; + let mode4 = main_def_sol.update_mode(UpdateModeVar(b"mode4"))?; + + assert_eq!(mode1, UpdateMode::InPlace); + assert_eq!(mode2, UpdateMode::Immutable); + assert_eq!(mode3, UpdateMode::InPlace); + assert_eq!(mode4, UpdateMode::Immutable); + + Ok(()) + } + + let result = run(); + if let Err(err) = result { + panic!("error: {}", err); + } +} diff --git a/www/public/index.html b/www/public/index.html index 7144f8368a..cccb3ab181 100644 --- a/www/public/index.html +++ b/www/public/index.html @@ -16,9 +16,10 @@

Roc's initial release is still under development, and this website is a placeholder until that release is ready.

In the meantime, if you'd like to learn more about Roc, here are some videos: