Merge branch 'trunk' into nested-rigid-introduced-twice

This commit is contained in:
Folkert de Vries 2021-10-03 23:55:23 +02:00 committed by GitHub
commit 3d82369bcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
119 changed files with 11699 additions and 4281 deletions

View file

@ -7,6 +7,7 @@ name: Benchmarks
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
ROC_NUM_WORKERS: 1
jobs: jobs:
prep-dependency-container: prep-dependency-container:
@ -42,4 +43,4 @@ jobs:
run: cd ci/bench-runner && cargo build --release && cd ../.. run: cd ci/bench-runner && cargo build --release && cd ../..
- name: run benchmarks with regression check - 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

3
.gitignore vendored
View file

@ -26,6 +26,9 @@ editor/benches/resources/25000000_lines.roc
editor/benches/resources/50000_lines.roc editor/benches/resources/50000_lines.roc
editor/benches/resources/500_lines.roc editor/benches/resources/500_lines.roc
# file editor creates when no arg is passed
new-roc-project
# rust cache (sccache folder) # rust cache (sccache folder)
sccache_dir sccache_dir

400
Cargo.lock generated
View file

@ -131,10 +131,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "ash" name = "arrayvec"
version = "0.32.1" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"libloading 0.7.0", "libloading 0.7.0",
] ]
@ -332,9 +338,6 @@ name = "cc"
version = "1.0.70" version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
dependencies = [
"jobserver",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -1074,15 +1077,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 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]] [[package]]
name = "dtoa" name = "dtoa"
version = "0.4.8" version = "0.4.8"
@ -1154,16 +1148,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -1182,6 +1166,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]]
name = "fixedbitset"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.21" version = "1.0.21"
@ -1215,6 +1205,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]] [[package]]
name = "fuchsia-cprng" name = "fuchsia-cprng"
version = "0.1.1" version = "0.1.1"
@ -1391,168 +1387,6 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1", "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]] [[package]]
name = "ghost" name = "ghost"
version = "0.1.2" version = "0.1.2"
@ -1583,9 +1417,9 @@ checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.9.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108" checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"slotmap", "slotmap",
@ -1634,9 +1468,9 @@ dependencies = [
[[package]] [[package]]
name = "gpu-alloc" name = "gpu-alloc"
version = "0.4.7" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc1b6ca374e81862526786d9cb42357ce03706ed1b8761730caafd02ab91f3a" checksum = "ab8524eac5fc9d05625c891adf78fcf64dc0ee9f8d0882874b9f220f42b442bf"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"gpu-alloc-types", "gpu-alloc-types",
@ -1653,9 +1487,9 @@ dependencies = [
[[package]] [[package]]
name = "gpu-descriptor" name = "gpu-descriptor"
version = "0.1.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" checksum = "d7a237f0419ab10d17006d55c62ac4f689a6bf52c75d3f38b8361d249e8d4b0b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"gpu-descriptor-types", "gpu-descriptor-types",
@ -1948,15 +1782,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.54" version = "0.3.54"
@ -2279,13 +2104,14 @@ dependencies = [
"sha2", "sha2",
"smallvec", "smallvec",
"thiserror", "thiserror",
"typed-arena",
] ]
[[package]] [[package]]
name = "naga" name = "naga"
version = "0.5.0" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef670817eef03d356d5a509ea275e7dd3a78ea9e24261ea3cb2dfed1abb08f64" checksum = "8c5859e55c51da10b98e7a73068e0a0c5da7bbcae4fc38f86043d0c6d1b917cf"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"bitflags", "bitflags",
@ -2293,9 +2119,8 @@ dependencies = [
"fxhash", "fxhash",
"log", "log",
"num-traits", "num-traits",
"petgraph", "petgraph 0.6.0",
"rose_tree", "spirv",
"spirv_headers",
"thiserror", "thiserror",
] ]
@ -2644,7 +2469,7 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"instant", "instant",
"libc", "libc",
"petgraph", "petgraph 0.5.1",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"thread-id", "thread-id",
@ -2706,7 +2531,17 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [ 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", "indexmap",
] ]
@ -3566,6 +3401,7 @@ dependencies = [
"confy", "confy",
"copypasta", "copypasta",
"env_logger 0.8.4", "env_logger 0.8.4",
"fs_extra",
"futures", "futures",
"glyph_brush", "glyph_brush",
"im 15.0.0", "im 15.0.0",
@ -3583,9 +3419,11 @@ dependencies = [
"quickcheck 1.0.3", "quickcheck 1.0.3",
"quickcheck_macros 1.0.0", "quickcheck_macros 1.0.0",
"rand 0.8.4", "rand 0.8.4",
"roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_fmt", "roc_fmt",
"roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
@ -3597,6 +3435,8 @@ dependencies = [
"ropey", "ropey",
"serde", "serde",
"snafu", "snafu",
"tempfile",
"uuid",
"ven_graph", "ven_graph",
"wgpu", "wgpu",
"wgpu_glyph", "wgpu_glyph",
@ -3942,15 +3782,6 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "rose_tree"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2"
dependencies = [
"petgraph",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.21" version = "0.1.21"
@ -4227,9 +4058,12 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
[[package]] [[package]]
name = "slotmap" name = "slotmap"
version = "0.4.3" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf34684c5767b87de9119790e92e9a1d60056be2ceeaf16a8e6ef13082aeab1" checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@ -4306,21 +4140,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "spirv_cross" name = "spirv"
version = "0.23.1" version = "0.2.0+1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
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"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"num-traits", "num-traits",
@ -4338,15 +4161,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 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]] [[package]]
name = "strip-ansi-escapes" name = "strip-ansi-escapes"
version = "0.1.1" version = "0.1.1"
@ -4565,12 +4379,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "thunderdome"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87b4947742c93ece24a0032141d9caa3d853752e694a57e35029dd2bd08673e0"
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@ -4757,6 +4565,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 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]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"
@ -4781,7 +4598,7 @@ dependencies = [
name = "ven_pretty" name = "ven_pretty"
version = "0.9.1-alpha.0" version = "0.9.1-alpha.0"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.5.2",
"criterion", "criterion",
"difference", "difference",
"tempfile", "tempfile",
@ -4807,7 +4624,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.5.2",
"utf8parse", "utf8parse",
"vte_generate_state_changes", "vte_generate_state_changes",
] ]
@ -5224,9 +5041,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.50" version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -5234,14 +5051,13 @@ dependencies = [
[[package]] [[package]]
name = "wgpu" name = "wgpu"
version = "0.9.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd247f8b26fd3d42ef2f320d378025cd6e84d782ef749fab45cc3b981fbe3275" checksum = "3d92a4fe73b1e7d7ef99938dacd49258cbf1ad87cdb5bf6efa20c27447442b45"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.7.1",
"js-sys", "js-sys",
"log", "log",
"naga",
"parking_lot", "parking_lot",
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
@ -5249,29 +5065,21 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"wgpu-core", "wgpu-core",
"wgpu-hal",
"wgpu-types", "wgpu-types",
] ]
[[package]] [[package]]
name = "wgpu-core" name = "wgpu-core"
version = "0.9.2" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958a8a5e418492723ab4e7933bf6dbdf06f5dc87274ba2ae0e4f9c891aac579c" checksum = "5f1b4d918c970526cbc83b72ccb72dbefd38aec45f07b2310de4ffcd7f4bd8c5"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.7.1",
"bitflags", "bitflags",
"cfg_aliases", "cfg_aliases",
"copyless", "copyless",
"fxhash", "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", "log",
"naga", "naga",
"parking_lot", "parking_lot",
@ -5279,23 +5087,58 @@ dependencies = [
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
"thiserror", "thiserror",
"wgpu-hal",
"wgpu-types", "wgpu-types",
] ]
[[package]] [[package]]
name = "wgpu-types" name = "wgpu-hal"
version = "0.9.0" version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"bitflags", "bitflags",
] ]
[[package]] [[package]]
name = "wgpu_glyph" name = "wgpu_glyph"
version = "0.13.0" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fee8c96eda18195a7ad9989737183e0a357f14b15e98838c76abbcf56a5f970" checksum = "cbf11aebbcf20806535bee127367bcb393c83d77c60c4f7917184d839716cf41"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"glyph_brush", "glyph_brush",
@ -5388,15 +5231,6 @@ dependencies = [
"x11-dl", "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]] [[package]]
name = "ws2_32-sys" name = "ws2_32-sys"
version = "0.2.1" version = "0.2.1"

View file

@ -65,7 +65,7 @@ check-rustfmt:
RUN cargo fmt --all -- --check RUN cargo fmt --all -- --check
check-typos: 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 ./ COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
RUN typos RUN typos
@ -97,11 +97,10 @@ test-all:
BUILD +test-zig BUILD +test-zig
BUILD +check-rustfmt BUILD +check-rustfmt
BUILD +check-clippy BUILD +check-clippy
BUILD +check-typos
BUILD +test-rust BUILD +test-rust
BUILD +verify-no-git-changes 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: prep-bench-folder:
FROM +copy-dirs FROM +copy-dirs
ARG BENCH_SUFFIX=branch ARG BENCH_SUFFIX=branch

View file

@ -25,12 +25,12 @@ fn bench_group_wall_time(c: &mut Criterion) {
group.sample_size(nr_of_runs); group.sample_size(nr_of_runs);
let bench_funcs: Vec<fn(Option<&mut BenchmarkGroup<WallTime>>) -> ()> = vec![ let bench_funcs: Vec<fn(Option<&mut BenchmarkGroup<WallTime>>) -> ()> = vec![
bench_nqueens, // queens 11 bench_nqueens, // queens 11
bench_cfold, // e = mkExpr 17 1 bench_cfold, // e = mkExpr 17 1
bench_deriv, // nest deriv 8 f bench_deriv, // nest deriv 8 f
bench_rbtree_ck, // ms = makeMap 5 80000 bench_rbtree_ck, // ms = makeMap 5 80000
bench_rbtree_delete, // m = makeMap 100000 // bench_rbtree_delete, // m = makeMap 100000
bench_quicksort, // list size 10000 bench_quicksort, // list size 10000
]; ];
for bench_func in bench_funcs.iter() { for bench_func in bench_funcs.iter() {

View file

@ -131,6 +131,7 @@ pub fn bench_rbtree_ck<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGro
); );
} }
#[allow(dead_code)]
pub fn bench_rbtree_delete<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) { pub fn bench_rbtree_delete<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) {
exec_bench_w_input( exec_bench_w_input(
&example_file("benchmarks", "RBTreeDel.roc"), &example_file("benchmarks", "RBTreeDel.roc"),

View file

@ -3,6 +3,8 @@ use roc_build::{
link::{link, rebuild_host, LinkType}, link::{link, rebuild_host, LinkType},
program, program,
}; };
#[cfg(feature = "llvm")]
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
@ -10,6 +12,7 @@ use roc_mono::ir::OptLevel;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use target_lexicon::Triple; use target_lexicon::Triple;
#[cfg(feature = "llvm")]
use tempfile::Builder; use tempfile::Builder;
fn report_timing(buf: &mut String, label: &str, duration: Duration) { fn report_timing(buf: &mut String, label: &str, duration: Duration) {
@ -240,11 +243,19 @@ pub fn build_file<'a>(
})?; })?;
BuildOutcome::NoProblems BuildOutcome::NoProblems
} else { } 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 let (mut child, _) = // TODO use lld
link( link(
target, target,
binary_path.clone(), binary_path.clone(),
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], &inputs,
link_type link_type
) )
.map_err(|_| { .map_err(|_| {

View file

@ -11,12 +11,13 @@ use std::path::{Path, PathBuf};
#[global_allocator] #[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[cfg(feature = "llvm")]
use roc_cli::build;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
#[cfg(feature = "llvm")]
use roc_cli::build;
#[cfg(not(feature = "llvm"))] #[cfg(not(feature = "llvm"))]
fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> { fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
panic!("Building without LLVM is not currently supported."); panic!("Building without LLVM is not currently supported.");
} }
@ -33,7 +34,7 @@ fn main() -> io::Result<()> {
} }
None => { None => {
launch_editor(&[])?; launch_editor(None)?;
Ok(0) 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) .subcommand_matches(CMD_EDIT)
.unwrap() .unwrap()
.values_of_os(DIRECTORY_OR_FILES) .values_of_os(DIRECTORY_OR_FILES)
.map(|mut values| values.next())
{ {
None => { Some(Some(os_str)) => {
launch_editor(&[])?; launch_editor(Some(Path::new(os_str)))?;
} }
Some(values) => { _ => {
let paths = values launch_editor(None)?;
.map(|os_str| Path::new(os_str))
.collect::<Vec<&Path>>();
launch_editor(&paths)?;
} }
} }
@ -186,8 +184,8 @@ fn roc_files_recursive<P: AsRef<Path>>(
} }
#[cfg(feature = "editor")] #[cfg(feature = "editor")]
fn launch_editor(filepaths: &[&Path]) -> io::Result<()> { fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> {
roc_editor::launch(filepaths) roc_editor::launch(project_dir_path)
} }
#[cfg(not(feature = "editor"))] #[cfg(not(feature = "editor"))]

View file

@ -435,77 +435,77 @@ mod cli_run {
} }
benchmarks! { benchmarks! {
nqueens => Example { nqueens => Example {
filename: "NQueens.roc", filename: "NQueens.roc",
executable_filename: "nqueens", executable_filename: "nqueens",
stdin: &["6"], stdin: &["6"],
expected_ending: "4\n", expected_ending: "4\n",
use_valgrind: true, use_valgrind: true,
}, },
cfold => Example { cfold => Example {
filename: "CFold.roc", filename: "CFold.roc",
executable_filename: "cfold", executable_filename: "cfold",
stdin: &["3"], stdin: &["3"],
expected_ending: "11 & 11\n", expected_ending: "11 & 11\n",
use_valgrind: true, use_valgrind: true,
}, },
deriv => Example { deriv => Example {
filename: "Deriv.roc", filename: "Deriv.roc",
executable_filename: "deriv", executable_filename: "deriv",
stdin: &["2"], stdin: &["2"],
expected_ending: "1 count: 6\n2 count: 22\n", expected_ending: "1 count: 6\n2 count: 22\n",
use_valgrind: true, use_valgrind: true,
}, },
rbtree_ck => Example { rbtree_ck => Example {
filename: "RBTreeCk.roc", filename: "RBTreeCk.roc",
executable_filename: "rbtree-ck", executable_filename: "rbtree-ck",
stdin: &["100"], stdin: &["100"],
expected_ending: "10\n", expected_ending: "10\n",
use_valgrind: true, use_valgrind: true,
}, },
rbtree_insert => Example { rbtree_insert => Example {
filename: "RBTreeInsert.roc", filename: "RBTreeInsert.roc",
executable_filename: "rbtree-insert", executable_filename: "rbtree-insert",
stdin: &[], stdin: &[],
expected_ending: "Node Black 0 {} Empty Empty\n", expected_ending: "Node Black 0 {} Empty Empty\n",
use_valgrind: true, use_valgrind: true,
}, },
rbtree_del => Example { // rbtree_del => Example {
filename: "RBTreeDel.roc", // filename: "RBTreeDel.roc",
executable_filename: "rbtree-del", // executable_filename: "rbtree-del",
stdin: &["420"], // stdin: &["420"],
expected_ending: "30\n", // expected_ending: "30\n",
use_valgrind: true, // use_valgrind: true,
}, // },
astar => Example { astar => Example {
filename: "TestAStar.roc", filename: "TestAStar.roc",
executable_filename: "test-astar", executable_filename: "test-astar",
stdin: &[], stdin: &[],
expected_ending: "True\n", expected_ending: "True\n",
use_valgrind: false, use_valgrind: false,
}, },
base64 => Example { base64 => Example {
filename: "TestBase64.roc", filename: "TestBase64.roc",
executable_filename: "test-base64", executable_filename: "test-base64",
stdin: &[], stdin: &[],
expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
use_valgrind: true, use_valgrind: true,
}, },
closure => Example { closure => Example {
filename: "Closure.roc", filename: "Closure.roc",
executable_filename: "closure", executable_filename: "closure",
stdin: &[], stdin: &[],
expected_ending: "", expected_ending: "",
use_valgrind: true, use_valgrind: true,
}, },
quicksort_app => Example { quicksort_app => Example {
filename: "QuicksortApp.roc", filename: "QuicksortApp.roc",
executable_filename: "quicksortapp", executable_filename: "quicksortapp",
stdin: &[], stdin: &[],
expected_ending: "todo put the correct quicksort answer here", expected_ending: "todo put the correct quicksort answer here",
use_valgrind: true, use_valgrind: true,
}, },
} }
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { 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(); file.read_exact(buf).unwrap();
// Only app modules in this directory are considered benchmarks. // 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(|| { 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);
}); });
} }
} }
} }

View file

@ -502,6 +502,11 @@ mod repl_eval {
expect_success("\\x -> x", "<function> : a -> a"); expect_success("\\x -> x", "<function> : a -> a");
} }
#[test]
fn sum_lambda() {
expect_success("\\x, y -> x + y", "<function> : Num a, Num a -> Num a");
}
#[test] #[test]
fn stdlib_function() { fn stdlib_function() {
expect_success("Num.abs", "<function> : Num a -> Num a"); expect_success("Num.abs", "<function> : Num a -> Num a");

View file

@ -1,7 +1,8 @@
use crate::target::arch_str; use crate::target::arch_str;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use libloading::{Error, Library}; use libloading::{Error, Library};
#[cfg(feature = "llvm")] use roc_builtins::bitcode;
// #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
@ -93,7 +94,12 @@ pub fn build_zig_host_native(
.env("PATH", env_path) .env("PATH", env_path)
.env("HOME", env_home); .env("HOME", env_home);
if let Some(shared_lib_path) = shared_lib_path { 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 { } else {
command.args(&["build-obj", "-fPIC"]); command.args(&["build-obj", "-fPIC"]);
} }
@ -109,7 +115,6 @@ pub fn build_zig_host_native(
// include libc // include libc
"--library", "--library",
"c", "c",
"--strip",
// cross-compile? // cross-compile?
"-target", "-target",
target, target,
@ -178,7 +183,12 @@ pub fn build_zig_host_native(
.env("PATH", &env_path) .env("PATH", &env_path)
.env("HOME", &env_home); .env("HOME", &env_home);
if let Some(shared_lib_path) = shared_lib_path { 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 { } else {
command.args(&["build-obj", "-fPIC"]); command.args(&["build-obj", "-fPIC"]);
} }
@ -197,7 +207,6 @@ pub fn build_zig_host_native(
// include libc // include libc
"--library", "--library",
"c", "c",
"--strip",
]); ]);
if matches!(opt_level, OptLevel::Optimize) { if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]); command.args(&["-O", "ReleaseSafe"]);
@ -274,6 +283,7 @@ pub fn build_c_host_native(
if let Some(shared_lib_path) = shared_lib_path { if let Some(shared_lib_path) = shared_lib_path {
command.args(&[ command.args(&[
shared_lib_path.to_str().unwrap(), shared_lib_path.to_str().unwrap(),
bitcode::OBJ_PATH,
"-fPIE", "-fPIE",
"-pie", "-pie",
"-lm", "-lm",

View file

@ -2,11 +2,9 @@
use roc_gen_llvm::llvm::build::module_from_builtins; use roc_gen_llvm::llvm::build::module_from_builtins;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
pub use roc_gen_llvm::llvm::build::FunctionIterator; pub use roc_gen_llvm::llvm::build::FunctionIterator;
#[cfg(feature = "llvm")]
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
#[cfg(feature = "llvm")]
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const RocResult = utils.RocResult; const RocResult = utils.RocResult;
const UpdateMode = utils.UpdateMode;
const mem = std.mem; const mem = std.mem;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; 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 { pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList {
if (self.isEmpty()) { if (self.isEmpty()) {
return self; 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 Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]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| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
var i: usize = 0; var i: usize = 0;
const end: usize = size - 1; const end: usize = size - 1;
if (list.isUnique()) { if (update_mode == .InPlace or list.isUnique()) {
// Working from the front and back so // Working from the front and back so
// we only need to go ~(n / 2) iterations. // 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; 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(); const old_length = list.len();
var output = list.reallocate(alignment, old_length + 1, element_width); 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 (output.bytes) |target| {
if (element) |source| { if (element) |source| {
@memcpy(target + old_length * element_width, source, element_width); @memcpy(target + old_length * element_width, source, element_width);
@ -763,18 +775,24 @@ pub fn listSwap(
element_width: usize, element_width: usize,
index_1: usize, index_1: usize,
index_2: usize, index_2: usize,
update_mode: UpdateMode,
) callconv(.C) RocList { ) callconv(.C) RocList {
const size = list.len(); 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 // Either index out of bounds so we just return
return list; 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| { const source_ptr = @ptrCast([*]u8, newList.bytes);
swapElements(source_ptr, element_width, index_1, index_2); swapElements(source_ptr, element_width, index_1, index_2);
}
return newList; return newList;
} }

View file

@ -101,6 +101,7 @@ comptime {
exportStrFn(str.strToUtf8C, "to_utf8"); exportStrFn(str.strToUtf8C, "to_utf8");
exportStrFn(str.fromUtf8C, "from_utf8"); exportStrFn(str.fromUtf8C, "from_utf8");
exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
} }
// Utils // Utils
@ -164,7 +165,12 @@ test "" {
// https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig // https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig
// //
// Thank you Zig Contributors! // 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); // @setRuntimeSafety(std.builtin.is_test);
const min = @bitCast(i128, @as(u128, 1 << (128 - 1))); const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));

View file

@ -1,5 +1,6 @@
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList; const RocList = @import("list.zig").RocList;
const UpdateMode = utils.UpdateMode;
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const always_inline = std.builtin.CallOptions.Modifier.always_inline; 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; 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 // Str.startsWithCodePt
pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool { pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool {
const bytes_ptr = string.asU8ptr(); const bytes_ptr = string.asU8ptr();
@ -1131,10 +1148,10 @@ test "RocStr.joinWith: result is big" {
// Str.toUtf8 // Str.toUtf8
pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList { 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()) { if (arg.isEmpty()) {
return RocList.empty(); return RocList.empty();
} else if (arg.isSmallStr()) { } else if (arg.isSmallStr()) {
@ -1161,11 +1178,11 @@ const CountAndStart = extern struct {
start: usize, start: usize,
}; };
pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { pub fn fromUtf8C(arg: RocList, update_mode: UpdateMode, output: *FromUtf8Result) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); 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]; const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length];
if (unicode.utf8ValidateSlice(bytes)) { if (unicode.utf8ValidateSlice(bytes)) {
@ -1178,13 +1195,23 @@ fn fromUtf8(arg: RocList) FromUtf8Result {
const data_bytes = arg.len(); const data_bytes = arg.len();
utils.decref(arg.bytes, data_bytes, RocStr.alignment); 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 { } 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 }; 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 { } else {
const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length);
@ -1193,7 +1220,12 @@ fn fromUtf8(arg: RocList) FromUtf8Result {
const data_bytes = arg.len(); const data_bytes = arg.len();
utils.decref(arg.bytes, data_bytes, RocStr.alignment); 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 { 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 { fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8(str); return fromUtf8(str, .Immutable);
} }
fn expectOk(result: FromUtf8Result) !void { fn expectOk(result: FromUtf8Result) !void {

View file

@ -256,3 +256,8 @@ pub const Ordering = enum(u8) {
GT = 1, GT = 1,
LT = 2, LT = 2,
}; };
pub const UpdateMode = extern enum(u8) {
Immutable = 0,
InPlace = 1,
};

View file

@ -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_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_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_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 = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";

View file

@ -618,6 +618,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(str_type()) 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 ]* // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
{ {
let bad_utf8 = SolvedType::TagUnion( let bad_utf8 = SolvedType::TagUnion(

View file

@ -66,6 +66,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_FROM_UTF8_RANGE => str_from_utf8_range, STR_FROM_UTF8_RANGE => str_from_utf8_range,
STR_TO_UTF8 => str_to_utf8, STR_TO_UTF8 => str_to_utf8,
STR_FROM_FLOAT=> str_from_float, STR_FROM_FLOAT=> str_from_float,
STR_REPEAT => str_repeat,
LIST_LEN => list_len, LIST_LEN => list_len,
LIST_GET => list_get, LIST_GET => list_get,
LIST_SET => list_set, 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 /// Str.concat : Str, Str -> Str
fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh(); let str_var = var_store.fresh();

View file

@ -226,6 +226,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>, _symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)], _args: &'a [(Layout<'a>, Symbol)],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,

View file

@ -9,7 +9,7 @@ use std::marker::PhantomData;
pub mod aarch64; pub mod aarch64;
pub mod x86_64; pub mod x86_64;
const PTR_SIZE: u32 = 64; const PTR_SIZE: u32 = 8;
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> { pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
const GENERAL_PARAM_REGS: &'static [GeneralReg]; const GENERAL_PARAM_REGS: &'static [GeneralReg];
@ -48,6 +48,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
// load_args updates the symbol map to know where every arg is stored. // load_args updates the symbol map to know where every arg is stored.
fn load_args<'a>( fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. // 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)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> 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. // Update used and free regs.
for (sym, storage) in &self.symbol_storage_map { for (sym, storage) in &self.symbol_storage_map {
match storage { match storage {
@ -489,6 +495,25 @@ impl<
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]);
Ok(()) 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!( x => Err(format!(
"FnCall: receiving return type, {:?}, is not yet implemented", "FnCall: receiving return type, {:?}, is not yet implemented",
x x
@ -893,6 +918,35 @@ impl<
ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val); ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val);
Ok(()) 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)), x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
} }
} }
@ -1012,6 +1066,19 @@ impl<
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(Builtin::Float64) => {
ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); 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) => { Layout::Struct(field_layouts) => {
let (offset, size) = (*offset, *size); let (offset, size) = (*offset, *size);
// Nothing to do for empty struct // Nothing to do for empty struct
@ -1446,8 +1513,6 @@ macro_rules! single_register_integers {
#[macro_export] #[macro_export]
macro_rules! single_register_floats { 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 Builtin::Float32 | Builtin::Float64
}; };
} }

View file

@ -177,6 +177,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
@ -231,6 +232,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> 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(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
@ -257,7 +281,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
// For most return layouts we will do nothing. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
Layout::Builtin(single_register_builtins!()) => {} Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
x => { x => {
return Err(format!( return Err(format!(
"receiving return type, {:?}, is not yet implemented", "receiving return type, {:?}, is not yet implemented",
@ -373,6 +397,32 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
stack_offset += 8; 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(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
@ -516,6 +566,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
@ -535,9 +586,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => { Layout::Builtin(single_register_integers!()) => {
symbol_map symbol_map
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
i += 1;
} }
Layout::Builtin(single_register_floats!()) => { Layout::Builtin(single_register_floats!()) => {
symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); 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(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
@ -547,7 +607,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
)); ));
} }
} }
i += 1;
} else { } else {
base_offset += match layout { base_offset += match layout {
Layout::Builtin(single_register_builtins!()) => 8, Layout::Builtin(single_register_builtins!()) => 8,
@ -580,7 +639,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<u32, String> { ) -> Result<u32, String> {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut reg_i = 0;
// For most return layouts we will do nothing. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
@ -597,7 +655,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => { Layout::Builtin(single_register_integers!()) => {
if i < Self::GENERAL_PARAM_REGS.len() { if i < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg. // 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 match symbol_map
.get(&args[i]) .get(&args[i])
.ok_or("function argument does not reference any symbol")? .ok_or("function argument does not reference any symbol")?
@ -615,7 +673,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
) )
} }
} }
reg_i += 1;
} else { } else {
// Load the value to the stack. // Load the value to the stack.
match symbol_map match symbol_map
@ -651,7 +708,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_floats!()) => { Layout::Builtin(single_register_floats!()) => {
if i < Self::FLOAT_PARAM_REGS.len() { if i < Self::FLOAT_PARAM_REGS.len() {
// Load the value to the param reg. // 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 match symbol_map
.get(&args[i]) .get(&args[i])
.ok_or("function argument does not reference any symbol")? .ok_or("function argument does not reference any symbol")?
@ -668,7 +725,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
return Err("Cannot load general symbol into FloatReg".to_string()) return Err("Cannot load general symbol into FloatReg".to_string())
} }
} }
reg_i += 1;
} else { } else {
// Load the value to the stack. // Load the value to the stack.
match symbol_map match symbol_map
@ -700,6 +756,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
stack_offset += 8; 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(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(

View file

@ -93,12 +93,8 @@ where
for (layout, sym) in proc.args { for (layout, sym) in proc.args {
self.set_layout_map(*sym, layout)?; self.set_layout_map(*sym, layout)?;
} }
// let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); 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.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize() self.finalize()
} }
@ -119,6 +115,11 @@ where
self.free_symbols(stmt)?; self.free_symbols(stmt)?;
Ok(()) 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 { Stmt::Switch {
cond_symbol, cond_symbol,
cond_layout, cond_layout,
@ -298,6 +299,13 @@ where
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
Symbol::STR_CONCAT => self.build_run_low_level(
sym,
&LowLevel::StrConcat,
arguments,
arg_layouts,
ret_layout,
),
x if x x if x
.module_string(&self.env().interns) .module_string(&self.env().interns)
.starts_with(ModuleName::APP) => .starts_with(ModuleName::APP) =>
@ -470,6 +478,13 @@ where
arg_layouts, arg_layouts,
ret_layout, 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)), x => Err(format!("low level, {:?}. is not yet implemented", x)),
} }
} }

View file

@ -1,12 +1,6 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use] #[macro_use]
extern crate indoc; extern crate indoc;
extern crate bumpalo;
extern crate libc;
#[macro_use] #[macro_use]
mod helpers; mod helpers;

View file

@ -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<RocStr>
// );
// 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<RocStr>
// );
// }
// #[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<RocStr>
// );
// }
// #[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<RocStr>
// );
// }
// #[test]
// fn str_split_empty_strs() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "" ""
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"")]),
// RocList<RocStr>
// );
// }
// #[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<RocStr>
// )
// }
// #[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<RocStr>
// );
// }
// #[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<RocStr>
// );
// }
// #[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<u8>
// );
// 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<u8>
// );
// }
// #[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
// );
// }
}

View file

@ -15,7 +15,7 @@ use crate::llvm::build_list::{
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, 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, str_starts_with, str_starts_with_code_point, str_to_utf8,
}; };
use crate::llvm::compare::{generic_eq, generic_neq}; use crate::llvm::compare::{generic_eq, generic_neq};
@ -708,7 +708,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
env, env,
main_fn_name, main_fn_name,
roc_main_fn, roc_main_fn,
&[], top_level.arguments,
top_level.result, top_level.result,
main_fn_name, main_fn_name,
); );
@ -934,7 +934,9 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
CallType::LowLevel { op, update_mode } => { CallType::LowLevel { op, update_mode } => {
let bytes = update_mode.to_bytes(); let bytes = update_mode.to_bytes();
let update_var = UpdateModeVar(&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( run_low_level(
env, env,
@ -2185,7 +2187,10 @@ fn list_literal<'a, 'ctx, 'env>(
let list_length = elems.len(); let list_length = elems.len();
let list_length_intval = env.ptr_int().const_int(list_length as _, false); 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_type = element_type.into_int_type();
let element_width = elem_layout.stack_size(env.ptr_bytes); let element_width = elem_layout.stack_size(env.ptr_bytes);
let size = list_length * element_width as usize; let size = list_length * element_width as usize;
@ -2226,17 +2231,18 @@ fn list_literal<'a, 'ctx, 'env>(
} }
ListLiteralElement::Symbol(symbol) => { ListLiteralElement::Symbol(symbol) => {
let val = load_symbol(scope, symbol); let val = load_symbol(scope, symbol);
let intval = val.into_int_value();
if intval.is_const() { // here we'd like to furthermore check for intval.is_const().
global_elements.push(intval); // if all elements are const for LLVM, we could make the array a constant.
} else { // BUT morphic does not know about this, and could allow us to modify that
is_all_constant = false; // 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 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>( fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
ident_string: &str, ident_string: &str,
@ -3220,16 +3361,24 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let context = env.context; let context = env.context;
// a generic version that writes the result into a passed *u8 pointer if env.is_gen_test {
if !env.is_gen_test { return expose_function_to_host_help_c_abi_gen_test(
expose_function_to_host_help_c_abi_generic(
env, env,
ident_string,
roc_function, roc_function,
arguments, 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 { let wrapper_return_type = if env.is_gen_test {
context context
.struct_type( .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 cc_return = to_cc_return(env, &return_layout);
let c_function_type = match cc_return { let c_function_type = match cc_return {
CCReturn::Void if !env.is_gen_test => { CCReturn::Void => env.context.void_type().fn_type(&argument_types, false),
env.context.void_type().fn_type(&argument_types, false) CCReturn::Return => return_type.fn_type(&argument_types, false),
} CCReturn::ByPointer => {
CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false),
_ => {
let output_type = return_type.ptr_type(AddressSpace::Generic); let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into()); argument_types.push(output_type.into());
env.context.void_type().fn_type(&argument_types, false) 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(); let args_length = args.len();
match cc_return { match cc_return {
CCReturn::Return if !env.is_gen_test => { CCReturn::Return => {
debug_assert_eq!(args.len(), roc_function.get_params().len()); 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()); debug_assert_eq!(args.len(), roc_function.get_params().len());
} }
_ => { CCReturn::ByPointer => {
args = &args[..args.len() - 1]; args = &args[..args.len() - 1];
debug_assert_eq!(args.len(), roc_function.get_params().len()); 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 arguments_for_call = &arguments_for_call.into_bump_slice();
let call_result = { let call_result = {
if env.is_gen_test { let call_unwrapped =
let roc_wrapper_function = make_exception_catcher(env, roc_function); builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function");
debug_assert_eq!( call_unwrapped.set_call_convention(FAST_CALL_CONV);
arguments_for_call.len(),
roc_wrapper_function.get_params().len()
);
builder.position_at_end(entry); let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
let call_wrapped = builder.build_call( // make_good_roc_result(env, call_unwrapped_result)
roc_wrapper_function, call_unwrapped_result
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
}
}; };
match cc_return { match cc_return {
CCReturn::Void if !env.is_gen_test => { CCReturn::Void => {
// TODO return empty struct here? // TODO return empty struct here?
builder.build_return(None); builder.build_return(None);
} }
CCReturn::Return if !env.is_gen_test => { CCReturn::Return => {
builder.build_return(Some(&call_result)); builder.build_return(Some(&call_result));
} }
_ => { CCReturn::ByPointer => {
let output_arg_index = args_length - 1; let output_arg_index = args_length - 1;
let output_arg = c_function 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 func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs(); let mut it = func_solutions.specs();
let func_spec = it.next().unwrap(); let evaluator = match it.next() {
debug_assert!( Some(func_spec) => {
it.next().is_none(), debug_assert!(
"we expect only one specialization of this symbol" it.next().is_none(),
); "we expect only one specialization of this symbol"
);
let evaluator = function_value_by_func_spec( function_value_by_func_spec(
env, env,
*func_spec, *func_spec,
symbol, symbol,
top_level.arguments, top_level.arguments,
&top_level.result, &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 ident_string = proc.name.as_str(&env.interns);
let fn_name: String = format!("{}_1", ident_string); let fn_name: String = format!("{}_1", ident_string);
@ -4777,7 +4914,7 @@ fn run_low_level<'a, 'ctx, 'env>(
layout: &Layout<'a>, layout: &Layout<'a>,
op: LowLevel, op: LowLevel,
args: &[Symbol], args: &[Symbol],
update_mode: Option<UpdateMode>, update_mode: UpdateMode,
// expect_failed: *const (), // expect_failed: *const (),
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use LowLevel::*; use LowLevel::*;
@ -4833,7 +4970,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); 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 => { StrFromUtf8Range => {
debug_assert_eq!(args.len(), 2); 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()) 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 => { StrSplit => {
// Str.split : Str, Str -> List Str // Str.split : Str, Str -> List Str
debug_assert_eq!(args.len(), 2); 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]); 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 => { ListConcat => {
debug_assert_eq!(args.len(), 2); 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 original_wrapper = load_symbol(scope, &args[0]).into_struct_value();
let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]); 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 => { ListSwap => {
// List.swap : List elem, Nat, Nat -> List elem // 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_1.into_int_value(),
index_2.into_int_value(), index_2.into_int_value(),
element_layout, element_layout,
update_mode,
), ),
_ => unreachable!("Invalid layout {:?} in List.swap", list_layout), _ => unreachable!("Invalid layout {:?} in List.swap", list_layout),
} }
@ -5298,7 +5442,7 @@ fn run_low_level<'a, 'ctx, 'env>(
index.into_int_value(), index.into_int_value(),
element, element,
element_layout, element_layout,
update_mode.unwrap(), update_mode,
), ),
_ => unreachable!("invalid dict layout"), _ => unreachable!("invalid dict layout"),
} }
@ -5615,89 +5759,115 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
let builder = env.builder; let builder = env.builder;
let context = env.context; let context = env.context;
// Here we build two functions: let fastcc_function_name = format!("{}_fastcc_wrapper", foreign.as_str());
//
// - 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 (fastcc_function, arguments) = match env.module.get_function(fastcc_function_name.as_str())
let cc_return = to_cc_return(env, ret_layout); {
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); for symbol in argument_symbols {
let mut fastcc_argument_types = Vec::with_capacity_in(argument_symbols.len(), env.arena); let (value, _) = load_symbol_and_layout(scope, symbol);
let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena);
for symbol in argument_symbols { arguments.push(value);
let (value, layout) = load_symbol_and_layout(scope, symbol); }
cc_argument_types.push(to_cc_type(env, layout)); (function_value, arguments)
}
let basic_type = basic_type_from_layout(env, layout); None => {
fastcc_argument_types.push(basic_type); // Here we build two functions:
//
arguments.push(value); // - 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
let cc_type = match cc_return { // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper`
CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false),
CCReturn::ByPointer => { let return_type = basic_type_from_layout(env, ret_layout);
cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); let cc_return = to_cc_return(env, ret_layout);
env.context.void_type().fn_type(&cc_argument_types, false)
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"); let call = env.builder.build_call(fastcc_function, &arguments, "tmp");
call.set_call_convention(FAST_CALL_CONV); call.set_call_convention(FAST_CALL_CONV);
return call.try_as_basic_value().left().unwrap(); return call.try_as_basic_value().left().unwrap();

View file

@ -17,6 +17,16 @@ use morphic_lib::UpdateMode;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, Layout, LayoutIds}; 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>( fn list_returned_from_zig<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
output: BasicValueEnum<'ctx>, output: BasicValueEnum<'ctx>,
@ -162,6 +172,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
list: BasicValueEnum<'ctx>, list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>, list_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let element_layout = match *list_layout { let element_layout = match *list_layout {
Layout::Builtin(Builtin::EmptyList) => { Layout::Builtin(Builtin::EmptyList) => {
@ -180,6 +191,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
pass_list_cc(env, list), pass_list_cc(env, list),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout), layout_width(env, &element_layout),
pass_update_mode(env, update_mode),
], ],
bitcode::LIST_REVERSE, bitcode::LIST_REVERSE,
) )
@ -228,6 +240,7 @@ pub fn list_append<'a, 'ctx, 'env>(
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
element: BasicValueEnum<'ctx>, element: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>, element_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
call_bitcode_fn_returns_list( call_bitcode_fn_returns_list(
env, env,
@ -236,6 +249,7 @@ pub fn list_append<'a, 'ctx, 'env>(
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), layout_width(env, element_layout),
pass_update_mode(env, update_mode),
], ],
bitcode::LIST_APPEND, bitcode::LIST_APPEND,
) )
@ -267,6 +281,7 @@ pub fn list_swap<'a, 'ctx, 'env>(
index_1: IntValue<'ctx>, index_1: IntValue<'ctx>,
index_2: IntValue<'ctx>, index_2: IntValue<'ctx>,
element_layout: &Layout<'a>, element_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
call_bitcode_fn_returns_list( call_bitcode_fn_returns_list(
env, env,
@ -276,6 +291,7 @@ pub fn list_swap<'a, 'ctx, 'env>(
layout_width(env, element_layout), layout_width(env, element_layout),
index_1.into(), index_1.into(),
index_2.into(), index_2.into(),
pass_update_mode(env, update_mode),
], ],
bitcode::LIST_SWAP, bitcode::LIST_SWAP,
) )

View file

@ -1,9 +1,12 @@
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build::{complex_bitcast, Env, Scope}; 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::builder::Builder;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace; use inkwell::AddressSpace;
use morphic_lib::UpdateMode;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
@ -12,6 +15,18 @@ use super::build::load_symbol;
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8); 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 /// Str.split : Str, Str -> List Str
pub fn str_split<'a, 'ctx, 'env>( pub fn str_split<'a, 'ctx, 'env>(
env: &Env<'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>, env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>, _parent: FunctionValue<'ctx>,
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; let builder = env.builder;
@ -353,6 +369,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>(
env.str_list_c_abi().into(), env.str_list_c_abi().into(),
"to_i128", "to_i128",
), ),
pass_update_mode(env, update_mode),
result_ptr.into(), result_ptr.into(),
], ],
bitcode::STR_FROM_UTF8, bitcode::STR_FROM_UTF8,

View file

@ -3,48 +3,57 @@
## Plan ## Plan
- Initial bringup - Initial bringup
- Get a wasm backend working for some of the number tests. - [x] 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. - [x] Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time.
- Improve the fundamentals - Get the fundamentals working
- [x] Come up with a way to do control flow - [x] Come up with a way to do control flow
- [x] Flesh out the details of value representations between local variables and stack memory - [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 - [x] Set up a way to write tests with any return value rather than just i64 and f64
- [ ] Figure out relocations for linking object files - [x] Implement stack memory
- [ ] Think about the Wasm module builder library we're using, are we happy with it? - [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<Instruction>` rather than a `Vec<u8>`. 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 - Integration
- Move wasm files to `gen_dev/src/wasm` - 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. - 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` 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<u8>`, instead of returning an Object structure - Get `build_module` to write to a file, or maybe return `Vec<u8>`, 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 ## 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. 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 [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 [cfg-api]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api
[binaryen-rs]: https://crates.io/crates/binaryen [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 ## 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. 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 (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 (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] i64.const 1 ; stack=[1]
local.set 0 ; store top of stack to local0 stack=[] local0=1 local.set 0 ; stack=[] local0=1
i64.const 2 ; load constant of type i64 and value 2 stack=[2] local0=1 i64.const 2 ; stack=[2] local0=1
local.set 1 ; store top of stack to local1 stack=[] local0=1 local1=2 local.set 1 ; stack=[] local0=1 local1=2
local.get 0 ; load local0 to top of stack stack=[1] local0=1 local1=2 local.get 0 ; stack=[1] local0=1 local1=2
local.get 1 ; load local1 to top of stack stack=[1,2] local0=1 local1=2 local.get 1 ; 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 call 0 ; stack=[3] local0=1 local1=2
local.set 2 ; store top of stack to local2 stack=[] local0=1 local1=2 local2=3 local.set 2 ; 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 i64.const 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.set 3 ; 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 2 ; 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 local.get 3 ; 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 call 0 ; stack=[7] local0=1 local1=2 local2=3 local3=4
return) ; return the value at the top of the stack 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 $ 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) (func (;0;) (param i64 i64) (result i64)
local.get 0 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 1
i64.const 2 i64.const 2
call 0 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 ## 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. 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. 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. 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? 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 ## Modules vs Instances

View file

@ -1,5 +1,5 @@
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, ModuleBuilder}; use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder};
use parity_wasm::elements::{ use parity_wasm::elements::{
BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, BlockType, Instruction, Instruction::*, Instructions, Local, ValueType,
}; };
@ -8,147 +8,28 @@ use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; 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. // 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) // Follow Emscripten's example by using 1kB (4 bytes would probably do)
const UNUSED_DATA_SECTION_BYTES: u32 = 1024; const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
#[derive(Clone, Copy, Debug)]
struct LocalId(u32);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct LabelId(u32); struct LabelId(u32);
#[derive(Debug)] enum LocalKind {
struct SymbolStorage(LocalId, WasmLayout); Parameter,
Variable,
// 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<Instruction, String> {
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<Instruction, String> {
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
)),
}
}
} }
// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43)
pub struct WasmBackend<'a> { pub struct WasmBackend<'a> {
// Module: Wasm AST // Module: Wasm AST
pub builder: ModuleBuilder, pub builder: ModuleBuilder,
@ -160,12 +41,12 @@ pub struct WasmBackend<'a> {
// Functions: Wasm AST // Functions: Wasm AST
instructions: std::vec::Vec<Instruction>, instructions: std::vec::Vec<Instruction>,
ret_type: ValueType,
arg_types: std::vec::Vec<ValueType>, arg_types: std::vec::Vec<ValueType>,
locals: std::vec::Vec<Local>, locals: std::vec::Vec<Local>,
// Functions: internal state & IR mappings // Functions: internal state & IR mappings
stack_memory: u32, stack_memory: i32,
stack_frame_pointer: Option<LocalId>,
symbol_storage_map: MutMap<Symbol, SymbolStorage>, symbol_storage_map: MutMap<Symbol, SymbolStorage>,
/// how many blocks deep are we (used for jumps) /// how many blocks deep are we (used for jumps)
block_depth: u32, block_depth: u32,
@ -185,12 +66,12 @@ impl<'a> WasmBackend<'a> {
// Functions: Wasm AST // Functions: Wasm AST
instructions: std::vec::Vec::with_capacity(256), instructions: std::vec::Vec::with_capacity(256),
ret_type: ValueType::I32,
arg_types: std::vec::Vec::with_capacity(8), arg_types: std::vec::Vec::with_capacity(8),
locals: std::vec::Vec::with_capacity(32), locals: std::vec::Vec::with_capacity(32),
// Functions: internal state & IR mappings // Functions: internal state & IR mappings
stack_memory: 0, stack_memory: 0,
stack_frame_pointer: None,
symbol_storage_map: MutMap::default(), symbol_storage_map: MutMap::default(),
block_depth: 0, block_depth: 0,
joinpoint_label_map: MutMap::default(), joinpoint_label_map: MutMap::default(),
@ -205,48 +86,18 @@ impl<'a> WasmBackend<'a> {
// Functions: internal state & IR mappings // Functions: internal state & IR mappings
self.stack_memory = 0; self.stack_memory = 0;
self.stack_frame_pointer = None;
self.symbol_storage_map.clear(); 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<u32, String> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
let ret_layout = WasmLayout::new(&proc.ret_layout); let signature_builder = self.start_proc(&proc);
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);
}
self.build_stmt(&proc.body, &proc.ret_layout)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
let signature = builder::signature() let function_def = self.finalize_proc(signature_builder);
.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 location = self.builder.push_function(function_def); let location = self.builder.push_function(function_def);
let function_index = location.body; let function_index = location.body;
self.proc_symbol_map.insert(sym, location); self.proc_symbol_map.insert(sym, location);
@ -255,32 +106,169 @@ impl<'a> WasmBackend<'a> {
Ok(function_index) Ok(function_index)
} }
fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId { fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder {
self.stack_memory += layout.stack_memory(); let ret_layout = WasmLayout::new(&proc.ret_layout);
let index = self.symbol_storage_map.len();
if index >= self.arg_types.len() { let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
self.locals.push(Local::new(1, layout.value_type())); 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); signature_builder.with_params(self.arg_types.clone())
self.symbol_storage_map.insert(symbol, storage);
local_id
} }
fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition {
self.symbol_storage_map.get(sym).ok_or_else(|| { self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any)
format!(
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{:?}", "Symbol {:?} not found in function scope:\n{:?}",
sym, self.symbol_storage_map sym, self.symbol_storage_map
) )
}) })
} }
fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> { fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId {
let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?; let storage = self.get_symbol_storage(sym);
let id: u32 = *local_id; storage.local_id()
self.instructions.push(GetLocal(id)); }
Ok(())
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 /// 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))); 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; self.block_depth += 1;
self.instructions.push(Block(block_type));
// Our blocks always end with a `return` or `br`,
// so they never leave extra values on the stack
self.instructions.push(Block(BlockType::NoResult));
} }
fn end_block(&mut self) { 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> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
match stmt { match stmt {
// This pattern is a simple optimisation to get rid of one local and two instructions per proc. // Simple optimisation: if we are just returning the expression, we don't need a local
// If we are just returning the expression result, then don't SetLocal and immediately GetLocal
Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { 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.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(()) Ok(())
} }
Stmt::Let(sym, expr, layout, following) => { Stmt::Let(sym, expr, layout, following) => {
let wasm_layout = WasmLayout::new(layout); 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.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)?; self.build_stmt(following, ret_layout)?;
Ok(()) Ok(())
} }
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
if let Some(SymbolStorage(local_id, _)) = self.symbol_storage_map.get(sym) { use crate::storage::SymbolStorage::*;
self.instructions.push(GetLocal(local_id.0));
self.instructions.push(Return); let storage = self.symbol_storage_map.get(sym).unwrap();
Ok(())
} else { match storage {
Err(format!( VarStackMemory {
"Not yet implemented: returning values with layout {:?}", local_id,
ret_layout 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 { Stmt::Switch {
@ -351,19 +377,16 @@ impl<'a> WasmBackend<'a> {
// create (number_of_branches - 1) new blocks. // create (number_of_branches - 1) new blocks.
for _ in 0..branches.len() { for _ in 0..branches.len() {
self.start_block() self.start_block(BlockType::NoResult)
} }
// the LocalId of the symbol that we match on // the LocalId of the symbol that we match on
let matched_on = match self.symbol_storage_map.get(cond_symbol) { let matched_on = self.local_id_from_symbol(cond_symbol);
Some(SymbolStorage(local_id, _)) => local_id.0,
None => unreachable!("symbol not defined: {:?}", cond_symbol),
};
// then, we jump whenever the value under scrutiny is equal to the value of a branch // then, we jump whenever the value under scrutiny is equal to the value of a branch
for (i, (value, _, _)) in branches.iter().enumerate() { for (i, (value, _, _)) in branches.iter().enumerate() {
// put the cond_symbol on the top of the stack // 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)); 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()); let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len());
for parameter in parameters.iter() { for parameter in parameters.iter() {
let wasm_layout = WasmLayout::new(&parameter.layout); let wasm_layout = WasmLayout::new(&parameter.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); jp_parameter_local_ids.push(local_id);
} }
self.start_block(); self.start_block(BlockType::NoResult);
self.joinpoint_label_map self.joinpoint_label_map
.insert(*id, (self.block_depth, jp_parameter_local_ids)); .insert(*id, (self.block_depth, jp_parameter_local_ids));
@ -429,12 +454,8 @@ impl<'a> WasmBackend<'a> {
// put the arguments on the stack // put the arguments on the stack
for (symbol, local_id) in arguments.iter().zip(locals.iter()) { for (symbol, local_id) in arguments.iter().zip(locals.iter()) {
let argument = match self.symbol_storage_map.get(symbol) { let argument = self.local_id_from_symbol(symbol);
Some(SymbolStorage(local_id, _)) => local_id.0, self.instructions.push(GetLocal(argument.0));
None => unreachable!("symbol not defined: {:?}", symbol),
};
self.instructions.push(GetLocal(argument));
self.instructions.push(SetLocal(local_id.0)); self.instructions.push(SetLocal(local_id.0));
} }
@ -463,7 +484,7 @@ impl<'a> WasmBackend<'a> {
}) => match call_type { }) => match call_type {
CallType::ByName { name: func_sym, .. } => { CallType::ByName { name: func_sym, .. } => {
for arg in *arguments { 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!( let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!(
"Cannot find function {:?} called from {:?}", "Cannot find function {:?} called from {:?}",
@ -479,45 +500,112 @@ impl<'a> WasmBackend<'a> {
x => Err(format!("the call type, {:?}, is not yet implemented", x)), 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)), x => Err(format!("Expression is not yet implemented {:?}", x)),
} }
} }
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
match lit { let instruction = match lit {
Literal::Bool(x) => { Literal::Bool(x) => I32Const(*x as i32),
self.instructions.push(I32Const(*x as i32)); Literal::Byte(x) => I32Const(*x as i32),
Ok(()) 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)); self.instructions.push(instruction);
Ok(()) 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) => { } else {
let instruction = match layout { // Struct expression but not Struct layout => single element. Copy it.
Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), let field_storage = self.get_symbol_storage(&fields[0]).to_owned();
Layout::Builtin( self.copy_storage(&storage, &field_storage);
Builtin::Int32 }
| Builtin::Int16 Ok(())
| Builtin::Int8 }
| Builtin::Int1
| Builtin::Usize, fn copy_symbol_to_pointer_at_offset(
) => I32Const(*x as i32), &mut self,
x => panic!("loading literal, {:?}, is not yet implemented", x), to_ptr: LocalId,
}; to_offset: u32,
self.instructions.push(instruction); from_symbol: &Symbol,
Ok(()) ) -> u32 {
} let from_storage = self.get_symbol_storage(from_symbol).to_owned();
Literal::Float(x) => { from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset)
let instruction = match layout { }
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()),
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) {
x => panic!("loading literal, {:?}, is not yet implemented", x), let has_stack_memory = to.has_stack_memory();
}; debug_assert!(from.has_stack_memory() == has_stack_memory);
self.instructions.push(instruction);
Ok(()) if !has_stack_memory {
} debug_assert!(from.value_type() == to.value_type());
x => Err(format!("loading literal, {:?}, is not yet implemented", x)), 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>, return_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
for arg in args { for arg in args {
self.load_from_symbol(arg)?; self.load_symbol(arg);
} }
let wasm_layout = WasmLayout::new(return_layout); let wasm_layout = WasmLayout::new(return_layout);
self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; 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, // 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. // so simple arrays of instructions won't work. But there are common patterns.
let instructions: &[Instruction] = match lowlevel { 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 { LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add], ValueType::I32 => &[I32Add],
ValueType::I64 => &[I64Add], ValueType::I64 => &[I64Add],

View file

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

View file

@ -1,9 +1,11 @@
mod backend; mod backend;
pub mod from_wasm32_memory; pub mod from_wasm32_memory;
mod layout;
mod storage;
use bumpalo::Bump; use bumpalo::Bump;
use parity_wasm::builder; 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_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
@ -22,6 +24,10 @@ pub const ALIGN_4: u32 = 2;
pub const ALIGN_8: u32 = 3; pub const ALIGN_8: u32 = 3;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0; 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 struct Env<'a> {
pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot 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)) 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<Instruction>,
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<Instruction>,
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<Instruction>,
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),
]);
}

View file

@ -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<Instruction>,
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
}
}
}
}

View file

@ -1,11 +1,15 @@
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::ModuleBuilder; 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::from_wasm32_memory::FromWasm32Memory;
use roc_gen_wasm::*; use roc_gen_wasm::*;
use roc_std::{RocDec, RocList, RocOrder, RocStr}; use roc_std::{RocDec, RocList, RocOrder, RocStr};
const STACK_POINTER_LOCAL_ID: u32 = 0;
pub trait Wasm32TestResult { pub trait Wasm32TestResult {
fn insert_test_wrapper( fn insert_test_wrapper(
module_builder: &mut ModuleBuilder, module_builder: &mut ModuleBuilder,
@ -16,9 +20,11 @@ pub trait Wasm32TestResult {
let signature = builder::signature().with_result(ValueType::I32).build_sig(); let signature = builder::signature().with_result(ValueType::I32).build_sig();
let stack_frame_pointer = Local::new(1, ValueType::I32);
let function_def = builder::function() let function_def = builder::function()
.with_signature(signature) .with_signature(signature)
.body() .body()
.with_locals(vec![stack_frame_pointer])
.with_instructions(Instructions::new(instructions)) .with_instructions(Instructions::new(instructions))
.build() // body .build() // body
.build(); // function .build(); // function
@ -35,22 +41,15 @@ pub trait Wasm32TestResult {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>; fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>;
} }
fn build_wrapper_body_prelude(stack_memory_size: usize) -> Vec<Instruction> {
vec![
GetGlobal(STACK_POINTER_GLOBAL_ID),
I32Const(stack_memory_size as i32),
I32Sub,
SetGlobal(STACK_POINTER_GLOBAL_ID),
]
}
macro_rules! build_wrapper_body_primitive { macro_rules! build_wrapper_body_primitive {
($store_instruction: expr, $align: expr) => { ($store_instruction: expr, $align: expr) => {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
const MAX_ALIGNED_SIZE: usize = 16; let size: i32 = 8;
let mut instructions = build_wrapper_body_prelude(MAX_ALIGNED_SIZE); let mut instructions = Vec::with_capacity(16);
push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.extend([ 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 the main function with no arguments. Get primitive back.
Call(main_function_index), Call(main_function_index),
@ -59,9 +58,10 @@ macro_rules! build_wrapper_body_primitive {
$store_instruction($align, 0), $store_instruction($align, 0),
// //
// Return the result pointer // Return the result pointer
GetGlobal(STACK_POINTER_GLOBAL_ID), GetLocal(STACK_POINTER_LOCAL_ID),
End,
]); ]);
pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.push(End);
instructions instructions
} }
}; };
@ -76,18 +76,28 @@ macro_rules! wasm_test_result_primitive {
} }
fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> { fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> {
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([ instructions.extend([
// //
// Call the main function with the allocated address to write the result. // 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. // 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), Call(main_function_index),
// //
// Return the result address // Return the result address
GetGlobal(STACK_POINTER_GLOBAL_ID), GetLocal(STACK_POINTER_LOCAL_ID),
End,
]); ]);
pop_stack_frame(
&mut instructions,
size as i32,
LocalId(STACK_POINTER_LOCAL_ID),
);
instructions.push(End);
instructions instructions
} }
@ -163,3 +173,106 @@ where
) )
} }
} }
impl<T, U, V, W> 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<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X> 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<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X, Y> 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<Instruction> {
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<T, U, V, W, X, Y, Z> 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<Instruction> {
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<T, U, V, W, X, Y, Z, A> 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<Instruction> {
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,
)
}
}

View file

@ -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] #[test]
fn i64_record1_literal() { fn i64_record1_literal() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
{ a: 3 } { x: 3 }
"# "#
), ),
3, 3,
@ -402,31 +321,86 @@ mod wasm_records {
); );
} }
// // #[test] #[test]
// // fn i64_record9_literal() { fn i64_record2_literal() {
// // assert_evals_to!( assert_evals_to!(
// // indoc!( indoc!(
// // r#" r#"
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } { x: 3, y: 5 }
// // "# "#
// // ), ),
// // (3, 5, 17, 1, 9, 12, 13, 14, 15), (3, 5),
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64) (i64, i64)
// // ); );
// // } }
// // #[test] #[test]
// // fn f64_record3_literal() { fn i64_record3_literal() {
// // assert_evals_to!( assert_evals_to!(
// // indoc!( indoc!(
// // r#" r#"
// // { x: 3.1, y: 5.1, z: 17.1 } { x: 3, y: 5, z: 17 }
// // "# "#
// // ), ),
// // (3.1, 5.1, 17.1), (3, 5, 17),
// // (f64, f64, f64) (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] #[test]
fn bool_literal() { fn bool_literal() {
@ -667,135 +641,135 @@ mod wasm_records {
// ); // );
// } // }
// #[test] #[test]
// fn return_record_2() { fn return_record_2() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { x: 3, y: 5 } { x: 3, y: 5 }
// "# "#
// ), ),
// [3, 5], [3, 5],
// [i64; 2] [i64; 2]
// ); );
// } }
// #[test] #[test]
// fn return_record_3() { fn return_record_3() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { x: 3, y: 5, z: 4 } { x: 3, y: 5, z: 4 }
// "# "#
// ), ),
// (3, 5, 4), (3, 5, 4),
// (i64, i64, i64) (i64, i64, i64)
// ); );
// } }
// #[test] #[test]
// fn return_record_4() { fn return_record_4() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2 } { a: 3, b: 5, c: 4, d: 2 }
// "# "#
// ), ),
// [3, 5, 4, 2], [3, 5, 4, 2],
// [i64; 4] [i64; 4]
// ); );
// } }
// #[test] #[test]
// fn return_record_5() { fn return_record_5() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1 } { a: 3, b: 5, c: 4, d: 2, e: 1 }
// "# "#
// ), ),
// [3, 5, 4, 2, 1], [3, 5, 4, 2, 1],
// [i64; 5] [i64; 5]
// ); );
// } }
// #[test] #[test]
// fn return_record_6() { fn return_record_6() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
// "# "#
// ), ),
// [3, 5, 4, 2, 1, 7], [3, 5, 4, 2, 1, 7],
// [i64; 6] [i64; 6]
// ); );
// } }
// #[test] #[test]
// fn return_record_7() { fn return_record_7() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
// "# "#
// ), ),
// [3, 5, 4, 2, 1, 7, 8], [3, 5, 4, 2, 1, 7, 8],
// [i64; 7] [i64; 7]
// ); );
// } }
// #[test] #[test]
// fn return_record_float_int() { fn return_record_float_int() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3.14, b: 0x1 } { a: 3.14, b: 0x1 }
// "# "#
// ), ),
// (3.14, 0x1), (3.14, 0x1),
// (f64, i64) (f64, i64)
// ); );
// } }
// #[test] #[test]
// fn return_record_int_float() { fn return_record_int_float() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 0x1, b: 3.14 } { a: 0x1, b: 3.14 }
// "# "#
// ), ),
// (0x1, 3.14), (0x1, 3.14),
// (i64, f64) (i64, f64)
// ); );
// } }
// #[test] #[test]
// fn return_record_float_float() { fn return_record_float_float() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 6.28, b: 3.14 } { a: 6.28, b: 3.14 }
// "# "#
// ), ),
// (6.28, 3.14), (6.28, 3.14),
// (f64, f64) (f64, f64)
// ); );
// } }
// #[test] #[test]
// fn return_record_float_float_float() { fn return_record_float_float_float() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 6.28, b: 3.14, c: 0.1 } { a: 6.28, b: 3.14, c: 0.1 }
// "# "#
// ), ),
// (6.28, 3.14, 0.1), (6.28, 3.14, 0.1),
// (f64, f64, f64) (f64, f64, f64)
// ); );
// } }
// #[test] // #[test]
// fn return_nested_record() { // fn return_nested_record() {
@ -851,20 +825,20 @@ mod wasm_records {
// ); // );
// } // }
#[test] // #[test]
fn update_single_element_record() { // fn update_single_element_record() {
assert_evals_to!( // assert_evals_to!(
indoc!( // indoc!(
r#" // r#"
rec = { foo: 42} // rec = { foo: 42}
{ rec & foo: rec.foo + 1 } // { rec & foo: rec.foo + 1 }
"# // "#
), // ),
43, // 43,
i64 // i64
); // );
} // }
// #[test] // #[test]
// fn booleans_in_record() { // 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] // #[test]
// fn blue_and_present() { // fn blue_and_present() {
// assert_evals_to!( // assert_evals_to!(

View file

@ -37,13 +37,13 @@ use roc_types::subs::{Subs, VarStore, Variable};
use roc_types::types::{Alias, Type}; use roc_types::types::{Alias, Type};
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs;
use std::io; use std::io;
use std::iter; use std::iter;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use std::{env, fs};
/// Default name for the binary generated for an app, if an invalid one was specified. /// Default name for the binary generated for an app, if an invalid one was specified.
const DEFAULT_APP_OUTPUT_PATH: &str = "app"; const DEFAULT_APP_OUTPUT_PATH: &str = "app";
@ -1351,7 +1351,12 @@ where
// doing .max(1) on the entire expression guards against // doing .max(1) on the entire expression guards against
// num_cpus returning 0, while also avoiding wrapping // num_cpus returning 0, while also avoiding wrapping
// unsigned subtraction overflow. // 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::<usize>().unwrap_or(default_num_workers),
Err(_) => default_num_workers,
};
let worker_arenas = arena.alloc(bumpalo::collections::Vec::with_capacity_in( let worker_arenas = arena.alloc(bumpalo::collections::Vec::with_capacity_in(
num_workers, 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!(work.is_empty());
debug_assert!(state.dependencies.solved_all()); debug_assert!(state.dependencies.solved_all());

View file

@ -15,6 +15,7 @@ pub enum LowLevel {
StrFromUtf8, StrFromUtf8,
StrFromUtf8Range, StrFromUtf8Range,
StrToUtf8, StrToUtf8,
StrRepeat,
StrFromFloat, StrFromFloat,
ListLen, ListLen,
ListGetUnsafe, ListGetUnsafe,
@ -114,19 +115,19 @@ impl LowLevel {
match self { match self {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8
| StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe
| ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains | ListSet | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat
| ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe
| DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference
| NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt
| NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf
| NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
| NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan
| NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not | NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast
| Hash | ExpectTrue => false, | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false,
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith

View file

@ -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<IdentId, String> {
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 /// (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 /// names cannot begin with a number, this has no chance of colliding
/// with actual user-defined variables. /// with actual user-defined variables.
@ -927,6 +973,7 @@ define_builtins! {
16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt"
17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
18 STR_FROM_UTF8_RANGE: "fromUtf8Range" 18 STR_FROM_UTF8_RANGE: "fromUtf8Range"
19 STR_REPEAT: "repeat"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias

View file

@ -9,13 +9,16 @@ use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::ir::{Call, CallType, Expr, ListLiteralElement, Literal, ModifyRc, Proc, Stmt}; use crate::ir::{
use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, Proc, Stmt,
};
use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout};
// just using one module for now // just using one module for now
pub const MOD_APP: ModName = ModName(b"UserApp"); 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_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"; const ENTRY_POINT_NAME: &[u8] = b"mainForHost";
@ -128,25 +131,49 @@ where
}; };
m.add_const(STATIC_STR_NAME, static_str_def)?; m.add_const(STATIC_STR_NAME, static_str_def)?;
// the entry point wrapper // a const that models all static lists
let roc_main_bytes = func_name_bytes_help( let static_list_def = {
entry_point.symbol, let mut cbuilder = ConstDefBuilder::new();
entry_point.layout.arguments.iter().copied(), let block = cbuilder.add_block();
entry_point.layout.result, let cell = cbuilder.add_new_heap_cell(block)?;
);
let roc_main = FuncName(&roc_main_bytes);
let entry_point_function = build_entry_point(entry_point.layout, roc_main)?; let unit_type = cbuilder.add_tuple_type(&[])?;
let entry_point_name = FuncName(ENTRY_POINT_NAME); let bag = cbuilder.add_empty_bag(block, unit_type)?;
m.add_func(entry_point_name, entry_point_function)?; 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 type_definitions = MutSet::default();
let mut host_exposed_functions = Vec::new();
// all other functions // all other functions
for proc in procs { for proc in procs {
let bytes = func_name_bytes(proc); let bytes = func_name_bytes(proc);
let func_name = FuncName(&bytes); 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 { if DEBUG {
eprintln!( eprintln!(
"{:?}: {:?} with {:?} args", "{:?}: {:?} with {:?} args",
@ -163,6 +190,19 @@ where
m.add_func(func_name, spec)?; 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 { for union_layout in type_definitions {
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes); let type_name = TypeName(&type_name_bytes);
@ -202,23 +242,83 @@ where
morphic_lib::solve(program) morphic_lib::solve(program)
} }
fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Result<FuncDef> { /// 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<ValueId> {
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<FuncDef> {
let mut builder = FuncDefBuilder::new(); 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 mut cases = Vec::new();
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
let argument = builder.add_unknown_with(block, &[], argument_type)?;
let name_bytes = [0; 16]; {
let spec_var = CalleeSpecVar(&name_bytes); let block = builder.add_block();
let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?;
// 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_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)?; let spec = builder.build(unit_type, unit_type, root)?;
Ok(spec) Ok(spec)
@ -818,6 +918,17 @@ fn lowlevel_spec(
let new_cell = builder.add_new_heap_cell(block)?; let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag]) 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 => { ListAppend => {
let list = env.symbols[&arguments[0]]; let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[1]]; let to_insert = env.symbols[&arguments[1]];
@ -833,6 +944,27 @@ fn lowlevel_spec(
let new_cell = builder.add_new_heap_cell(block)?; let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag]) 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 => { DictEmpty => {
match layout { match layout {
Layout::Builtin(Builtin::EmptyDict) => { Layout::Builtin(Builtin::EmptyDict) => {
@ -1117,9 +1249,11 @@ fn expr_spec<'a>(
let list = new_list(builder, block, type_id)?; let list = new_list(builder, block, type_id)?;
let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let mut all_constants = true;
for element in elems.iter() { for element in elems.iter() {
let value_id = if let ListLiteralElement::Symbol(symbol) = element { let value_id = if let ListLiteralElement::Symbol(symbol) = element {
all_constants = false;
env.symbols[symbol] env.symbols[symbol]
} else { } else {
builder.add_make_tuple(block, &[]).unwrap() builder.add_make_tuple(block, &[]).unwrap()
@ -1128,9 +1262,13 @@ fn expr_spec<'a>(
bag = builder.add_bag_insert(block, bag, value_id)?; 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 => { EmptyArray => {
@ -1296,6 +1434,14 @@ fn str_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
builder.add_tuple_type(&[cell_id]) builder.add_tuple_type(&[cell_id])
} }
fn static_list_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
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 OK_TAG_ID: u8 = 1u8;
// const ERR_TAG_ID: u8 = 0u8; // const ERR_TAG_ID: u8 = 0u8;
@ -1329,6 +1475,12 @@ fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<Val
builder.add_const_ref(block, module, STATIC_STR_NAME) builder.add_const_ref(block, module, STATIC_STR_NAME)
} }
fn new_static_list(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
let module = MOD_APP;
builder.add_const_ref(block, module, STATIC_LIST_NAME)
}
fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> { fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// we model all our numbers as unit values // we model all our numbers as unit values
builder.add_make_tuple(block, &[]) builder.add_make_tuple(block, &[])

View file

@ -1013,6 +1013,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrToUtf8 => arena.alloc_slice_copy(&[owned]), StrToUtf8 => arena.alloc_slice_copy(&[owned]),
StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
DictSize => arena.alloc_slice_copy(&[borrowed]), DictSize => arena.alloc_slice_copy(&[borrowed]),

View file

@ -94,6 +94,12 @@ impl<'a> CapturedSymbols<'a> {
} }
} }
impl<'a> Default for CapturedSymbols<'a> {
fn default() -> Self {
CapturedSymbols::None
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PendingSpecialization<'a> { pub struct PendingSpecialization<'a> {
solved_type: SolvedType, solved_type: SolvedType,
@ -6208,6 +6214,8 @@ fn reuse_function_symbol<'a>(
layout_cache, layout_cache,
); );
// even though this function may not itself capture,
// unification may still cause it to have an extra argument
construct_closure_data( construct_closure_data(
env, env,
lambda_set, lambda_set,
@ -6437,8 +6445,6 @@ fn call_by_name<'a>(
assign_to_symbols(env, procs, layout_cache, iter, result) assign_to_symbols(env, procs, layout_cache, iter, result)
} }
} else { } else {
let argument_layouts = lambda_set.extend_argument_list(env.arena, arg_layouts);
call_by_name_help( call_by_name_help(
env, env,
procs, procs,
@ -6446,7 +6452,7 @@ fn call_by_name<'a>(
proc_name, proc_name,
loc_args, loc_args,
lambda_set, lambda_set,
argument_layouts, arg_layouts,
ret_layout, ret_layout,
layout_cache, layout_cache,
assigned, assigned,
@ -6494,10 +6500,6 @@ fn call_by_name_help<'a>(
let original_fn_var = fn_var; let original_fn_var = fn_var;
let arena = env.arena; 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 // the arguments given to the function, stored in symbols
let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena); let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena);
field_symbols.extend( field_symbols.extend(
@ -6506,7 +6508,13 @@ fn call_by_name_help<'a>(
.map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), .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 // the variables of the given arguments
let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena);
@ -6535,6 +6543,8 @@ fn call_by_name_help<'a>(
proc_name, proc_name,
); );
let field_symbols = field_symbols.into_bump_slice();
let call = self::Call { let call = self::Call {
call_type: CallType::ByName { call_type: CallType::ByName {
name: proc_name, 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 {:?}", "see call_by_name for background (scroll down a bit), function is {:?}",
proc_name, proc_name,
); );
let field_symbols = field_symbols.into_bump_slice();
let call = self::Call { let call = self::Call {
call_type: CallType::ByName { call_type: CallType::ByName {
name: proc_name, name: proc_name,
@ -6625,6 +6638,8 @@ fn call_by_name_help<'a>(
proc_name, proc_name,
); );
let field_symbols = field_symbols.into_bump_slice();
let call = self::Call { let call = self::Call {
call_type: CallType::ByName { call_type: CallType::ByName {
name: proc_name, name: proc_name,
@ -6643,6 +6658,19 @@ fn call_by_name_help<'a>(
None => { None => {
let opt_partial_proc = procs.partial_procs.get(&proc_name); 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 { match opt_partial_proc {
Some(partial_proc) => { Some(partial_proc) => {
// TODO should pending_procs hold a Rc<Proc> to avoid this .clone()? // TODO should pending_procs hold a Rc<Proc> 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) match specialize(env, procs, proc_name, layout_cache, pending, partial_proc)
{ {
Ok((proc, layout)) => call_specialized_proc( Ok((proc, layout)) => {
env, // now we just call our freshly-specialized function
procs, call_specialized_proc(
proc_name, env,
proc, procs,
layout, proc_name,
field_symbols, proc,
loc_args, lambda_set,
layout_cache, layout,
assigned, field_symbols,
hole, loc_args,
), layout_cache,
assigned,
hole,
)
}
Err(SpecializeFailure { Err(SpecializeFailure {
attempted_layout, attempted_layout,
problem: _, problem: _,
@ -6684,6 +6716,7 @@ fn call_by_name_help<'a>(
procs, procs,
proc_name, proc_name,
proc, proc,
lambda_set,
attempted_layout, attempted_layout,
field_symbols, field_symbols,
loc_args, loc_args,
@ -6833,6 +6866,7 @@ fn call_specialized_proc<'a>(
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
proc_name: Symbol, proc_name: Symbol,
proc: Proc<'a>, proc: Proc<'a>,
lambda_set: LambdaSet<'a>,
layout: RawFunctionLayout<'a>, layout: RawFunctionLayout<'a>,
field_symbols: &'a [Symbol], field_symbols: &'a [Symbol],
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>, loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
@ -6871,6 +6905,8 @@ fn call_specialized_proc<'a>(
arguments: field_symbols, 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) build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole)
} }
RawFunctionLayout::ZeroArgumentThunk(_) => { RawFunctionLayout::ZeroArgumentThunk(_) => {
@ -6878,30 +6914,75 @@ fn call_specialized_proc<'a>(
} }
} }
} else { } 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 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)
}
}
} }
} }

View file

@ -599,7 +599,7 @@ impl<'a> LambdaSet<'a> {
// this can happen when there is a type error somewhere // this can happen when there is a type error somewhere
Ok(LambdaSet { Ok(LambdaSet {
set: &[], set: &[],
representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))), representation: arena.alloc(Layout::Struct(&[])),
}) })
} }
_ => panic!("called LambdaSet.from_var on invalid input"), _ => panic!("called LambdaSet.from_var on invalid input"),

View file

@ -32,7 +32,7 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
#[inline(always)] #[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, SyntaxError<'a>> { pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, 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; let min_indent = 0;
skip_second!( skip_second!(
specialize( specialize(

View file

@ -1,6 +1,9 @@
use crate::ast; use crate::ast;
use crate::module::module_defs;
// use crate::module::module_defs; // use crate::module::module_defs;
use crate::parser::Parser;
use crate::parser::{State, SyntaxError}; use crate::parser::{State, SyntaxError};
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::Located; use roc_region::all::Located;
@ -23,3 +26,15 @@ pub fn parse_loc_with<'a>(
Err(fail) => Err(SyntaxError::Expr(fail)), Err(fail) => Err(SyntaxError::Expr(fail)),
} }
} }
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<BumpVec<'a, Located<ast::Def<'a>>>, 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),
}
}

View file

@ -2031,3 +2031,31 @@ fn map_with_index_multi_record() {
RocList<((), ())> 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
);
}

View file

@ -2980,3 +2980,30 @@ fn nested_rigid_tag_union() {
RocStr 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
);
}

View file

@ -949,3 +949,31 @@ fn str_from_utf8_range_count_too_high_for_start() {
RocStr 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);
}

View file

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

View file

@ -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] // #[ignore]
// #[mono_test] // #[mono_test]
// fn static_str_closure() { // fn static_str_closure() {

View file

@ -9,6 +9,8 @@ exclude = ["src/shaders/*.spv"]
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_load = { path = "../compiler/load" }
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" } roc_can = { path = "../compiler/can" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
@ -27,13 +29,13 @@ arraystring = "0.3.0"
libc = "0.2" libc = "0.2"
page_size = "0.4" page_size = "0.4"
winit = "0.24" winit = "0.24"
wgpu = "0.9" wgpu = "0.10"
glyph_brush = "0.7" glyph_brush = "0.7"
log = "0.4" log = "0.4"
zerocopy = "0.3" zerocopy = "0.3"
env_logger = "0.8" env_logger = "0.8"
futures = "0.3" futures = "0.3"
wgpu_glyph = "0.13" wgpu_glyph = "0.14"
cgmath = "0.18.0" cgmath = "0.18.0"
snafu = { version = "0.6", features = ["backtraces"] } snafu = { version = "0.6", features = ["backtraces"] }
colored = "2" colored = "2"
@ -49,6 +51,9 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [
], default-features = false } ], default-features = false }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
nonempty = "0.6.0" nonempty = "0.6.0"
tempfile = "3.2.0"
uuid = { version = "0.8", features = ["v4"] }
fs_extra = "1.2.0"
[dependencies.bytemuck] [dependencies.bytemuck]
version = "1.4" version = "1.4"

View file

@ -1,10 +1,10 @@
use crate::ui::text::lines::Lines; use crate::ui::text::lines::Lines;
use crate::ui::text::selection::Selection; 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;
use crate::ui::util::slice_get_mut; use crate::ui::util::slice_get_mut;
use bumpalo::collections::String as BumpString; use std::cmp::Ordering;
use bumpalo::Bump;
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
@ -14,31 +14,102 @@ pub struct CodeLines {
} }
impl 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( pub fn insert_between_line(
&mut self, &mut self,
line_nr: usize, line_nr: usize,
index: usize, index: usize,
new_str: &str, new_str: &str,
) -> UIResult<()> { ) -> 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(); self.nr_of_chars += new_str.len();
Ok(()) 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<()> { pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?; let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
@ -49,6 +120,18 @@ impl CodeLines {
Ok(()) Ok(())
} }
pub fn del_range_at_line(
&mut self,
line_nr: usize,
col_range: std::ops::Range<usize>,
) -> 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<()> { pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> {
if selection.is_on_same_line() { if selection.is_on_same_line() {
let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?; let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?;
@ -60,17 +143,36 @@ impl CodeLines {
Ok(()) 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 { 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)?; let line_string = slice_get(line_nr, &self.lines)?;
Ok(line_string) Ok(line_string)
} }
fn line_len(&self, line_nr: usize) -> UIResult<usize> { fn line_len(&self, line_nr: usize) -> UIResult<usize> {
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 { fn nr_of_lines(&self) -> usize {
@ -81,14 +183,8 @@ impl Lines for CodeLines {
self.nr_of_chars self.nr_of_chars
} }
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { fn all_lines_as_string(&self) -> String {
let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena); self.lines.join("\n")
for line in &self.lines {
lines.push_str(line);
}
lines
} }
fn is_last_line(&self, line_nr: usize) -> bool { 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<Option<char>> { fn last_char(&self, line_nr: usize) -> UIResult<Option<char>> {
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 { for row in &self.lines {
let row_str = row let row_str = row
.chars() .chars()
.map(|code_char| format!("'{}'", code_char)) .map(|code_char| format!("{}", code_char))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.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(()) Ok(())
} }

View file

@ -5,6 +5,7 @@ use crate::editor::theme::EdTheme;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Config { pub struct Config {
pub code_font_size: f32, pub code_font_size: f32,
pub debug_font_size: f32,
pub ed_theme: EdTheme, pub ed_theme: EdTheme,
} }
@ -12,6 +13,7 @@ impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
code_font_size: 30.0, code_font_size: 30.0,
debug_font_size: 20.0,
ed_theme: EdTheme::default(), ed_theme: EdTheme::default(),
} }
} }

View file

@ -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 crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos};
use colored::*; use colored::*;
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
@ -11,6 +11,24 @@ use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))] #[snafu(visibility(pub))]
pub enum EdError { 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( #[snafu(display(
"CaretNotFound: No carets were found in the expected node with id {}", "CaretNotFound: No carets were found in the expected node with id {}",
node_id node_id
@ -43,6 +61,17 @@ pub enum EdError {
backtrace: Backtrace, 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."))] #[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))]
GetContentOnNestedNode { backtrace: Backtrace }, GetContentOnNestedNode { backtrace: Backtrace },
@ -99,6 +128,12 @@ pub enum EdError {
backtrace: Backtrace, 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."))] #[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 }, NodeWithoutAttributes { backtrace: Backtrace },
@ -131,6 +166,17 @@ pub enum EdError {
backtrace: Backtrace, 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( #[snafu(display(
"UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.", "UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.",
descriptive_vec_name descriptive_vec_name
@ -154,7 +200,10 @@ pub enum EdError {
}, },
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))] #[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."))] #[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))]
RecordWithoutFields { backtrace: Backtrace }, 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 { fn color_backtrace(backtrace: &snafu::Backtrace) -> String {
let backtrace_str = format!("{}", backtrace); let backtrace_str = format!("{}", backtrace);
let backtrace_split = backtrace_str.split('\n'); let backtrace_split = backtrace_str.split('\n');
@ -242,10 +283,3 @@ impl From<UIError> for EdError {
dummy_res.context(UIErrorBacktrace { msg }).unwrap_err() dummy_res.context(UIErrorBacktrace { msg }).unwrap_err()
} }
} }
pub fn from_ui_res<T>(ui_res: UIResult<T>) -> EdResult<T> {
match ui_res {
Ok(t) => Ok(t),
Err(ui_err) => Err(EdError::from(ui_err)),
}
}

View file

@ -1,40 +1,28 @@
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::NestedNodeWithoutChildren; 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::mvc::ed_model::EdModel;
use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::MarkNodeId;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::util::first_last_index_of; use crate::editor::util::first_last_index_of;
use crate::editor::util::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::selection::Selection;
use crate::ui::text::text_pos::TextPos; 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 crate::ui::util::{slice_get, slice_get_mut};
use snafu::OptionExt; use snafu::OptionExt;
use std::cmp::Ordering;
use std::fmt; use std::fmt;
use super::markup::nodes::get_root_mark_node_id;
#[derive(Debug)] #[derive(Debug)]
pub struct GridNodeMap { pub struct GridNodeMap {
pub lines: Vec<Vec<MarkNodeId>>, pub lines: Vec<Vec<MarkNodeId>>,
} }
impl GridNodeMap { 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<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
line_ref.append(&mut new_cols_vec);
Ok(())
}
pub fn insert_between_line( pub fn insert_between_line(
&mut self, &mut self,
line_nr: usize, line_nr: usize,
@ -42,18 +30,105 @@ impl GridNodeMap {
len: usize, len: usize,
node_id: MarkNodeId, node_id: MarkNodeId,
) -> UIResult<()> { ) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?; let nr_of_lines = self.lines.len();
let new_cols_vec: Vec<MarkNodeId> = std::iter::repeat(node_id).take(len).collect();
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<MarkNodeId> = 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(()) 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<MarkNodeId> = 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)?; 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<usize>,
) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.drain(col_range);
Ok(()) Ok(())
} }
@ -64,16 +139,12 @@ impl GridNodeMap {
line_ref.drain(selection.start_pos.column..selection.end_pos.column); line_ref.drain(selection.start_pos.column..selection.end_pos.column);
} else { } else {
// TODO support multiline unimplemented!("TODO support deleting multiline selection")
} }
Ok(()) Ok(())
} }
/*pub fn new_line(&mut self) {
self.lines.push(vec![])
}*/
pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult<MarkNodeId> { pub fn get_id_at_row_col(&self, caret_pos: TextPos) -> UIResult<MarkNodeId> {
let line = slice_get(caret_pos.line, &self.lines)?; let line = slice_get(caret_pos.line, &self.lines)?;
let node_id = slice_get(caret_pos.column, line)?; 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 // returns start and end pos of Expr2/Def2, relevant AST node and MarkNodeId of the corresponding MarkupNode
pub fn get_expr_start_end_pos( pub fn get_block_start_end_pos(
&self, &self,
caret_pos: TextPos, caret_pos: TextPos,
ed_model: &EdModel, ed_model: &EdModel,
) -> EdResult<(TextPos, TextPos, ExprId, MarkNodeId)> { ) -> EdResult<(TextPos, TextPos, ASTNodeId, MarkNodeId)> {
let line = slice_get(caret_pos.line, &self.lines)?; let line = slice_get(caret_pos.line, &self.lines)?;
let node_id = slice_get(caret_pos.column, line)?; 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() { if node.is_nested() {
let (start_pos, end_pos) = self.get_nested_start_end_pos(*node_id, ed_model)?; 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 (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_node_id = slice_get(first_node_index, line)?;
let curr_ast_node_id = ed_model let curr_ast_node_id = ed_model.mark_node_pool.get(*curr_node_id).get_ast_node_id();
.markup_node_pool
.get(*curr_node_id)
.get_ast_node_id();
let mut expr_start_index = first_node_index; let mut expr_start_index = first_node_index;
let mut expr_end_index = last_node_index; let mut expr_end_index = last_node_index;
@ -165,7 +233,7 @@ impl GridNodeMap {
for i in (0..first_node_index).rev() { for i in (0..first_node_index).rev() {
let prev_pos_node_id = slice_get(i, line)?; let prev_pos_node_id = slice_get(i, line)?;
let prev_ast_node_id = ed_model let prev_ast_node_id = ed_model
.markup_node_pool .mark_node_pool
.get(*prev_pos_node_id) .get(*prev_pos_node_id)
.get_ast_node_id(); .get_ast_node_id();
@ -187,7 +255,7 @@ impl GridNodeMap {
for i in last_node_index..line.len() { for i in last_node_index..line.len() {
let next_pos_node_id = slice_get(i, line)?; let next_pos_node_id = slice_get(i, line)?;
let next_ast_node_id = ed_model let next_ast_node_id = ed_model
.markup_node_pool .mark_node_pool
.get(*next_pos_node_id) .get(*next_pos_node_id)
.get_ast_node_id(); .get_ast_node_id();
@ -204,7 +272,7 @@ impl GridNodeMap {
} }
let correct_mark_node_id = 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(( Ok((
TextPos { TextPos {
@ -225,12 +293,12 @@ impl GridNodeMap {
// `{` is not the entire Expr2 // `{` is not the entire Expr2
fn get_top_node_with_expr_id( fn get_top_node_with_expr_id(
curr_node_id: MarkNodeId, curr_node_id: MarkNodeId,
markup_node_pool: &SlowPool, mark_node_pool: &SlowPool,
) -> MarkNodeId { ) -> 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() { 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() { if parent.get_ast_node_id() == curr_node.get_ast_node_id() {
parent_id parent_id
@ -247,30 +315,109 @@ impl GridNodeMap {
nested_node_id: MarkNodeId, nested_node_id: MarkNodeId,
ed_model: &EdModel, ed_model: &EdModel,
) -> EdResult<(TextPos, TextPos)> { ) -> 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 right_most_leaf = self.get_rightmost_leaf(nested_node_id, ed_model)?;
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 expr_start_pos = ed_model let expr_start_pos = ed_model
.grid_node_map .grid_node_map
.get_node_position(*first_child_id, true)?; .get_node_position(left_most_leaf, true)?;
let expr_end_pos = ed_model let expr_end_pos = ed_model
.grid_node_map .grid_node_map
.get_node_position(*last_child_id, false)? .get_node_position(right_most_leaf, false)?
.increment_col(); .increment_col();
Ok((expr_start_pos, expr_end_pos)) Ok((expr_start_pos, expr_end_pos))
} }
fn get_leftmost_leaf(
&self,
nested_node_id: MarkNodeId,
ed_model: &EdModel,
) -> EdResult<MarkNodeId> {
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<MarkNodeId> {
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<MarkNodeId> {
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 { impl fmt::Display for GridNodeMap {
@ -282,10 +429,10 @@ impl fmt::Display for GridNodeMap {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
write!(f, "{}", row_str)?; writeln!(f, "{}", row_str)?;
} }
write!(f, " (grid_node_map)")?; writeln!(f, "(grid_node_map, {:?} lines)", self.lines.len())?;
Ok(()) Ok(())
} }

View file

@ -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)?, F11 => pass_keydown_to_focused(&modifiers, virtual_keycode, app_model)?,

View file

@ -1,9 +1,8 @@
use super::keyboard_input; use super::keyboard_input;
use super::style::CODE_TXT_XY; 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;
use crate::editor::mvc::ed_view::RenderedWgpu; 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::{ use crate::editor::{
config::Config, config::Config,
ed_error::print_err, ed_error::print_err,
@ -20,17 +19,23 @@ use crate::graphics::{
}; };
use crate::lang::expr::Env; use crate::lang::expr::Env;
use crate::lang::pool::Pool; use crate::lang::pool::Pool;
use crate::ui::ui_error::UIError::FileOpenFailed; use crate::ui::text::caret_w_select::CaretPos;
use crate::ui::util::slice_get; use crate::ui::util::path_to_string;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use cgmath::Vector2; use cgmath::Vector2;
use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue};
use pipelines::RectResources; use pipelines::RectResources;
use roc_module::symbol::Interns; use roc_can::builtins::builtin_defs_map;
use roc_module::symbol::{IdentIds, ModuleIds}; use roc_collections::all::MutMap;
use roc_load;
use roc_load::file::LoadedModule;
use roc_module::symbol::IdentIds;
use roc_types::subs::VarStore; 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 std::{error::Error, io, path::Path};
use wgpu::{CommandEncoder, RenderPass, TextureView}; use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush; use wgpu_glyph::GlyphBrush;
use winit::{ use winit::{
dpi::PhysicalSize, dpi::PhysicalSize,
@ -48,26 +53,13 @@ use winit::{
/// The editor is actually launched from the CLI if you pass it zero arguments, /// 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. /// or if you provide it 1 or more files or directories to open on launch.
pub fn launch(filepaths: &[&Path]) -> io::Result<()> { pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> {
//TODO support using multiple filepaths run_event_loop(project_dir_path_opt).expect("Error running event loop");
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");
Ok(()) Ok(())
} }
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> { fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
env_logger::init(); env_logger::init();
// Open window and create a surface // Open window and create a surface
@ -78,7 +70,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
let instance = wgpu::Instance::new(wgpu::BackendBit::all()); let instance = wgpu::Instance::new(wgpu::Backends::all());
let surface = unsafe { instance.create_surface(&window) }; let surface = unsafe { instance.create_surface(&window) };
@ -114,17 +106,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let render_format = wgpu::TextureFormat::Bgra8Unorm; let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size(); let mut size = window.inner_size();
let swap_chain_descr = wgpu::SwapChainDescriptor { let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format, format: render_format,
width: size.width, width: size.width,
height: size.height, height: size.height,
present_mode: wgpu::PresentMode::Mailbox, 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)?; 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<dyn Error>> {
let env_arena = Bump::new(); let env_arena = Bump::new();
let code_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 mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8); let dep_idents = IdentIds::exposed_builtins(8);
let exposed_ident_ids = IdentIds::default(); let exposed_ident_ids = IdentIds::default();
let mut module_ids = ModuleIds::default(); let module_ids = loaded_module.interns.module_ids.clone();
let mod_id = module_ids.get_or_insert(&"ModId123".into());
let interns = Interns {
module_ids,
all_ident_ids: IdentIds::exposed_builtins(8),
};
let env = Env::new( let env = Env::new(
mod_id, loaded_module.module_id,
&env_arena, &env_arena,
&mut env_pool, &mut env_pool,
&mut var_store, &mut var_store,
dep_idents, dep_idents,
&interns.module_ids, &module_ids,
exposed_ident_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_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 { match ed_model_res {
Ok(mut ed_model) => { Ok(mut ed_model) => {
@ -227,10 +205,10 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
} => { } => {
size = new_size; size = new_size;
swap_chain = gpu_device.create_swap_chain( surface.configure(
&surface, &gpu_device,
&wgpu::SwapChainDescriptor { &wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format, format: render_format,
width: size.width, width: size.width,
height: size.height, height: size.height,
@ -251,7 +229,8 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
event: event::WindowEvent::ReceivedCharacter(ch), 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 { if let Err(e) = input_outcome_res {
print_err(&e) print_err(&e)
} else if let Ok(InputOutcome::Ignored) = input_outcome_res { } else if let Ok(InputOutcome::Ignored) = input_outcome_res {
@ -295,11 +274,15 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
label: Some("Redraw"), label: Some("Redraw"),
}); });
let frame = swap_chain let frame = surface
.get_current_frame() .get_current_frame()
.expect("Failed to acquire next SwapChainFrame") .expect("Failed to acquire next SwapChainFrame")
.output; .output;
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
if let Some(ref mut ed_model) = app_model.ed_model_opt { if let Some(ref mut ed_model) = app_model.ed_model_opt {
if rendered_wgpu_opt.is_none() || ed_model.dirty { if rendered_wgpu_opt.is_none() || ed_model.dirty {
let rendered_wgpu_res = let rendered_wgpu_res =
@ -314,23 +297,55 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
} }
if let Some(ref rendered_wgpu) = rendered_wgpu_opt { 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(); let borrowed_text = text_section.to_borrowed();
glyph_brush.queue(borrowed_text); glyph_brush.queue(borrowed_text);
} }
draw_all_rects( // draw first layer of text
&rendered_wgpu.rects, 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, &mut encoder,
&frame.view, &view,
&gpu_device, &gpu_device,
&rect_resources, &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 { } 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( queue_no_file_text(
&size, &size,
@ -341,17 +356,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
); );
} }
// draw all text // draw text
glyph_brush glyph_brush
.draw_queued( .draw_queued(
&gpu_device, &gpu_device,
&mut staging_belt, &mut staging_belt,
&mut encoder, &mut encoder,
&frame.view, &view,
size.width, size.width,
size.height, size.height,
) )
.expect("Draw queued"); .expect("Failed to draw queued text.");
staging_belt.finish(); staging_belt.finish();
cmd_queue.submit(Some(encoder.finish())); cmd_queue.submit(Some(encoder.finish()));
@ -374,17 +389,17 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
fn draw_all_rects( fn draw_rects(
all_rects: &[Rect], all_rects: &[Rect],
encoder: &mut CommandEncoder, encoder: &mut CommandEncoder,
texture_view: &TextureView, texture_view: &TextureView,
gpu_device: &wgpu::Device, gpu_device: &wgpu::Device,
rect_resources: &RectResources, rect_resources: &RectResources,
ed_theme: &EdTheme, load_op: LoadOp<wgpu::Color>,
) { ) {
let rect_buffers = create_rect_buffers(gpu_device, encoder, all_rects); 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_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
@ -399,16 +414,14 @@ fn draw_all_rects(
fn begin_render_pass<'a>( fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder, encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView, texture_view: &'a TextureView,
ed_theme: &EdTheme, load_op: LoadOp<wgpu::Color>,
) -> RenderPass<'a> { ) -> RenderPass<'a> {
let bg_color = to_wgpu_color(ed_theme.background);
encoder.begin_render_pass(&wgpu::RenderPassDescriptor { encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachment { color_attachments: &[wgpu::RenderPassColorAttachment {
view: texture_view, view: texture_view,
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(bg_color), load: load_op,
store: true, 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::<Vec<&String>>()
})
.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( fn queue_no_file_text(
size: &PhysicalSize<u32>, size: &PhysicalSize<u32>,
text: &str, text: &str,

View file

@ -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<MarkNodeId>) -> 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<MarkNodeId>) -> 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<MarkNodeId>) -> 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<MarkNodeId>,
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<MarkNodeId>) -> 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<MarkNodeId>) -> 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<MarkNodeId>) -> 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<MarkNodeId>) -> 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<MarkNodeId>) -> 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,
}
}

View file

@ -1,2 +1,3 @@
pub mod attribute; pub mod attribute;
pub mod common_nodes;
pub mod nodes; pub mod nodes;

View file

@ -1,42 +1,62 @@
use super::attribute::Attributes; use super::attribute::Attributes;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::ExpectedTextNode; use crate::editor::ed_error::ExpectedTextNode;
use crate::editor::ed_error::GetContentOnNestedNode;
use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; 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::MarkNodeId;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::util::index_of; use crate::editor::util::index_of;
use crate::lang::ast::{Expr2, ExprId, RecordField}; use crate::lang::ast::Def2;
use crate::lang::{expr::Env, pool::PoolStr}; 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 crate::ui::util::slice_get;
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::symbol::Interns;
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
pub enum MarkupNode { pub enum MarkupNode {
Nested { Nested {
ast_node_id: ExprId, ast_node_id: ASTNodeId,
children_ids: Vec<MarkNodeId>, children_ids: Vec<MarkNodeId>,
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
newlines_at_end: usize,
}, },
Text { Text {
content: String, content: String,
ast_node_id: ExprId, ast_node_id: ASTNodeId,
syn_high_style: HighlightStyle, syn_high_style: HighlightStyle,
attributes: Attributes, attributes: Attributes,
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
newlines_at_end: usize,
}, },
Blank { Blank {
ast_node_id: ExprId, ast_node_id: ASTNodeId,
attributes: Attributes, attributes: Attributes,
syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
newlines_at_end: usize,
}, },
} }
impl MarkupNode { impl MarkupNode {
pub fn get_ast_node_id(&self) -> ExprId { pub fn get_ast_node_id(&self) -> ASTNodeId {
match self { match self {
MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, MarkupNode::Nested { ast_node_id, .. } => *ast_node_id,
MarkupNode::Text { 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<MarkNodeId> { pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec<MarkNodeId> {
if let Some(parent_id) = self.get_parent_id_opt() { 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() parent_node.get_children_ids()
} else { } else {
@ -74,7 +94,7 @@ impl MarkupNode {
pub fn get_child_indices( pub fn get_child_indices(
&self, &self,
child_id: MarkNodeId, child_id: MarkNodeId,
markup_node_pool: &SlowPool, mark_node_pool: &SlowPool,
) -> EdResult<(usize, usize)> { ) -> EdResult<(usize, usize)> {
match self { match self {
MarkupNode::Nested { children_ids, .. } => { MarkupNode::Nested { children_ids, .. } => {
@ -87,7 +107,7 @@ impl MarkupNode {
mark_child_index_opt = Some(indx); 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 ',', '[', ']' // a node that points to the same ast_node as the parent is a ',', '[', ']'
// those are not "real" ast children // those are not "real" ast children
if child_mark_node.get_ast_node_id() != self_ast_id { 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) -> String {
pub fn get_content(&self) -> EdResult<String> {
match self { match self {
MarkupNode::Nested { .. } => GetContentOnNestedNode {}.fail(), MarkupNode::Nested { .. } => "".to_owned(),
MarkupNode::Text { content, .. } => Ok(content.clone()), MarkupNode::Text { content, .. } => content.clone(),
MarkupNode::Blank { .. } => Ok(BLANK_PLACEHOLDER.to_owned()), 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> { pub fn get_content_mut(&mut self) -> EdResult<&mut String> {
match self { match self {
MarkupNode::Nested { .. } => ExpectedTextNode { MarkupNode::Nested { .. } => ExpectedTextNode {
@ -172,11 +202,10 @@ impl MarkupNode {
} }
} }
pub fn is_all_alphanumeric(&self) -> EdResult<bool> { pub fn is_all_alphanumeric(&self) -> bool {
Ok(self self.get_content()
.get_content()?
.chars() .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<()> { 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 { pub fn is_nested(&self) -> bool {
matches!(self, MarkupNode::Nested { .. }) 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 { 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 COLON: &str = ": ";
pub const COMMA: &str = ", "; pub const COMMA: &str = ", ";
pub const STRING_QUOTES: &str = "\"\""; pub const STRING_QUOTES: &str = "\"\"";
pub const EQUALS: &str = " = ";
fn new_markup_node( fn new_markup_node(
text: String, text: String,
node_id: ExprId, node_id: ASTNodeId,
highlight_style: HighlightStyle, highlight_style: HighlightStyle,
markup_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
) -> MarkNodeId { ) -> MarkNodeId {
let node = MarkupNode::Text { let node = MarkupNode::Text {
content: text, content: text,
@ -236,9 +294,51 @@ fn new_markup_node(
syn_high_style: highlight_style, syn_high_style: highlight_style,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt: None, 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<MarkNodeId> {
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 // make Markup Nodes: generate String representation, assign Highlighting Style
@ -247,55 +347,44 @@ pub fn expr2_to_markup<'a, 'b>(
env: &mut Env<'b>, env: &mut Env<'b>,
expr2: &Expr2, expr2: &Expr2,
expr2_node_id: ExprId, expr2_node_id: ExprId,
markup_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
) -> MarkNodeId { interns: &Interns,
match expr2 { ) -> EdResult<MarkNodeId> {
let ast_node_id = ASTNodeId::AExprId(expr2_node_id);
let mark_node_id = match expr2 {
Expr2::SmallInt { text, .. } Expr2::SmallInt { text, .. }
| Expr2::I128 { text, .. } | Expr2::I128 { text, .. }
| Expr2::U128 { text, .. } | Expr2::U128 { text, .. }
| Expr2::Float { text, .. } => { | Expr2::Float { text, .. } => {
let num_str = get_string(env, text); let num_str = get_string(env, text);
new_markup_node( new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool)
num_str,
expr2_node_id,
HighlightStyle::Number,
markup_node_pool,
)
} }
Expr2::Str(text) => new_markup_node( Expr2::Str(text) => new_markup_node(
"\"".to_owned() + text.as_str(env.pool) + "\"", "\"".to_owned() + text.as_str(env.pool) + "\"",
expr2_node_id, ast_node_id,
HighlightStyle::String, HighlightStyle::String,
markup_node_pool, mark_node_pool,
), ),
Expr2::GlobalTag { name, .. } => new_markup_node( Expr2::GlobalTag { name, .. } => new_markup_node(
get_string(env, name), get_string(env, name),
expr2_node_id, ast_node_id,
HighlightStyle::Type, HighlightStyle::Type,
markup_node_pool, mark_node_pool,
), ),
Expr2::Call { expr: expr_id, .. } => { Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*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) => { Expr2::Var(symbol) => {
//TODO make bump_format with arena //TODO make bump_format with arena
let text = format!("{:?}", symbol); let text = format!("{:?}", symbol);
new_markup_node( new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool)
text,
expr2_node_id,
HighlightStyle::Variable,
markup_node_pool,
)
} }
Expr2::List { elems, .. } => { Expr2::List { elems, .. } => {
let mut children_ids = vec![new_markup_node( let mut children_ids =
LEFT_SQUARE_BR.to_string(), vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))];
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
)];
let indexed_node_ids: Vec<(usize, ExprId)> = let indexed_node_ids: Vec<(usize, ExprId)> =
elems.iter(env.pool).copied().enumerate().collect(); elems.iter(env.pool).copied().enumerate().collect();
@ -308,64 +397,43 @@ pub fn expr2_to_markup<'a, 'b>(
env, env,
sub_expr2, sub_expr2,
*node_id, *node_id,
markup_node_pool, mark_node_pool,
)); interns,
)?);
if idx + 1 < elems.len() { if idx + 1 < elems.len() {
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
", ".to_string(),
expr2_node_id,
HighlightStyle::Operator,
markup_node_pool,
));
} }
} }
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None)));
RIGHT_SQUARE_BR.to_string(),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
let list_node = MarkupNode::Nested { let list_node = MarkupNode::Nested {
ast_node_id: expr2_node_id, ast_node_id,
children_ids, children_ids,
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0,
}; };
markup_node_pool.add(list_node) mark_node_pool.add(list_node)
} }
Expr2::EmptyRecord => { Expr2::EmptyRecord => {
let children_ids = vec![ let children_ids = vec![
new_markup_node( mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)),
LEFT_ACCOLADE.to_string(), mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
),
new_markup_node(
RIGHT_ACCOLADE.to_string(),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
),
]; ];
let record_node = MarkupNode::Nested { let record_node = MarkupNode::Nested {
ast_node_id: expr2_node_id, ast_node_id,
children_ids, children_ids,
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0,
}; };
markup_node_pool.add(record_node) mark_node_pool.add(record_node)
} }
Expr2::Record { fields, .. } => { Expr2::Record { fields, .. } => {
let mut children_ids = vec![new_markup_node( let mut children_ids =
LEFT_ACCOLADE.to_string(), vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))];
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
)];
for (idx, field_node_id) in fields.iter_node_ids().enumerate() { for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
let record_field = env.pool.get(field_node_id); 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( children_ids.push(new_markup_node(
field_name.as_str(env.pool).to_owned(), field_name.as_str(env.pool).to_owned(),
expr2_node_id, ast_node_id,
HighlightStyle::RecordField, HighlightStyle::RecordField,
markup_node_pool, mark_node_pool,
)); ));
match record_field { match record_field {
RecordField::InvalidLabelOnly(_, _) => (), RecordField::InvalidLabelOnly(_, _) => (),
RecordField::LabelOnly(_, _, _) => (), RecordField::LabelOnly(_, _, _) => (),
RecordField::LabeledValue(_, _, sub_expr2_node_id) => { RecordField::LabeledValue(_, _, sub_expr2_node_id) => {
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None)));
COLON.to_string(),
expr2_node_id,
HighlightStyle::Operator,
markup_node_pool,
));
let sub_expr2 = env.pool.get(*sub_expr2_node_id); let sub_expr2 = env.pool.get(*sub_expr2_node_id);
children_ids.push(expr2_to_markup( children_ids.push(expr2_to_markup(
@ -396,66 +459,117 @@ pub fn expr2_to_markup<'a, 'b>(
env, env,
sub_expr2, sub_expr2,
*sub_expr2_node_id, *sub_expr2_node_id,
markup_node_pool, mark_node_pool,
)); interns,
)?);
} }
} }
if idx + 1 < fields.len() { if idx + 1 < fields.len() {
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
", ".to_string(),
expr2_node_id,
HighlightStyle::Operator,
markup_node_pool,
));
} }
} }
children_ids.push(new_markup_node( children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)));
RIGHT_ACCOLADE.to_string(),
expr2_node_id,
HighlightStyle::Bracket,
markup_node_pool,
));
let record_node = MarkupNode::Nested { let record_node = MarkupNode::Nested {
ast_node_id: expr2_node_id, ast_node_id,
children_ids, children_ids,
parent_id_opt: None, 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( Expr2::RuntimeError() => new_markup_node(
"RunTimeError".to_string(), "RunTimeError".to_string(),
expr2_node_id, ast_node_id,
HighlightStyle::Blank, HighlightStyle::Blank,
markup_node_pool, mark_node_pool,
), ),
rest => todo!("implement expr2_to_markup for {:?}", rest), 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) { pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) {
let node = markup_node_pool.get(markup_node_id); let node = mark_node_pool.get(markup_node_id);
if let MarkupNode::Nested { if let MarkupNode::Nested {
ast_node_id: _, ast_node_id: _,
children_ids, children_ids,
parent_id_opt: _, parent_id_opt: _,
newlines_at_end: _,
} = node } = node
{ {
// need to clone because of borrowing issues // need to clone because of borrowing issues
let children_ids_clone = children_ids.clone(); let children_ids_clone = children_ids.clone();
for child_id in 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( pub fn set_parent_for_all_helper(
markup_node_id: MarkNodeId, markup_node_id: MarkNodeId,
parent_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 { match node {
MarkupNode::Nested { MarkupNode::Nested {
@ -479,7 +593,7 @@ pub fn set_parent_for_all_helper(
let children_ids_clone = children_ids.clone(); let children_ids_clone = children_ids.clone();
for child_id in 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), 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<MarkNodeId> = 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<MarkNodeId> = 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<MarkNodeId> {
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<Vec<MarkNodeId>> {
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 { impl fmt::Display for MarkupNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!( write!(
f, f,
"{} ({})", "{} ({}, {})",
self.node_type_as_string(), 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 { 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); 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); tree_as_string_helper(node, 1, &mut full_string, mark_node_pool);
@ -524,11 +852,25 @@ fn tree_as_string_helper(
.to_owned(); .to_owned();
let child = mark_node_pool.get(child_id); 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_string.push_str(&full_str);
tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); 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
}

View file

@ -1,6 +1,6 @@
mod code_lines; mod code_lines;
mod config; mod config;
mod ed_error; pub mod ed_error;
mod grid_node_map; mod grid_node_map;
mod keyboard_input; mod keyboard_input;
pub mod main; pub mod main;

View file

@ -1,8 +1,8 @@
use super::app_model::AppModel; use super::app_model::AppModel;
use super::ed_update; use super::ed_update;
use crate::editor::ed_error::EdResult;
use crate::window::keyboard_input::Modifiers; 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<()> { pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> {
if let Some(ref mut ed_model) = app_model.ed_model_opt { 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 { pub enum InputOutcome {
Accepted, Accepted,
Ignored, Ignored,
SilentIgnored,
} }
pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResult<InputOutcome> { pub fn handle_new_char(
received_char: &char,
app_model: &mut AppModel,
modifiers_winit: ModifiersState,
) -> EdResult<InputOutcome> {
if let Some(ref mut ed_model) = app_model.ed_model_opt { if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus { 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)
} }
/* /*

View file

@ -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<InputOutcome> {
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(())
}

View file

@ -1,27 +1,22 @@
use crate::editor::code_lines::CodeLines; use crate::editor::code_lines::CodeLines;
use crate::editor::grid_node_map::GridNodeMap; 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::slow_pool::{MarkNodeId, SlowPool};
use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::{ use crate::editor::{
ed_error::EdError::ParseError, ed_error::SrcParseError,
ed_error::{EdResult, MissingParent, NoNodeAtCaretPosition}, ed_error::{EdResult, EmptyCodeString, MissingParent, NoNodeAtCaretPosition},
markup::attribute::Attributes,
markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode},
}; };
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::lang::ast::{Expr2, ExprId}; use crate::lang::expr::Env;
use crate::lang::expr::{str_to_expr2, Env}; use crate::lang::parse::{ASTNodeId, AST};
use crate::lang::pool::PoolStr; use crate::lang::pool::PoolStr;
use crate::lang::scope::Scope; use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect};
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::lines::SelectableLines; use crate::ui::text::lines::SelectableLines;
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use roc_module::symbol::Interns; use roc_load::file::LoadedModule;
use roc_region::all::Region;
use std::path::Path; use std::path::Path;
#[derive(Debug)] #[derive(Debug)]
@ -31,83 +26,98 @@ pub struct EdModel<'a> {
pub code_lines: CodeLines, pub code_lines: CodeLines,
// allows us to map window coordinates to MarkNodeId's // allows us to map window coordinates to MarkNodeId's
pub grid_node_map: GridNodeMap, pub grid_node_map: GridNodeMap,
pub markup_root_id: MarkNodeId, pub markup_ids: Vec<MarkNodeId>, // one root node for every expression
pub markup_node_pool: SlowPool, pub mark_node_pool: SlowPool,
// contains single char dimensions, used to calculate line height, column width... // contains single char dimensions, used to calculate line height, column width...
pub glyph_dim_rect_opt: Option<Rect>, pub glyph_dim_rect_opt: Option<Rect>,
pub has_focus: bool, pub has_focus: bool,
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>,
pub selected_expr_opt: Option<SelectedExpression>, pub selected_block_opt: Option<SelectedBlock>,
pub interns: &'a Interns, // this should eventually come from LoadedModule, see #1442 pub loaded_module: LoadedModule,
pub show_debug_view: bool, pub show_debug_view: bool,
// EdModel is dirty if it has changed since the previous render. // EdModel is dirty if it has changed since the previous render.
pub dirty: bool, pub dirty: bool,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SelectedExpression { pub struct SelectedBlock {
pub ast_node_id: ExprId, pub ast_node_id: ASTNodeId,
pub mark_node_id: MarkNodeId, pub mark_node_id: MarkNodeId,
pub type_str: PoolStr, pub type_str: PoolStr,
} }
pub fn init_model<'a>( pub fn init_model<'a>(
code_str: &'a BumpString, code_str: &'a str,
file_path: &'a Path, file_path: &'a Path,
env: Env<'a>, env: Env<'a>,
interns: &'a Interns, loaded_module: LoadedModule,
code_arena: &'a Bump, code_arena: &'a Bump,
caret_pos: CaretPos, // to set caret position
) -> EdResult<EdModel<'a>> { ) -> EdResult<EdModel<'a>> {
let mut module = EdModule::new(code_str, env, code_arena)?; let mut module = EdModule::new(code_str, env, code_arena)?;
let ast_root_id = module.ast_root_id; let mut mark_node_pool = SlowPool::new();
let mut markup_node_pool = SlowPool::new();
let markup_root_id = if code_str.is_empty() { let markup_ids = if code_str.is_empty() {
let blank_root = MarkupNode::Blank { EmptyCodeString {}.fail()
ast_node_id: ast_root_id,
attributes: Attributes::new(),
syn_high_style: HighlightStyle::Blank,
parent_id_opt: None,
};
markup_node_pool.add(blank_root)
} else { } else {
let ast_root = &module.env.pool.get(ast_root_id); ast_to_mark_nodes(
let temp_markup_root_id = expr2_to_markup(
code_arena, code_arena,
&mut module.env, &mut module.env,
ast_root, &module.ast,
ast_root_id, &mut mark_node_pool,
&mut markup_node_pool, &loaded_module.interns,
); )
set_parent_for_all(temp_markup_root_id, &mut markup_node_pool); }?;
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 { Ok(EdModel {
module, module,
file_path, file_path,
code_lines, code_lines,
grid_node_map, grid_node_map,
markup_root_id, markup_ids,
markup_node_pool, mark_node_pool,
glyph_dim_rect_opt: None, glyph_dim_rect_opt: None,
has_focus: true, has_focus: true,
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)), caret_w_select_vec: NonEmpty::new((caret, None)),
selected_expr_opt: None, selected_block_opt: None,
interns, loaded_module,
show_debug_view: false, show_debug_view: false,
dirty: true, dirty: true,
}) })
} }
impl<'a> EdModel<'a> { impl<'a> EdModel<'a> {
pub fn get_carets(&self) -> Vec<TextPos> {
self.caret_w_select_vec
.iter()
.map(|tup| tup.0.caret_pos)
.collect()
}
pub fn get_curr_mark_node_id(&self) -> UIResult<MarkNodeId> { pub fn get_curr_mark_node_id(&self) -> UIResult<MarkNodeId> {
let caret_pos = self.get_caret(); let caret_pos = self.get_caret();
self.grid_node_map.get_id_at_row_col(caret_pos) 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)> { pub fn get_curr_child_indices(&self) -> EdResult<(usize, usize)> {
if self.node_exists_at_caret() { if self.node_exists_at_caret() {
let curr_mark_node_id = self.get_curr_mark_node_id()?; 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() { if let Some(parent_id) = curr_mark_node.get_parent_id_opt() {
let parent = self.markup_node_pool.get(parent_id); let parent = self.mark_node_pool.get(parent_id);
parent.get_child_indices(curr_mark_node_id, &self.markup_node_pool) parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool)
} else { } else {
MissingParent { MissingParent {
node_id: curr_mark_node_id, node_id: curr_mark_node_id,
@ -161,38 +171,26 @@ impl<'a> EdModel<'a> {
#[derive(Debug)] #[derive(Debug)]
pub struct EdModule<'a> { pub struct EdModule<'a> {
pub env: Env<'a>, pub env: Env<'a>,
pub ast_root_id: ExprId, pub ast: AST,
} }
// for debugging // for debugging
// use crate::lang::ast::expr2_to_string; //use crate::lang::ast::expr2_to_string;
impl<'a> EdModule<'a> { impl<'a> EdModule<'a> {
pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult<EdModule<'a>> { pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult<EdModule<'a>> {
if !code_str.is_empty() { 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); match parse_res {
Ok(ast) => Ok(EdModule { env, ast }),
let expr2_result = str_to_expr2(ast_arena, code_str, &mut env, &mut scope, region); Err(err) => SrcParseError {
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 {
syntax_err: format!("{:?}", err), syntax_err: format!("{:?}", err),
}), }
.fail(),
} }
} else { } else {
let ast_root_id = env.pool.add(Expr2::Blank); EmptyCodeString {}.fail()
Ok(EdModule { env, ast_root_id })
} }
} }
} }
@ -200,40 +198,49 @@ impl<'a> EdModule<'a> {
#[cfg(test)] #[cfg(test)]
pub mod test_ed_model { pub mod test_ed_model {
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::main::load_module;
use crate::editor::mvc::ed_model; use crate::editor::mvc::ed_model;
use crate::editor::resources::strings::HELLO_WORLD;
use crate::lang::expr::Env; use crate::lang::expr::Env;
use crate::lang::pool::Pool; 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_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::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::lines::SelectableLines;
use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use ed_model::EdModel; 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 roc_types::subs::VarStore;
use std::fs::File;
use std::io::Write;
use std::path::Path; use std::path::Path;
use std::path::PathBuf;
use tempfile::tempdir;
use uuid::Uuid;
pub fn init_dummy_model<'a>( 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, ed_model_refs: &'a mut EdModelRefs,
code_arena: &'a Bump,
) -> EdResult<EdModel<'a>> { ) -> EdResult<EdModel<'a>> {
let file_path = Path::new(""); let file_path = Path::new("");
let dep_idents = IdentIds::exposed_builtins(8); let dep_idents = IdentIds::exposed_builtins(8);
let exposed_ident_ids = IdentIds::default(); let exposed_ident_ids = IdentIds::default();
let mod_id = ed_model_refs
.interns
.module_ids
.get_or_insert(&"ModId123".into());
let env = Env::new( let env = Env::new(
mod_id, loaded_module.module_id,
&ed_model_refs.env_arena, &ed_model_refs.env_arena,
&mut ed_model_refs.env_pool, &mut ed_model_refs.env_pool,
&mut ed_model_refs.var_store, &mut ed_model_refs.var_store,
dep_idents, dep_idents,
&ed_model_refs.interns.module_ids, module_ids,
exposed_ident_ids, exposed_ident_ids,
); );
@ -241,43 +248,69 @@ pub mod test_ed_model {
code_str, code_str,
file_path, file_path,
env, env,
&ed_model_refs.interns, loaded_module,
&ed_model_refs.code_arena, code_arena,
CaretPos::End,
) )
} }
pub struct EdModelRefs { pub struct EdModelRefs {
code_arena: Bump,
env_arena: Bump, env_arena: Bump,
env_pool: Pool, env_pool: Pool,
var_store: VarStore, var_store: VarStore,
interns: Interns,
} }
pub fn init_model_refs() -> EdModelRefs { pub fn init_model_refs() -> EdModelRefs {
EdModelRefs { EdModelRefs {
code_arena: Bump::new(),
env_arena: Bump::new(), env_arena: Bump::new(),
env_pool: Pool::with_capacity(1024), env_pool: Pool::with_capacity(1024),
var_store: VarStore::default(), var_store: VarStore::default(),
interns: Interns {
module_ids: ModuleIds::default(),
all_ident_ids: IdentIds::exposed_builtins(8),
},
} }
} }
pub fn ed_model_from_dsl<'a>( pub fn ed_model_from_dsl<'a>(
clean_code_str: &'a BumpString, clean_code_str: &'a mut String,
code_lines: &[&str], code_lines: Vec<String>,
ed_model_refs: &'a mut EdModelRefs, ed_model_refs: &'a mut EdModelRefs,
module_ids: &'a ModuleIds,
code_arena: &'a Bump,
) -> Result<EdModel<'a>, String> { ) -> Result<EdModel<'a>, String> {
let code_lines_vec: Vec<String> = (*code_lines).iter().map(|s| s.to_string()).collect(); let full_code = vec![HELLO_WORLD, clean_code_str.as_str()];
let caret_w_select = convert_dsl_to_selection(&code_lines_vec)?; *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) Ok(ed_model)
} }

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
use super::ed_model::EdModel; use super::ed_model::EdModel;
use crate::editor::config::Config; use crate::editor::config::Config;
use crate::editor::ed_error::EdResult; 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_ast::build_code_graphics;
use crate::editor::render_debug::build_debug_graphics; use crate::editor::render_debug::build_debug_graphics;
use crate::editor::resources::strings::START_TIP; use crate::editor::resources::strings::START_TIP;
@ -20,33 +20,49 @@ use winit::dpi::PhysicalSize;
#[derive(Debug)] #[derive(Debug)]
pub struct RenderedWgpu { pub struct RenderedWgpu {
pub text_sections: Vec<glyph_brush::OwnedSection>, pub text_sections_behind: Vec<glyph_brush::OwnedSection>, // displayed in front of rect_behind, behind everything else
pub rects: Vec<Rect>, pub text_sections_front: Vec<glyph_brush::OwnedSection>, // displayed in front of everything
pub rects_behind: Vec<Rect>, // displayed at lowest depth
pub rects_front: Vec<Rect>, // displayed in front of text_sections_behind, behind text_sections_front
} }
impl RenderedWgpu { impl RenderedWgpu {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
text_sections: Vec::new(), text_sections_behind: Vec::new(),
rects: 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) { pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) {
self.text_sections.push(new_text_section); self.text_sections_behind.push(new_text_section);
} }
pub fn add_rect(&mut self, new_rect: Rect) { pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) {
self.rects.push(new_rect); self.text_sections_front.push(new_text_section);
} }
pub fn add_rects(&mut self, new_rects: Vec<Rect>) { pub fn add_rect_behind(&mut self, new_rect: Rect) {
self.rects.extend(new_rects); self.rects_behind.push(new_rect);
}
pub fn add_rects_behind(&mut self, new_rects: Vec<Rect>) {
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) { pub fn extend(&mut self, rendered_wgpu: RenderedWgpu) {
self.text_sections.extend(rendered_wgpu.text_sections); self.text_sections_behind
self.rects.extend(rendered_wgpu.rects); .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 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 { let start_tip_text = owned_section_from_text(&Text {
position: tip_txt_coords.into(), position: tip_txt_coords.into(),
@ -72,15 +91,15 @@ pub fn model_to_wgpu<'a>(
..Default::default() ..Default::default()
}); });
all_rendered.add_text(start_tip_text); all_rendered.add_text_behind(start_tip_text);
let rendered_code_graphics = build_code_graphics( let rendered_code_graphics = build_code_graphics(
ed_model.markup_node_pool.get(ed_model.markup_root_id), &ed_model.markup_ids,
size, size,
txt_coords, txt_coords,
config, config,
glyph_dim_rect, glyph_dim_rect,
&ed_model.markup_node_pool, &ed_model.mark_node_pool,
)?; )?;
all_rendered.extend(rendered_code_graphics); all_rendered.extend(rendered_code_graphics);
@ -93,7 +112,7 @@ pub fn model_to_wgpu<'a>(
let rendered_selection = build_selection_graphics( let rendered_selection = build_selection_graphics(
caret_w_sel_vec, caret_w_sel_vec,
&ed_model.selected_expr_opt, &ed_model.selected_block_opt,
txt_coords, txt_coords,
config, config,
glyph_dim_rect, glyph_dim_rect,
@ -103,7 +122,7 @@ pub fn model_to_wgpu<'a>(
all_rendered.extend(rendered_selection); all_rendered.extend(rendered_selection);
if ed_model.show_debug_view { 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) Ok(all_rendered)
@ -111,7 +130,7 @@ pub fn model_to_wgpu<'a>(
pub fn build_selection_graphics( pub fn build_selection_graphics(
caret_w_select_vec: Vec<CaretWSelect>, caret_w_select_vec: Vec<CaretWSelect>,
selected_expr_opt: &Option<SelectedExpression>, selected_expr_opt: &Option<SelectedBlock>,
txt_coords: Vector2<f32>, txt_coords: Vector2<f32>,
config: &Config, config: &Config,
glyph_dim_rect: Rect, glyph_dim_rect: Rect,
@ -139,7 +158,7 @@ pub fn build_selection_graphics(
let width = let width =
((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_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_x,
sel_rect_y, sel_rect_y,
width, width,
@ -158,12 +177,12 @@ pub fn build_selection_graphics(
let (tip_rect, tip_text) = let (tip_rect, tip_text) =
tooltip.render_tooltip(&glyph_dim_rect, &config.ed_theme.ui_theme); tooltip.render_tooltip(&glyph_dim_rect, &config.ed_theme.ui_theme);
all_rendered.add_rect(tip_rect); all_rendered.add_rect_front(tip_rect);
all_rendered.add_text(tip_text); 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_x,
top_left_y, top_left_y,
&glyph_dim_rect, &glyph_dim_rect,

View file

@ -25,6 +25,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let int_var = ed_model.module.env.var_store.fresh(); let int_var = ed_model.module.env.var_store.fresh();
@ -37,7 +38,11 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
text: PoolStr::new(&digit_string, &mut ed_model.module.env.pool), text: PoolStr::new(&digit_string, &mut ed_model.module.env.pool),
}; };
ed_model.module.env.pool.set(ast_node_id, expr2_node); ed_model
.module
.env
.pool
.set(ast_node_id.to_expr_id()?, expr2_node);
let int_node = MarkupNode::Text { let int_node = MarkupNode::Text {
content: digit_string, content: digit_string,
@ -45,11 +50,12 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
syn_high_style: HighlightStyle::Number, syn_high_style: HighlightStyle::Number,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
if is_blank_node { if is_blank_node {
ed_model ed_model
.markup_node_pool .mark_node_pool
.replace_node(curr_mark_node_id, int_node); .replace_node(curr_mark_node_id, int_node);
// remove data corresponding to Blank node // remove data corresponding to Blank node
@ -59,11 +65,13 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
ed_model.simple_move_carets_right(char_len); ed_model.simple_move_carets_right(char_len);
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
&digit_char.to_string(), &digit_char.to_string(),
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -85,7 +93,7 @@ pub fn update_int(
.grid_node_map .grid_node_map
.get_offset_to_node_id(old_caret_pos, int_mark_node_id)?; .get_offset_to_node_id(old_caret_pos, int_mark_node_id)?;
let int_mark_node = ed_model.markup_node_pool.get_mut(int_mark_node_id); let int_mark_node = ed_model.mark_node_pool.get_mut(int_mark_node_id);
let int_ast_node_id = int_mark_node.get_ast_node_id(); let int_ast_node_id = int_mark_node.get_ast_node_id();
let content_str_mut = int_mark_node.get_content_mut()?; let content_str_mut = int_mark_node.get_content_mut()?;
@ -98,19 +106,25 @@ pub fn update_int(
} else { } else {
content_str_mut.insert(node_caret_offset, *ch); content_str_mut.insert(node_caret_offset, *ch);
let content_str = int_mark_node.get_content()?; let content_str = int_mark_node.get_content();
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
&ch.to_string(), &ch.to_string(),
int_mark_node_id, int_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update ast // update ast
let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool); let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool);
let int_ast_node = ed_model.module.env.pool.get_mut(int_ast_node_id); let int_ast_node = ed_model
.module
.env
.pool
.get_mut(int_ast_node_id.to_expr_id()?);
match int_ast_node { match int_ast_node {
SmallInt { number, text, .. } => { SmallInt { number, text, .. } => {
update_small_int_num(number, &content_str)?; update_small_int_num(number, &content_str)?;

View file

@ -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<InputOutcome> {
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<ValueDef>,
body_id: NodeId<Expr2>,
ed_model: &mut EdModel,
new_char: &char,
) -> EdResult<InputOutcome> {
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)
}
}
*/

View file

@ -1,6 +1,8 @@
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; 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;
use crate::editor::markup::nodes::MarkupNode; use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::app_update::InputOutcome; 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::get_node_context;
use crate::editor::mvc::ed_update::NodeContext; use crate::editor::mvc::ed_update::NodeContext;
use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::MarkNodeId;
use crate::editor::syntax_highlight::HighlightStyle; use crate::lang::ast::ExprId;
use crate::lang::ast::Expr2; use crate::lang::ast::{ast_node_to_string, Expr2};
use crate::lang::ast::{expr2_to_string, ExprId}; use crate::lang::parse::ASTNodeId;
use crate::lang::pool::PoolVec; use crate::lang::pool::PoolVec;
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
@ -24,63 +26,62 @@ pub fn start_new_list(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); 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 { let expr2_node = Expr2::List {
elem_var: ed_model.module.env.var_store.fresh(), elem_var: ed_model.module.env.var_store.fresh(),
elems: PoolVec::empty(ed_model.module.env.pool), 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 { let right_bracket_node_id = ed_model.add_mark_node(new_right_square_mn(
content: nodes::LEFT_SQUARE_BR.to_owned(), ast_node_id.to_expr_id()?,
ast_node_id, Some(curr_mark_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 nested_node = MarkupNode::Nested { let nested_node = MarkupNode::Nested {
ast_node_id, ast_node_id,
children_ids: vec![left_bracket_node_id, right_bracket_node_id], children_ids: vec![left_bracket_node_id, right_bracket_node_id],
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
if is_blank_node { 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_blank_expr_node(old_caret_pos)?;
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?;
ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len()); ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::LEFT_SQUARE_BR, nodes::LEFT_SQUARE_BR,
left_bracket_node_id, 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.line,
old_caret_pos.column + nodes::LEFT_SQUARE_BR.len(), old_caret_pos.column + nodes::LEFT_SQUARE_BR.len(),
nodes::RIGHT_SQUARE_BR, nodes::RIGHT_SQUARE_BR,
right_bracket_node_id, right_bracket_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) 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 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_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 { match list_ast_node {
Expr2::List { Expr2::List {
@ -118,11 +119,11 @@ pub fn add_blank_child(
let blank_elt = Expr2::Blank; let blank_elt = Expr2::Blank;
let blank_elt_id = ed_model.module.env.pool.add(blank_elt); 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 { _ => UnexpectedASTNode {
required_node_type: "List".to_string(), 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(), .fail(),
} }
@ -159,7 +160,7 @@ pub fn add_blank_child(
} }
_ => UnexpectedASTNode { _ => UnexpectedASTNode {
required_node_type: "List".to_string(), 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(), .fail(),
}?; }?;
@ -173,7 +174,7 @@ pub fn add_blank_child(
ed_model, 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() { for (indx, child) in new_mark_children.iter().enumerate() {
parent.add_child_at_index(new_child_index + indx, *child)?; parent.add_child_at_index(new_child_index + indx, *child)?;
@ -191,35 +192,26 @@ pub fn update_mark_children(
parent_id_opt: Option<MarkNodeId>, parent_id_opt: Option<MarkNodeId>,
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<Vec<MarkNodeId>> { ) -> EdResult<Vec<MarkNodeId>> {
let blank_mark_node = MarkupNode::Blank { let blank_mark_node_id = ed_model.add_mark_node(new_blank_mn(
ast_node_id: blank_elt_id, ASTNodeId::AExprId(blank_elt_id),
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(),
parent_id_opt, parent_id_opt,
}; ));
let blank_mark_node_id = ed_model.markup_node_pool.add(blank_mark_node);
let mut children: Vec<MarkNodeId> = vec![]; let mut children: Vec<MarkNodeId> = vec![];
if new_child_index > 1 { if new_child_index > 1 {
let comma_mark_node = MarkupNode::Text { let comma_mark_node_id =
content: nodes::COMMA.to_owned(), ed_model.add_mark_node(new_comma_mn(list_ast_node_id, parent_id_opt));
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);
ed_model.simple_move_carets_right(nodes::COMMA.len()); 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.line,
old_caret_pos.column, old_caret_pos.column,
nodes::COMMA, nodes::COMMA,
comma_mark_node_id, comma_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
children.push(comma_mark_node_id); children.push(comma_mark_node_id);
@ -234,11 +226,13 @@ pub fn update_mark_children(
}; };
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column + comma_shift, old_caret_pos.column + comma_shift,
nodes::BLANK_PLACEHOLDER, nodes::BLANK_PLACEHOLDER,
blank_mark_node_id, blank_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(children) Ok(children)

View file

@ -10,7 +10,7 @@ pub fn update_invalid_lookup(
input_str: &str, input_str: &str,
old_pool_str: &PoolStr, old_pool_str: &PoolStr,
curr_mark_node_id: MarkNodeId, curr_mark_node_id: MarkNodeId,
ast_node_id: ExprId, expr_id: ExprId,
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) { if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) {
@ -32,10 +32,10 @@ pub fn update_invalid_lookup(
.module .module
.env .env
.pool .pool
.set(ast_node_id, Expr2::InvalidLookup(new_pool_str)); .set(expr_id, Expr2::InvalidLookup(new_pool_str));
// update MarkupNode // 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 content_str_mut = curr_mark_node_mut.get_content_mut()?;
content_str_mut.insert_str(caret_offset, input_str); 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()); ed_model.simple_move_carets_right(input_str.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
input_str, input_str,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)

View file

@ -1,10 +1,13 @@
pub mod app_model; pub mod app_model;
pub mod app_update; pub mod app_update;
mod break_line;
pub mod ed_model; pub mod ed_model;
pub mod ed_update; pub mod ed_update;
pub mod ed_view; pub mod ed_view;
mod int_update; mod int_update;
mod let_update;
mod list_update; mod list_update;
mod lookup_update; mod lookup_update;
mod record_update; mod record_update;
mod string_update; mod string_update;
pub mod tld_value_update;

View file

@ -2,6 +2,9 @@ use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::MissingParent;
use crate::editor::ed_error::RecordWithoutFields; use crate::editor::ed_error::RecordWithoutFields;
use crate::editor::markup::attribute::Attributes; 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;
use crate::editor::markup::nodes::MarkupNode; use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::app_update::InputOutcome; 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::syntax_highlight::HighlightStyle;
use crate::editor::util::index_of; use crate::editor::util::index_of;
use crate::lang::ast::{Expr2, ExprId, RecordField}; use crate::lang::ast::{Expr2, ExprId, RecordField};
use crate::lang::parse::ASTNodeId;
use crate::lang::pool::{PoolStr, PoolVec}; use crate::lang::pool::{PoolStr, PoolVec};
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
use snafu::OptionExt; use snafu::OptionExt;
@ -26,61 +30,44 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); 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 ast_pool = &mut ed_model.module.env.pool;
let expr2_node = Expr2::EmptyRecord; 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 { let right_bracket_node_id = ed_model.add_mark_node(new_right_accolade_mn(
content: nodes::LEFT_ACCOLADE.to_owned(), ast_node_id.to_expr_id()?,
ast_node_id, Some(curr_mark_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 nested_node = MarkupNode::Nested { let nested_node = MarkupNode::Nested {
ast_node_id, ast_node_id,
children_ids: vec![left_bracket_node_id, right_bracket_node_id], children_ids: vec![left_bracket_node_id, right_bracket_node_id],
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
if is_blank_node { 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_blank_expr_node(old_caret_pos)?;
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?;
ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len()); ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( ed_model.insert_all_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::LEFT_ACCOLADE, &[left_bracket_node_id, right_bracket_node_id],
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,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -100,7 +87,7 @@ pub fn update_empty_record(
if input_chars.all(|ch| ch.is_ascii_alphabetic()) if input_chars.all(|ch| ch.is_ascii_alphabetic())
&& input_chars.all(|ch| ch.is_ascii_lowercase()) && 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 { let NodeContext {
old_caret_pos, old_caret_pos,
@ -110,8 +97,8 @@ pub fn update_empty_record(
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE if prev_mark_node.get_content() == nodes::LEFT_ACCOLADE
&& curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE && curr_mark_node.get_content() == nodes::RIGHT_ACCOLADE
{ {
// update AST // update AST
let record_var = ed_model.module.env.var_store.fresh(); 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 }; 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 // update Markup
@ -134,12 +125,13 @@ pub fn update_empty_record(
syn_high_style: HighlightStyle::RecordField, syn_high_style: HighlightStyle::RecordField,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt, 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 { 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)?; 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); ed_model.simple_move_carets_right(1);
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, new_input,
record_field_node_id, record_field_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -183,118 +177,114 @@ pub fn update_record_colon(
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
if let Some(parent_id) = parent_id_opt { 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()?; 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 { 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 match prev_mark_node.get_ast_node_id() {
.module ASTNodeId::ADefId(_) => Ok(InputOutcome::Ignored),
.env ASTNodeId::AExprId(prev_expr_id) => {
.pool let prev_expr = ed_model.module.env.pool.get(prev_expr_id);
.get(prev_mark_node.get_ast_node_id());
// current and prev node should always point to record when in valid position to add ':' // current and prev node should always point to record when in valid position to add ':'
if matches!(prev_ast_node, Expr2::Record { .. }) if matches!(prev_expr, Expr2::Record { .. })
&& matches!(curr_ast_node, Expr2::Record { .. }) && matches!(curr_ast_node, Expr2::Record { .. })
{ {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); 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 { match ast_node_ref {
Expr2::Record { Expr2::Record {
record_var: _, record_var: _,
fields, fields,
} => { } => {
if ed_model.node_exists_at_caret() { if ed_model.node_exists_at_caret() {
let next_mark_node_id = let next_mark_node_id =
ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; 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); let next_mark_node =
if next_mark_node.get_content()? == nodes::RIGHT_ACCOLADE { ed_model.mark_node_pool.get(next_mark_node_id);
// update AST node if next_mark_node.get_content() == nodes::RIGHT_ACCOLADE {
let new_field_val = Expr2::Blank; // update AST node
let new_field_val_id = ed_model.module.env.pool.add(new_field_val); 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 let first_field_mut = fields
.iter_mut(ed_model.module.env.pool) .iter_mut(ed_model.module.env.pool)
.next() .next()
.with_context(|| RecordWithoutFields {})?; .with_context(|| RecordWithoutFields {})?;
*first_field_mut = RecordField::LabeledValue( *first_field_mut = RecordField::LabeledValue(
*first_field_mut.get_record_field_pool_str(), *first_field_mut.get_record_field_pool_str(),
*first_field_mut.get_record_field_var(), *first_field_mut.get_record_field_var(),
new_field_val_id, new_field_val_id,
); );
// update Markup // update Markup
let record_colon = nodes::COLON; let record_colon = nodes::COLON;
let record_colon_node = MarkupNode::Text { let record_colon_node = MarkupNode::Text {
content: record_colon.to_owned(), content: record_colon.to_owned(),
ast_node_id: record_ast_node_id, ast_node_id: ASTNodeId::AExprId(record_ast_node_id),
syn_high_style: HighlightStyle::Operator, syn_high_style: HighlightStyle::Operator,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt: Some(parent_id), parent_id_opt: Some(parent_id),
}; newlines_at_end: 0,
};
let record_colon_node_id = let record_colon_node_id =
ed_model.markup_node_pool.add(record_colon_node); ed_model.add_mark_node(record_colon_node);
ed_model ed_model
.markup_node_pool .mark_node_pool
.get_mut(parent_id) .get_mut(parent_id)
.add_child_at_index(new_child_index, record_colon_node_id)?; .add_child_at_index(
new_child_index,
record_colon_node_id,
)?;
let record_blank_node = MarkupNode::Blank { let record_blank_node_id =
ast_node_id: new_field_val_id, ed_model.add_mark_node(new_blank_mn(
syn_high_style: HighlightStyle::Blank, ASTNodeId::AExprId(new_field_val_id),
attributes: Attributes::new(), Some(parent_id),
parent_id_opt: Some(parent_id), ));
};
let record_blank_node_id = ed_model
ed_model.markup_node_pool.add(record_blank_node); .mark_node_pool
ed_model .get_mut(parent_id)
.markup_node_pool .add_child_at_index(
.get_mut(parent_id) new_child_index + 1,
.add_child_at_index( record_blank_node_id,
new_child_index + 1, )?;
record_blank_node_id,
)?;
// update caret // update caret
ed_model.simple_move_carets_right(record_colon.len()); ed_model.simple_move_carets_right(record_colon.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( ed_model.insert_all_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::COLON, &[record_colon_node_id, record_blank_node_id],
record_colon_node_id, )?;
)?;
ed_model.insert_between_line( Ok(InputOutcome::Accepted)
old_caret_pos.line, } else {
old_caret_pos.column + nodes::COLON.len(), Ok(InputOutcome::Ignored)
nodes::BLANK_PLACEHOLDER, }
record_blank_node_id, } else {
)?; Ok(InputOutcome::Ignored)
}
Ok(InputOutcome::Accepted)
} else {
Ok(InputOutcome::Ignored)
} }
} else { _ => Ok(InputOutcome::Ignored),
Ok(InputOutcome::Ignored)
} }
} else {
Ok(InputOutcome::Ignored)
} }
_ => Ok(InputOutcome::Ignored),
} }
} else {
Ok(InputOutcome::Ignored)
} }
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)
@ -315,7 +305,7 @@ pub fn update_record_field(
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
// update MarkupNode // 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 content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
@ -337,11 +327,13 @@ pub fn update_record_field(
ed_model.simple_move_carets_right(new_input.len()); ed_model.simple_move_carets_right(new_input.len());
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, new_input,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update AST Node // update AST Node

View file

@ -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::get_node_context;
use crate::editor::mvc::ed_update::NodeContext; use crate::editor::mvc::ed_update::NodeContext;
use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::syntax_highlight::HighlightStyle;
use crate::lang::ast::update_str_expr;
use crate::lang::ast::ArrString; use crate::lang::ast::ArrString;
use crate::lang::ast::Expr2; use crate::lang::ast::Expr2;
use crate::lang::pool::PoolStr; use crate::lang::pool::PoolStr;
@ -27,7 +28,7 @@ pub fn update_small_string(
let new_input = &new_char.to_string(); let new_input = &new_char.to_string();
// update markup // 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 content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .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 node_caret_offset != 0 && node_caret_offset < content_str_mut.len() {
if old_array_str.len() < ArrString::capacity() { if old_array_str.len() < ArrString::capacity() {
if let Expr2::SmallStr(ref mut mut_array_str) = 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 // safe because we checked the length
unsafe { 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)); 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); content_str_mut.insert_str(node_caret_offset, new_input);
// update GridNodeMap and CodeLines // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, new_input,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update caret // update caret
@ -73,11 +80,7 @@ pub fn update_small_string(
} }
} }
pub fn update_string( pub fn update_string(new_char: char, ed_model: &mut EdModel) -> EdResult<InputOutcome> {
new_input: &str,
old_pool_str: &PoolStr,
ed_model: &mut EdModel,
) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos,
curr_mark_node_id, curr_mark_node_id,
@ -87,31 +90,32 @@ pub fn update_string(
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
// update markup // 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 content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { 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 // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
new_input, &new_char.to_string(),
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
// update ast // update ast
let mut new_string = old_pool_str.as_str(ed_model.module.env.pool).to_owned(); update_str_expr(
new_string.push_str(new_input); ast_node_id.to_expr_id()?,
new_char,
let new_pool_str = PoolStr::new(&new_string, &mut ed_model.module.env.pool); node_caret_offset - 1, // -1 because offset was calculated with quotes
let new_ast_node = Expr2::Str(new_pool_str); &mut ed_model.module.env.pool,
)?;
ed_model.module.env.pool.set(ast_node_id, new_ast_node);
// update caret // update caret
ed_model.simple_move_carets_right(1); ed_model.simple_move_carets_right(1);
@ -133,8 +137,13 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
if curr_mark_node.is_blank() { if curr_mark_node.is_blank() {
let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new()); 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 { let new_string_node = MarkupNode::Text {
content: nodes::STRING_QUOTES.to_owned(), content: nodes::STRING_QUOTES.to_owned(),
@ -142,21 +151,24 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
syn_high_style: HighlightStyle::String, syn_high_style: HighlightStyle::String,
attributes: Attributes::new(), attributes: Attributes::new(),
parent_id_opt, parent_id_opt,
newlines_at_end: curr_mark_node_nls,
}; };
ed_model ed_model
.markup_node_pool .mark_node_pool
.replace_node(curr_mark_node_id, new_string_node); .replace_node(curr_mark_node_id, new_string_node);
// remove data corresponding to Blank 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 // update GridNodeMap and CodeLines
ed_model.insert_between_line( EdModel::insert_between_line(
old_caret_pos.line, old_caret_pos.line,
old_caret_pos.column, old_caret_pos.column,
nodes::STRING_QUOTES, nodes::STRING_QUOTES,
curr_mark_node_id, curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
ed_model.simple_move_carets_right(1); ed_model.simple_move_carets_right(1);

View file

@ -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<Pattern2>,
expr_mark_node_id: MarkNodeId,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
env: &Env<'a>,
interns: &Interns,
) -> EdResult<MarkupNode> {
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<InputOutcome> {
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<InputOutcome> {
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)
}
}

View file

@ -1,4 +1,5 @@
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
use super::slow_pool::MarkNodeId;
use crate::editor::mvc::ed_view::RenderedWgpu; use crate::editor::mvc::ed_view::RenderedWgpu;
use crate::editor::slow_pool::SlowPool; use crate::editor::slow_pool::SlowPool;
use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; 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}; use crate::{editor::config::Config, graphics::colors};
pub fn build_code_graphics<'a>( pub fn build_code_graphics<'a>(
markup_node: &'a MarkupNode, markup_ids: &[MarkNodeId],
size: &PhysicalSize<u32>, size: &PhysicalSize<u32>,
txt_coords: Vector2<f32>, txt_coords: Vector2<f32>,
config: &Config, config: &Config,
glyph_dim_rect: Rect, glyph_dim_rect: Rect,
markup_node_pool: &'a SlowPool, mark_node_pool: &'a SlowPool,
) -> EdResult<RenderedWgpu> { ) -> EdResult<RenderedWgpu> {
let area_bounds = (size.width as f32, size.height as f32); let area_bounds = (size.width as f32, size.height as f32);
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
let mut rendered_wgpu = RenderedWgpu::new(); let mut rendered_wgpu = RenderedWgpu::new();
let (glyph_text_vec, rects) = markup_to_wgpu( let mut all_glyph_text_vec = vec![];
markup_node, let mut all_rects = vec![];
&CodeStyle { let mut txt_row_col = (0, 0);
ed_theme: &config.ed_theme,
font_size: config.code_font_size, for markup_id in markup_ids.iter() {
txt_coords, let mark_node = mark_node_pool.get(*markup_id);
glyph_dim_rect,
}, let (mut glyph_text_vec, mut rects) = markup_to_wgpu(
markup_node_pool, 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( let section = gr_text::owned_section_from_glyph_texts(
glyph_text_vec, all_glyph_text_vec,
txt_coords.into(), txt_coords.into(),
area_bounds, area_bounds,
layout, layout,
); );
rendered_wgpu.add_rects(rects); rendered_wgpu.add_rects_behind(all_rects); // currently only rects for Blank
rendered_wgpu.add_text(section); rendered_wgpu.add_text_behind(section);
Ok(rendered_wgpu) Ok(rendered_wgpu)
} }
@ -55,51 +68,57 @@ struct CodeStyle<'a> {
fn markup_to_wgpu<'a>( fn markup_to_wgpu<'a>(
markup_node: &'a MarkupNode, markup_node: &'a MarkupNode,
code_style: &CodeStyle, code_style: &CodeStyle,
markup_node_pool: &'a SlowPool, txt_row_col: &mut (usize, usize),
mark_node_pool: &'a SlowPool,
) -> EdResult<(Vec<glyph_brush::OwnedText>, Vec<Rect>)> { ) -> EdResult<(Vec<glyph_brush::OwnedText>, Vec<Rect>)> {
let mut wgpu_texts: Vec<glyph_brush::OwnedText> = Vec::new(); let mut wgpu_texts: Vec<glyph_brush::OwnedText> = Vec::new();
let mut rects: Vec<Rect> = Vec::new(); let mut rects: Vec<Rect> = Vec::new();
let mut txt_row_col = (0, 0);
markup_to_wgpu_helper( markup_to_wgpu_helper(
markup_node, markup_node,
&mut wgpu_texts, &mut wgpu_texts,
&mut rects, &mut rects,
code_style, code_style,
&mut txt_row_col, txt_row_col,
markup_node_pool, mark_node_pool,
)?; )?;
Ok((wgpu_texts, rects)) Ok((wgpu_texts, rects))
} }
// TODO use text_row
fn markup_to_wgpu_helper<'a>( fn markup_to_wgpu_helper<'a>(
markup_node: &'a MarkupNode, markup_node: &'a MarkupNode,
wgpu_texts: &mut Vec<glyph_brush::OwnedText>, wgpu_texts: &mut Vec<glyph_brush::OwnedText>,
rects: &mut Vec<Rect>, rects: &mut Vec<Rect>,
code_style: &CodeStyle, code_style: &CodeStyle,
txt_row_col: &mut (usize, usize), txt_row_col: &mut (usize, usize),
markup_node_pool: &'a SlowPool, mark_node_pool: &'a SlowPool,
) -> EdResult<()> { ) -> EdResult<()> {
match markup_node { match markup_node {
MarkupNode::Nested { MarkupNode::Nested {
ast_node_id: _, ast_node_id: _,
children_ids, children_ids,
parent_id_opt: _, parent_id_opt: _,
newlines_at_end,
} => { } => {
for child_id in children_ids.iter() { 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( markup_to_wgpu_helper(
child, child,
wgpu_texts, wgpu_texts,
rects, rects,
code_style, code_style,
txt_row_col, 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 { MarkupNode::Text {
content, content,
@ -107,14 +126,23 @@ fn markup_to_wgpu_helper<'a>(
syn_high_style, syn_high_style,
attributes: _, attributes: _,
parent_id_opt: _, parent_id_opt: _,
newlines_at_end,
} => { } => {
let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; 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_color(colors::to_slice(*highlight_color))
.with_scale(code_style.font_size); .with_scale(code_style.font_size);
txt_row_col.1 += content.len(); 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); wgpu_texts.push(glyph_text);
} }
MarkupNode::Blank { MarkupNode::Blank {
@ -122,8 +150,11 @@ fn markup_to_wgpu_helper<'a>(
attributes: _, attributes: _,
syn_high_style, syn_high_style,
parent_id_opt: _, 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_color(colors::to_slice(colors::WHITE))
.with_scale(code_style.font_size); .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_width = code_style.glyph_dim_rect.width;
let char_height = code_style.glyph_dim_rect.height; let char_height = code_style.glyph_dim_rect.height;
let hole_rect = Rect { let blank_rect = Rect {
top_left_coords: ( top_left_coords: (
code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width, code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width,
code_style.txt_coords.y code_style.txt_coords.y
@ -144,12 +175,21 @@ fn markup_to_wgpu_helper<'a>(
height: char_height, height: char_height,
color: *highlight_color, color: *highlight_color,
}; };
rects.push(hole_rect); rects.push(blank_rect);
txt_row_col.1 += BLANK_PLACEHOLDER.len(); txt_row_col.1 += BLANK_PLACEHOLDER.len();
wgpu_texts.push(glyph_text); wgpu_texts.push(glyph_text);
for _ in 0..*newlines_at_end {
txt_row_col.0 += 1;
txt_row_col.1 = 0;
}
} }
}; };
Ok(()) Ok(())
} }
fn newline(font_size: f32) -> glyph_brush::OwnedText {
glyph_brush::OwnedText::new("\n").with_scale(font_size)
}

View file

@ -4,7 +4,7 @@ use crate::editor::mvc::ed_model::EdModel;
use crate::graphics::colors; use crate::graphics::colors;
use crate::graphics::colors::from_hsb; use crate::graphics::colors::from_hsb;
use crate::graphics::primitives::text as gr_text; 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 cgmath::Vector2;
use winit::dpi::PhysicalSize; 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 area_bounds = (size.width as f32, size.height as f32);
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left); let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
let debug_txt_coords: Vector2<f32> = (txt_coords.x, txt_coords.y * 3.0).into(); let debug_txt_coords: Vector2<f32> = (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)) 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_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)) let code_lines_text = glyph_brush::OwnedText::new(format!("{}", ed_model.code_lines))
.with_color(colors::to_slice(from_hsb(0, 49, 96))) .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( let mut mark_node_trees_string = "\nmark node trees:".to_owned();
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 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_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!( let mut ast_node_text_str = "AST:\n".to_owned();
"\n\n(ast_root)\n{}",
expr2_to_string(ed_model.module.ast_root_id, ed_model.module.env.pool) 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))
.with_color(colors::to_slice(from_hsb(211, 80, 100))) }
.with_scale(config.code_font_size);
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( let section = gr_text::owned_section_from_glyph_texts(
vec![ vec![
carets_text,
grid_node_map_text, grid_node_map_text,
code_lines_text, code_lines_text,
mark_node_tree_text, mark_node_tree_text,

View file

@ -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."; #![allow(dead_code)]
pub const START_TIP: &str =
"Start by typing '[', '{', '\"' or a number.\nInput chars that would create parse errors will be ignored."; 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!"
"#;

View file

@ -63,7 +63,7 @@ impl fmt::Display for SlowPool {
"{}: {} ({}) ast_id {:?} {}", "{}: {} ({}) ast_id {:?} {}",
index, index,
node.node_type_as_string(), node.node_type_as_string(),
node.get_content().unwrap_or_else(|_| "".to_string()), node.get_content(),
ast_node_id.parse::<usize>().unwrap(), ast_node_id.parse::<usize>().unwrap(),
child_str child_str
)?; )?;

View file

@ -1 +1 @@
pub const CODE_TXT_XY: (f32, f32) = (40.0, 130.0); pub const CODE_TXT_XY: (f32, f32) = (40.0, 350.0);

View file

@ -14,6 +14,8 @@ pub enum HighlightStyle {
PackageRelated, // app, packages, imports, exposes, provides... PackageRelated, // app, packages, imports, exposes, provides...
Variable, Variable,
RecordField, RecordField,
Import,
Provides,
Blank, Blank,
} }
@ -31,6 +33,8 @@ pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
(PackageRelated, gr_colors::WHITE), (PackageRelated, gr_colors::WHITE),
(Variable, gr_colors::WHITE), (Variable, gr_colors::WHITE),
(RecordField, from_hsb(258, 50, 90)), (RecordField, from_hsb(258, 50, 90)),
(Import, from_hsb(185, 50, 75)),
(Provides, from_hsb(185, 50, 75)),
(Blank, from_hsb(258, 50, 90)), (Blank, from_hsb(258, 50, 90)),
// comment from_hsb(285, 6, 47) or 186, 35, 40 // comment from_hsb(285, 6, 47) or 186, 35, 40
] ]

View file

@ -105,7 +105,7 @@ pub fn create_rect_buffers(
let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None, label: None,
size: Vertex::SIZE * 4 * nr_of_rects, 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, mapped_at_creation: false,
}); });
@ -114,7 +114,7 @@ pub fn create_rect_buffers(
let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None, label: None,
size: u32_size * 6 * nr_of_rects, 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, mapped_at_creation: false,
}); });
@ -148,7 +148,7 @@ impl StagingBuffer {
StagingBuffer { StagingBuffer {
buffer: device.create_buffer_init(&BufferInitDescriptor { buffer: device.create_buffer_init(&BufferInitDescriptor {
contents: bytemuck::cast_slice(data), contents: bytemuck::cast_slice(data),
usage: wgpu::BufferUsage::COPY_SRC, usage: wgpu::BufferUsages::COPY_SRC,
label: Some("Staging Buffer"), label: Some("Staging Buffer"),
}), }),
size: size_of_slice(data) as wgpu::BufferAddress, size: size_of_slice(data) as wgpu::BufferAddress,

View file

@ -2,7 +2,7 @@ use cgmath::{Matrix4, Ortho};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use wgpu::{ use wgpu::{
BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer,
ShaderStage, ShaderStages,
}; };
// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu // 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 { let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"), label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[new_uniforms]), 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 // 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 { let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"), label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[uniforms]), 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 // bind groups consist of extra resources that are provided to the shaders
let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry { entries: &[BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: ShaderStage::VERTEX, visibility: ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer { ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform, ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false, has_dynamic_offset: false,

View file

@ -9,9 +9,9 @@ pub struct RectResources {
pub fn make_rect_pipeline( pub fn make_rect_pipeline(
gpu_device: &wgpu::Device, gpu_device: &wgpu::Device,
swap_chain_descr: &wgpu::SwapChainDescriptor, surface_config: &wgpu::SurfaceConfiguration,
) -> RectResources { ) -> 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 { let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[&ortho.bind_group_layout], bind_group_layouts: &[&ortho.bind_group_layout],
@ -21,11 +21,10 @@ pub fn make_rect_pipeline(
let pipeline = create_render_pipeline( let pipeline = create_render_pipeline(
gpu_device, gpu_device,
&pipeline_layout, &pipeline_layout,
swap_chain_descr.format, surface_config.format,
&wgpu::ShaderModuleDescriptor { &wgpu::ShaderModuleDescriptor {
label: None, label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))), 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, alpha: wgpu::BlendComponent::REPLACE,
}), }),
write_mask: wgpu::ColorWrite::ALL, write_mask: wgpu::ColorWrites::ALL,
}], }],
}), }),
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),

View file

@ -18,7 +18,7 @@ impl Vertex {
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress; pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: Self::SIZE, array_stride: Self::SIZE,
step_mode: wgpu::InputStepMode::Vertex, step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[ attributes: &[
// position // position
wgpu::VertexAttribute { wgpu::VertexAttribute {

View file

@ -3,6 +3,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use crate::editor::ed_error::{EdResult, UnexpectedASTNode};
use crate::lang::pattern::{Pattern2, PatternId}; use crate::lang::pattern::{Pattern2, PatternId};
use crate::lang::pool::Pool; use crate::lang::pool::Pool;
use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; 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 */), 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<Pattern2>,
expr_id: NodeId<Expr2>,
},
Blank,
}
#[derive(Debug)] #[derive(Debug)]
pub enum ValueDef { pub enum ValueDef {
WithAnnotation { 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<Pattern2> {
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)] #[derive(Debug)]
pub enum FunctionDef { pub enum FunctionDef {
WithAnnotation { WithAnnotation {
@ -402,7 +456,11 @@ pub struct WhenBranch {
// TODO make the inner types private? // TODO make the inner types private?
pub type ExprId = NodeId<Expr2>; pub type ExprId = NodeId<Expr2>;
pub type DefId = NodeId<Def2>;
use RecordField::*; use RecordField::*;
use super::parse::ASTNodeId;
impl RecordField { impl RecordField {
pub fn get_record_field_var(&self) -> &Variable { pub fn get_record_field_var(&self) -> &Variable {
match self { 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 { pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String {
let mut full_string = String::new(); let mut full_string = String::new();
let expr2 = pool.get(node_id); let expr2 = pool.get(node_id);
@ -550,16 +615,118 @@ fn expr2_to_string_helper(
Expr2::SmallInt { text, .. } => { Expr2::SmallInt { text, .. } => {
out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); 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), other => todo!("Implement for {:?}", other),
} }
out_string.push('\n'); 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 { fn var_to_string(some_var: &Variable, indent_level: usize) -> String {
format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) 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<String> {
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] #[test]
fn size_of_expr() { fn size_of_expr() {
assert_eq!(std::mem::size_of::<Expr2>(), crate::lang::pool::NODE_BYTES); assert_eq!(std::mem::size_of::<Expr2>(), crate::lang::pool::NODE_BYTES);

View file

@ -3,10 +3,11 @@
#![allow(unused_imports)] #![allow(unused_imports)]
use bumpalo::{collections::Vec as BumpVec, Bump}; use bumpalo::{collections::Vec as BumpVec, Bump};
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::FromIterator;
use crate::lang::ast::{ use crate::lang::ast::{
expr2_to_string, ClosureExtra, Expr2, ExprId, FloatVal, IntStyle, IntVal, RecordField, expr2_to_string, value_def_to_string, ClosureExtra, Def2, Expr2, ExprId, FloatVal, IntStyle,
WhenBranch, IntVal, RecordField, ValueDef, WhenBranch,
}; };
use crate::lang::def::{ use crate::lang::def::{
canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, 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) (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<Vec<Def2>, 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>( pub fn str_to_expr2<'a>(
arena: &'a Bump, arena: &'a Bump,
input: &'a str, input: &'a str,
@ -296,20 +316,23 @@ pub fn str_to_expr2<'a>(
region: Region, region: Region,
) -> Result<(Expr2, self::Output), SyntaxError<'a>> { ) -> Result<(Expr2, self::Output), SyntaxError<'a>> {
match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) {
Ok(loc_expr) => { Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)),
let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr));
Ok(to_expr2(
env,
scope,
arena.alloc(desugared_loc_expr.value),
region,
))
}
Err(fail) => Err(fail), Err(fail) => Err(fail),
} }
} }
fn loc_expr_to_expr2<'a>(
arena: &'a Bump,
loc_expr: Located<Expr<'a>>,
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>( pub fn to_expr2<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, 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<roc_region::all::Loc<roc_parse::ast::Def<'a>>>,
region: Region,
) -> Vec<Def2> {
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>( fn flatten_str_literal<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, 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::AnnotationOnly { .. } => todo!(),
Def::Value(value_def) => { Def::Value(value_def) => {
let def_id = pool.add(value_def); let def_id = pool.add(value_def);
let body_id = pool.add(ret); let body_id = pool.add(ret);
Expr2::LetValue { Expr2::LetValue {

View file

@ -3,7 +3,8 @@ pub mod constrain;
mod def; mod def;
pub mod expr; pub mod expr;
mod module; mod module;
mod pattern; pub mod parse;
pub mod pattern;
pub mod pool; pub mod pool;
pub mod roc_file; pub mod roc_file;
pub mod scope; pub mod scope;

98
editor/src/lang/parse.rs Normal file
View file

@ -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<DefId>,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ASTNodeId {
ADefId(DefId),
AExprId(ExprId),
}
impl ASTNodeId {
pub fn to_expr_id(&self) -> EdResult<ExprId> {
match self {
ASTNodeId::AExprId(expr_id) => Ok(*expr_id),
_ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?,
}
}
pub fn to_def_id(&self) -> EdResult<DefId> {
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<String>,
pub provides: Vec<String>,
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<AST, SyntaxError<'a>> {
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::<DefId>::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,
}
}
}

View file

@ -1,6 +1,7 @@
#![allow(clippy::all)] #![allow(clippy::all)]
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_imports)] #![allow(unused_imports)]
use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant};
use crate::lang::ast::{ExprId, FloatVal, IntVal}; use crate::lang::ast::{ExprId, FloatVal, IntVal};
use crate::lang::expr::{to_expr_id, Env, Output}; use crate::lang::expr::{to_expr_id, Env, Output};
use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; 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::expr::unescape_char;
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
use roc_collections::all::BumpMap; 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::ast::{StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
@ -482,6 +483,17 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
symbols symbols
} }
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult<String> {
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( pub fn symbols_and_variables_from_pattern(
pool: &Pool, pool: &Pool,
initial: &Pattern2, initial: &Pattern2,

View file

@ -17,6 +17,6 @@ mod window;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
pub fn launch(filepaths: &[&Path]) -> io::Result<()> { pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> {
editor::main::launch(filepaths) editor::main::launch(project_dir_path_opt)
} }

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,12 @@ pub struct CaretWSelect {
pub selection_opt: Option<Selection>, pub selection_opt: Option<Selection>,
} }
pub enum CaretPos {
Start,
Exact(TextPos),
End,
}
fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> { fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<Selection>> {
if start_pos == end_pos { if start_pos == end_pos {
Ok(None) Ok(None)
@ -146,7 +152,7 @@ pub mod test_caret_w_select {
// Retrieve selection and position from formatted string // Retrieve selection and position from formatted string
pub fn convert_dsl_to_selection(lines: &[String]) -> Result<CaretWSelect, String> { pub fn convert_dsl_to_selection(lines: &[String]) -> Result<CaretWSelect, String> {
let lines_str: String = lines.join(""); let lines_str: String = lines.join("\n");
let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str) let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str)
.expect("Selection test DSL parsing failed"); .expect("Selection test DSL parsing failed");

View file

@ -13,14 +13,12 @@ use crate::ui::text::{
use crate::ui::ui_error::UIResult; use crate::ui::ui_error::UIResult;
use crate::ui::util::is_newline; use crate::ui::util::is_newline;
use crate::window::keyboard_input::Modifiers; use crate::window::keyboard_input::Modifiers;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use std::cmp::max; use std::cmp::max;
use std::cmp::min; use std::cmp::min;
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
pub trait Lines { 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<usize>; fn line_len(&self, line_nr: usize) -> UIResult<usize>;
@ -28,7 +26,7 @@ pub trait Lines {
fn nr_of_chars(&self) -> usize; 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; 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 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<()>; fn del_selection(&mut self) -> UIResult<()>;
} }
@ -114,7 +112,7 @@ pub fn move_caret_left<T: Lines>(
} else { } else {
let curr_line_len = lines.line_len(old_line_nr - 1)?; 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 { } else {
(old_line_nr, old_col_nr - 1) (old_line_nr, old_col_nr - 1)
@ -185,7 +183,7 @@ pub fn move_caret_right<T: Lines>(
let is_last_line = lines.is_last_line(old_line_nr); let is_last_line = lines.is_last_line(old_line_nr);
if !is_last_line { 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) (old_line_nr + 1, 0)
} else { } else {
(old_line_nr, old_col_nr + 1) (old_line_nr, old_col_nr + 1)
@ -263,7 +261,9 @@ pub fn move_caret_up<T: Lines>(
let prev_line_len = lines.line_len(old_line_nr - 1)?; let prev_line_len = lines.line_len(old_line_nr - 1)?;
if prev_line_len <= old_col_nr { 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 { } else {
(old_line_nr - 1, old_col_nr) (old_line_nr - 1, old_col_nr)
} }
@ -331,7 +331,9 @@ pub fn move_caret_down<T: Lines>(
if next_line_len <= old_col_nr { if next_line_len <= old_col_nr {
if !is_last_line { 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 { } else {
(old_line_nr + 1, next_line_len) (old_line_nr + 1, next_line_len)
} }
@ -382,7 +384,7 @@ pub fn move_caret_home<T: Lines>(
let curr_line_nr = caret_w_select.caret_pos.line; let curr_line_nr = caret_w_select.caret_pos.line;
let old_col_nr = caret_w_select.caret_pos.column; 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 line_char_iter = curr_line_str.chars();
let mut first_no_space_char_col = 0; let mut first_no_space_char_col = 0;

View file

@ -2,4 +2,5 @@ pub mod big_text_area;
pub mod caret_w_select; pub mod caret_w_select;
pub mod lines; pub mod lines;
pub mod selection; pub mod selection;
mod text_buffer;
pub mod text_pos; pub mod text_pos;

View file

@ -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<String>,
}
impl TextBuffer {
pub fn from_path(path: &Path) -> UIResult<Self> {
let buf_reader = reader_from_path(path)?;
let mut lines: Vec<String> = 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<usize> {
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<String> {
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(())
}
}

View file

@ -1,3 +1,5 @@
use std::io;
use snafu::{Backtrace, Snafu}; use snafu::{Backtrace, Snafu};
//import errors as follows: //import errors as follows:
@ -8,6 +10,16 @@ use snafu::{Backtrace, Snafu};
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))] #[snafu(visibility(pub))]
pub enum UIError { 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( #[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.", "OutOfBounds: index {} was out of bounds for {} with length {}.",
index, index,
@ -34,6 +46,13 @@ pub enum UIError {
))] ))]
FileOpenFailed { path_str: String, err_msg: String }, 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))] #[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 }, TextBufReadFailed { path_str: String, err_msg: String },

View file

@ -1,6 +1,6 @@
use super::ui_error::{OutOfBounds, UIResult}; use super::ui_error::{FileOpenFailed, FileWriteFailed, OutOfBounds, UIResult};
use snafu::OptionExt; use snafu::{OptionExt, ResultExt};
use std::slice::SliceIndex; use std::{fs::File, io::BufReader, path::Path, slice::SliceIndex};
pub fn is_newline(char_ref: &char) -> bool { pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}', '\n']; let newline_codes = vec!['\u{d}', '\n'];
@ -33,3 +33,27 @@ pub fn slice_get_mut<T>(
Ok(elt_ref) Ok(elt_ref)
} }
pub fn reader_from_path(path: &Path) -> UIResult<BufReader<File>> {
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),
})
}

View file

@ -27,6 +27,17 @@ impl Modifiers {
active 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 { pub fn no_mods() -> Modifiers {

5
editor/tests/README.md Normal file
View file

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

View file

@ -1,4 +1,4 @@
text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" )* } text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n" | "{" | "}" | "," | "." | "[" | "]" | ":" | "<" | ">" | "-" | "\"" | "=" )* }
caret = {"┃"} caret = {"┃"}

View file

@ -11,6 +11,12 @@ async function roc_web_platform_run(wasm_filename, callback) {
const importObj = { const importObj = {
wasi_snapshot_preview1: { wasi_snapshot_preview1: {
proc_exit: (code) => {
if (code !== 0) {
console.error(`Exited with code ${code}`);
}
exit_code = code;
},
roc_panic: (_pointer, _tag_id) => { roc_panic: (_pointer, _tag_id) => {
throw 'Roc panicked!'; throw 'Roc panicked!';
} }

Some files were not shown because too many files have changed in this diff Show more