diff --git a/Cargo.lock b/Cargo.lock index e02a05538c..7ca399ed2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,12 @@ # It is not intended for manual editing. [[package]] name = "ab_glyph" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b1f87418ab9d7e1ee2a783aa167767ebeb316d0a9fb10c347aec28a5008acb" +checksum = "7a933731feda8b460bdad9a9e43bb386baba6ec593d2bc19716ef3c75c09085c" dependencies = [ "ab_glyph_rasterizer", - "owned_ttf_parser", + "owned_ttf_parser 0.12.0", ] [[package]] @@ -27,9 +27,15 @@ dependencies = [ [[package]] name = "adler" -version = "0.2.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" [[package]] name = "aho-corasick" @@ -42,24 +48,17 @@ dependencies = [ [[package]] name = "andrew" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e" +checksum = "8c4afb09dd642feec8408e33f92f3ffc4052946f6b20f32fb99c1f58cd4fa7cf" dependencies = [ "bitflags", - "line_drawing", - "rusttype 0.7.9", + "rusttype", "walkdir", "xdg", "xml-rs", ] -[[package]] -name = "android_log-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" - [[package]] name = "ansi_term" version = "0.11.0" @@ -91,7 +90,7 @@ dependencies = [ name = "arena-pool" version = "0.1.0" dependencies = [ - "pretty_assertions", + "pretty_assertions 0.5.1", ] [[package]] @@ -115,7 +114,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c69a8137596e84c22d57f3da1b5de1d4230b1742a710091c85f4d7ce50f00f38" dependencies = [ - "libloading", + "libloading 0.6.7", ] [[package]] @@ -226,9 +225,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.6.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "byte-tools" @@ -238,9 +237,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a4bad0c5981acc24bc09e532f35160f952e35422603f0563cd7a73c2c2e65a0" +checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" dependencies = [ "bytemuck_derive", ] @@ -252,8 +251,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -270,13 +269,12 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "calloop" -version = "0.4.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aa2097be53a00de9e8fc349fea6d76221f398f5c4fa550d420669906962d160" +checksum = "0b036167e76041694579972c28cf4877b4f92da222560ddb49008937b6a6727c" dependencies = [ - "mio", - "mio-extras", - "nix 0.14.1", + "log", + "nix 0.18.0", ] [[package]] @@ -290,9 +288,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" dependencies = [ "jobserver", ] @@ -310,14 +308,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cgmath" -version = "0.17.0" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283944cdecc44bf0b8dd010ec9af888d3b4f142844fdbe026c20ef68148d6fe7" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgmath" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" dependencies = [ - "approx 0.3.2", + "approx 0.4.0", "num-traits", - "rand 0.6.5", ] [[package]] @@ -356,8 +359,8 @@ dependencies = [ "heck", "proc-macro-error", "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -381,14 +384,15 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.20.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" dependencies = [ "bitflags", "block", - "core-foundation 0.7.0", - "core-graphics", + "cocoa-foundation", + "core-foundation 0.9.1", + "core-graphics 0.22.2", "foreign-types", "libc", "objc", @@ -430,12 +434,6 @@ dependencies = [ "serde_yaml", ] -[[package]] -name = "const_fn" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" - [[package]] name = "const_format" version = "0.2.13" @@ -452,7 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df496e1bbc93814d728a8036ff054cd95830afe9cf2275c9326688c02eff936" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", + "quote 1.0.9", "unicode-xid 0.2.1", ] @@ -520,6 +518,19 @@ dependencies = [ "libc", ] +[[package]] +name = "core-graphics" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" +dependencies = [ + "bitflags", + "core-foundation 0.9.1", + "core-graphics-types", + "foreign-types", + "libc", +] + [[package]] name = "core-graphics-types" version = "0.1.1" @@ -540,7 +551,7 @@ checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" dependencies = [ "cfg-if 0.1.10", "core-foundation-sys 0.7.0", - "core-graphics", + "core-graphics 0.19.2", "libc", "objc", ] @@ -621,7 +632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.1", + "crossbeam-utils 0.8.3", ] [[package]] @@ -642,8 +653,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.1", - "crossbeam-utils 0.8.1", + "crossbeam-epoch 0.9.3", + "crossbeam-utils 0.8.3", ] [[package]] @@ -663,13 +674,12 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" dependencies = [ "cfg-if 1.0.0", - "const_fn", - "crossbeam-utils 0.8.1", + "crossbeam-utils 0.8.3", "lazy_static", "memoffset 0.6.1", "scopeguard", @@ -699,9 +709,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ "autocfg 1.0.1", "cfg-if 1.0.0", @@ -730,6 +740,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" +dependencies = [ + "quote 1.0.9", + "syn 1.0.61", +] + [[package]] name = "d3d12" version = "0.3.2" @@ -737,10 +757,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a60cceb22c7c53035f8980524fdc7f17cf49681a3c154e6757d30afbec6ec4" dependencies = [ "bitflags", - "libloading", + "libloading 0.6.7", "winapi 0.3.9", ] +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.24", + "quote 1.0.9", + "strsim", + "syn 1.0.61", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote 1.0.9", + "syn 1.0.61", +] + [[package]] name = "derivative" version = "2.2.0" @@ -748,8 +803,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -816,7 +871,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" dependencies = [ - "libloading", + "libloading 0.6.7", +] + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading 0.7.0", ] [[package]] @@ -833,7 +897,7 @@ dependencies = [ "fs_extra", "handlebars", "maplit", - "pretty_assertions", + "pretty_assertions 0.5.1", "pulldown-cmark", "roc_builtins", "roc_can", @@ -880,9 +944,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" dependencies = [ "atty", "humantime", @@ -976,9 +1040,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" +checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" dependencies = [ "futures-channel", "futures-core", @@ -991,9 +1055,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" +checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" dependencies = [ "futures-core", "futures-sink", @@ -1001,15 +1065,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" +checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" [[package]] name = "futures-executor" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" +checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" dependencies = [ "futures-core", "futures-task", @@ -1018,42 +1082,39 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" +checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" [[package]] name = "futures-macro" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" +checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] name = "futures-sink" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" +checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" [[package]] name = "futures-task" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" -dependencies = [ - "once_cell", -] +checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" [[package]] name = "futures-util" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" +checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" dependencies = [ "futures-channel", "futures-core", @@ -1062,7 +1123,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.6", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -1080,9 +1141,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" dependencies = [ "typenum", ] @@ -1111,9 +1172,9 @@ dependencies = [ [[package]] name = "gfx-auxil" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cd956b592970f08545b9325b87580eb95a51843b6f39da27b8667fec1a1216" +checksum = "e7b33ecf067f2117668d91c9b0f2e5f223ebd1ffec314caa2f3de27bb580186d" dependencies = [ "fxhash", "gfx-hal", @@ -1122,17 +1183,17 @@ dependencies = [ [[package]] name = "gfx-backend-dx11" -version = "0.6.17" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b43f06089866bdffe59b5a6801022c86b74d2c1dd28940a9cf301d3d014fbc" +checksum = "f851d03c2e8f117e3702bf41201a4fafa447d5cb1276d5375870ae7573d069dd" dependencies = [ "arrayvec", "bitflags", "gfx-auxil", "gfx-hal", - "libloading", + "libloading 0.6.7", "log", - "parking_lot 0.11.1", + "parking_lot", "range-alloc", "raw-window-handle", "smallvec", @@ -1144,9 +1205,9 @@ dependencies = [ [[package]] name = "gfx-backend-dx12" -version = "0.6.13" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375014deed24d76b03604736dd899f0925158a1a96db90cbefb9cce070f71af7" +checksum = "36dc6ba2b7647e2c2b27b8f74ff5ccdd53c703776588eee5b1de515fdcbd6bc9" dependencies = [ "arrayvec", "bit-set", @@ -1155,6 +1216,7 @@ dependencies = [ "gfx-auxil", "gfx-hal", "log", + "parking_lot", "range-alloc", "raw-window-handle", "smallvec", @@ -1164,9 +1226,9 @@ dependencies = [ [[package]] name = "gfx-backend-empty" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2085227c12b78f6657a900c829f2d0deb46a9be3eaf86844fde263cdc218f77c" +checksum = "9f07ef26a65954cfdd7b4c587f485100d1bb3b0bd6a51b02d817d6c87cca7a91" dependencies = [ "gfx-hal", "log", @@ -1174,10 +1236,33 @@ dependencies = [ ] [[package]] -name = "gfx-backend-metal" -version = "0.6.5" +name = "gfx-backend-gl" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273d60d5207f96d99e0d11d0718995f67e56533a9df1444d83baf787f4c3cb32" +checksum = "c6717c50ab601efe4a669bfb44db615e3888695ac8263222aeaa702642b9fbc2" +dependencies = [ + "arrayvec", + "bitflags", + "gfx-auxil", + "gfx-hal", + "glow", + "js-sys", + "khronos-egl", + "libloading 0.6.7", + "log", + "naga", + "parking_lot", + "raw-window-handle", + "spirv_cross", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gfx-backend-metal" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc54b456ece69ef49f8893269ebf24ac70969ed34ba2719c3f3abcc8fbff14e" dependencies = [ "arrayvec", "bitflags", @@ -1187,23 +1272,22 @@ dependencies = [ "foreign-types", "gfx-auxil", "gfx-hal", - "lazy_static", "log", "metal", + "naga", "objc", - "parking_lot 0.11.1", + "parking_lot", "range-alloc", "raw-window-handle", - "smallvec", "spirv_cross", "storage-map", ] [[package]] name = "gfx-backend-vulkan" -version = "0.6.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3a63cf61067a09b7d1ac480af3cb2ae0c5ede5bed294607bbd814cb1666c45" +checksum = "dabe88b1a5c91e0f969b441cc57e70364858066e4ba937deeb62065654ef9bd9" dependencies = [ "arrayvec", "ash", @@ -1211,48 +1295,25 @@ dependencies = [ "core-graphics-types", "gfx-hal", "inplace_it", - "lazy_static", "log", + "naga", "objc", + "parking_lot", "raw-window-handle", "smallvec", "winapi 0.3.9", - "x11", -] - -[[package]] -name = "gfx-descriptor" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8c7afcd000f279d541a490e27117e61037537279b9342279abf4938fe60c6b" -dependencies = [ - "arrayvec", - "fxhash", - "gfx-hal", - "log", ] [[package]] name = "gfx-hal" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d0754f5b7a43915fd7466883b2d1bb0800d7cc4609178d0b27bf143b9e5123" +checksum = "c1d9cc8d3b573dda62d0baca4f02e0209786e22c562caff001d77c389008781d" dependencies = [ "bitflags", + "naga", "raw-window-handle", -] - -[[package]] -name = "gfx-memory" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dccdda5d2b39412f4ca2cb15c70b5a82783a86b0606f5e985342754c8ed88f05" -dependencies = [ - "bit-set", - "fxhash", - "gfx-hal", - "log", - "slab", + "thiserror", ] [[package]] @@ -1261,6 +1322,18 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +[[package]] +name = "glow" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072136d2c3783f3a92f131acb227bc806d3886278e2a4dc1e9990ec89ef9e70b" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glyph_brush" version = "0.7.2" @@ -1270,7 +1343,7 @@ dependencies = [ "glyph_brush_draw_cache", "glyph_brush_layout", "log", - "ordered-float 2.1.1", + "ordered-float", "rustc-hash", "twox-hash", ] @@ -1300,6 +1373,47 @@ dependencies = [ "xi-unicode", ] +[[package]] +name = "gpu-alloc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7724b9aef57ea36d70faf54e0ee6265f86e41de16bed8333efdeab5b00e16b" +dependencies = [ + "bitflags", + "gpu-alloc-types", + "tracing", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", + "tracing", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + [[package]] name = "half" version = "1.7.1" @@ -1308,14 +1422,14 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" [[package]] name = "handlebars" -version = "3.5.2" +version = "3.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964d0e99a61fe9b1b347389b77ebf8b7e1587b70293676aaca7d27e59b9073b2" +checksum = "cdb0867bbc5a3da37a753e78021d5fcf8a4db00e18dd2dd90fd36e24190e162d" dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.0", + "quick-error", "serde", "serde_json", ] @@ -1325,6 +1439,9 @@ name = "hashbrown" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] [[package]] name = "heck" @@ -1346,12 +1463,15 @@ dependencies = [ [[package]] name = "humantime" -version = "1.3.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "im" @@ -1362,7 +1482,21 @@ dependencies = [ "bitmaps", "rand_core 0.5.1", "rand_xoshiro", - "sized-chunks", + "sized-chunks 0.5.3", + "typenum", + "version_check", +] + +[[package]] +name = "im" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111c1983f3c5bb72732df25cddacee9b546d08325fb584b5ebd38148be7b0246" +dependencies = [ + "bitmaps", + "rand_core 0.5.1", + "rand_xoshiro", + "sized-chunks 0.6.4", "typenum", "version_check", ] @@ -1376,16 +1510,30 @@ dependencies = [ "bitmaps", "rand_core 0.5.1", "rand_xoshiro", - "sized-chunks", + "sized-chunks 0.5.3", + "typenum", + "version_check", +] + +[[package]] +name = "im-rc" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f" +dependencies = [ + "bitmaps", + "rand_core 0.5.1", + "rand_xoshiro", + "sized-chunks 0.6.4", "typenum", "version_check", ] [[package]] name = "indexmap" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg 1.0.1", "hashbrown", @@ -1401,6 +1549,15 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "indoc" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" +dependencies = [ + "unindent", +] + [[package]] name = "indoc-impl" version = "0.3.6" @@ -1409,8 +1566,8 @@ checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", "unindent", ] @@ -1424,7 +1581,7 @@ dependencies = [ "libc", "llvm-sys", "once_cell", - "parking_lot 0.11.1", + "parking_lot", "regex", ] @@ -1434,8 +1591,8 @@ version = "0.2.0" source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release3#57e9f00d98fc99486e737c314e56a59498c5dbbb" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -1509,9 +1666,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.47" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65" +checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" dependencies = [ "wasm-bindgen", ] @@ -1526,6 +1683,16 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "khronos-egl" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19cc4a81304db2a0ad69740e83cdc3a9364e3f9bd6d88a87288a4c2deec927b" +dependencies = [ + "libc", + "libloading 0.6.7", +] + [[package]] name = "lazy-bytes-cast" version = "5.0.1" @@ -1546,9 +1713,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.85" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" +checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" [[package]] name = "libloading" @@ -1561,12 +1728,13 @@ dependencies = [ ] [[package]] -name = "line_drawing" +name = "libloading" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ - "num-traits", + "cfg-if 1.0.0", + "winapi 0.3.9", ] [[package]] @@ -1577,9 +1745,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "llvm-sys" -version = "100.2.0" +version = "100.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9109e19fbfac3458f2970189719fa19f1007c6fd4e08c44fdebf4be0ddbe261d" +checksum = "15d9c00ce56221b2150e2d4d51887ff139fce5a0e50346c744861d1e66d2f7c4" dependencies = [ "cc", "lazy_static", @@ -1588,15 +1756,6 @@ dependencies = [ "semver", ] -[[package]] -name = "lock_api" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -dependencies = [ - "scopeguard", -] - [[package]] name = "lock_api" version = "0.4.2" @@ -1642,16 +1801,6 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -[[package]] -name = "memmap" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -dependencies = [ - "libc", - "winapi 0.3.9", -] - [[package]] name = "memmap2" version = "0.1.0" @@ -1681,9 +1830,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4e8a431536529327e28c9ba6992f2cb0c15d4222f0602a16e6d7695ff3bccf" +checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c" dependencies = [ "bitflags", "block", @@ -1695,9 +1844,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg 1.0.1", @@ -1748,48 +1897,64 @@ dependencies = [ [[package]] name = "naga" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0873deb76cf44b7454fba7b2ba6a89d3de70c08aceffd2c489379b3d9d08e661" +checksum = "05089b2acdf0e6a962cdbf5e328402345a27f59fcde1a59fe97a73e8149d416f" dependencies = [ + "bit-set", "bitflags", "fxhash", "log", "num-traits", + "petgraph", "spirv_headers", "thiserror", ] [[package]] name = "ndk" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a356cafe20aee088789830bfea3a61336e84ded9e545e00d3869ce95dcb80c" +checksum = "5eb167c1febed0a496639034d0c76b3b74263636045db5489eee52143c246e73" dependencies = [ "jni-sys", "ndk-sys", "num_enum", + "thiserror", ] [[package]] name = "ndk-glue" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1730ee2e3de41c3321160a6da815f008c4006d71b095880ea50e17cf52332b8" +checksum = "bdf399b8b7a39c6fb153c4ec32c72fd5fe789df24a647f229c239aa7adb15241" dependencies = [ - "android_log-sys", "lazy_static", "libc", "log", "ndk", + "ndk-macro", "ndk-sys", ] [[package]] -name = "ndk-sys" -version = "0.1.0" +name = "ndk-macro" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2820aca934aba5ed91c79acc72b6a44048ceacc5d36c035ed4e051f12d887d" +checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.61", +] + +[[package]] +name = "ndk-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" [[package]] name = "net2" @@ -1802,19 +1967,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "nix" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" -dependencies = [ - "bitflags", - "cc", - "cfg-if 0.1.10", - "libc", - "void", -] - [[package]] name = "nix" version = "0.18.0" @@ -1840,10 +1992,22 @@ dependencies = [ ] [[package]] -name = "nom" -version = "6.1.0" +name = "nix" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6f70b46d6325aa300f1c7bb3d470127dfc27806d8ea6bf294ee0ce643ce2b1" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "memchr", "version_check", @@ -1886,8 +2050,8 @@ checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" dependencies = [ "proc-macro-crate", "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -1949,9 +2113,9 @@ checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" [[package]] name = "once_cell" -version = "1.5.2" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "oorandom" @@ -1965,15 +2129,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -[[package]] -name = "ordered-float" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-float" version = "2.1.1" @@ -1984,12 +2139,30 @@ dependencies = [ ] [[package]] -name = "owned_ttf_parser" -version = "0.11.0" +name = "output_vt100" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "948b27637ba56144c62d3415929ef18986b3a383137ebcbe97d9362a968bf997" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "ttf-parser", + "winapi 0.3.9", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" +dependencies = [ + "ttf-parser 0.6.2", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3c7a20e3f122223e68eef6ca58e39bc1ea8a1d83418ba4c2c1ba189d2ee355" +dependencies = [ + "ttf-parser 0.12.0", ] [[package]] @@ -2022,18 +2195,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b4b5f600e60dd3a147fb57b4547033d382d1979eb087af310e91cb45a63b1f4" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", -] - -[[package]] -name = "parking_lot" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.7.2", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -2043,36 +2206,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" dependencies = [ "instant", - "lock_api 0.4.2", - "parking_lot_core 0.8.2", + "lock_api", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.7.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.57", - "smallvec", - "winapi 0.3.9", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ "backtrace", "cfg-if 1.0.0", "instant", "libc", "petgraph", - "redox_syscall 0.1.57", + "redox_syscall 0.2.5", "smallvec", "thread-id", "winapi 0.3.9", @@ -2112,8 +2261,8 @@ dependencies = [ "pest", "pest_meta", "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -2177,15 +2326,15 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" [[package]] name = "pin-utils" @@ -2243,6 +2392,18 @@ dependencies = [ "difference", ] +[[package]] +name = "pretty_assertions" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +dependencies = [ + "ansi_term", + "ctor", + "difference", + "output_vt100", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2260,8 +2421,8 @@ checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", "version_check", ] @@ -2272,8 +2433,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", "syn-mid", "version_check", ] @@ -2319,12 +2480,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.0" @@ -2343,6 +2498,17 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger 0.8.3", + "log", + "rand 0.8.3", +] + [[package]] name = "quickcheck_macros" version = "0.8.0" @@ -2354,6 +2520,17 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.61", +] + [[package]] name = "quote" version = "0.6.13" @@ -2365,9 +2542,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2 1.0.24", ] @@ -2413,7 +2590,7 @@ checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", "rand_chacha 0.3.0", - "rand_core 0.6.1", + "rand_core 0.6.2", "rand_hc 0.3.0", ] @@ -2444,7 +2621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core 0.6.1", + "rand_core 0.6.2", ] [[package]] @@ -2473,9 +2650,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ "getrandom 0.2.2", ] @@ -2504,7 +2681,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ - "rand_core 0.6.1", + "rand_core 0.6.2", ] [[package]] @@ -2613,7 +2790,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel 0.5.0", "crossbeam-deque 0.8.0", - "crossbeam-utils 0.8.1", + "crossbeam-utils 0.8.3", "lazy_static", "num_cpus", ] @@ -2635,9 +2812,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" dependencies = [ "bitflags", ] @@ -2649,7 +2826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.2", - "redox_syscall 0.2.4", + "redox_syscall 0.2.5", ] [[package]] @@ -2693,16 +2870,16 @@ name = "roc_build" version = "0.1.0" dependencies = [ "bumpalo", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inkwell", "inlinable_string", - "libloading", + "libloading 0.6.7", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_builtins", "roc_can", "roc_collections", @@ -2726,11 +2903,11 @@ dependencies = [ name = "roc_builtins" version = "0.1.0" dependencies = [ - "indoc", + "indoc 0.3.6", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_collections", "roc_module", "roc_region", @@ -2742,15 +2919,14 @@ name = "roc_can" version = "0.1.0" dependencies = [ "bumpalo", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inlinable_string", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", - "roc_builtins", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_collections", "roc_module", "roc_parse", @@ -2767,17 +2943,17 @@ dependencies = [ "bumpalo", "clap 3.0.0-beta.1", "const_format", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inkwell", "inlinable_string", "libc", - "libloading", + "libloading 0.6.7", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_build", "roc_builtins", "roc_can", @@ -2811,8 +2987,8 @@ name = "roc_collections" version = "0.1.0" dependencies = [ "bumpalo", - "im", - "im-rc", + "im 14.3.0", + "im-rc 14.3.0", "wyhash", ] @@ -2820,11 +2996,11 @@ dependencies = [ name = "roc_constrain" version = "0.1.0" dependencies = [ - "indoc", + "indoc 0.3.6", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_builtins", "roc_can", "roc_collections", @@ -2846,12 +3022,12 @@ dependencies = [ "confy", "copypasta", "criterion", - "env_logger 0.7.1", + "env_logger 0.8.3", "futures", "glyph_brush", - "im", - "im-rc", - "indoc", + "im 15.0.0", + "im-rc 15.0.0", + "indoc 1.0.3", "inlinable_string", "libc", "log", @@ -2860,9 +3036,9 @@ dependencies = [ "palette", "pest", "pest_derive", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.6.1", + "quickcheck 1.0.3", + "quickcheck_macros 1.0.0", "rand 0.8.3", "roc_can", "roc_collections", @@ -2888,14 +3064,14 @@ name = "roc_fmt" version = "0.1.0" dependencies = [ "bumpalo", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inlinable_string", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_collections", "roc_module", "roc_parse", @@ -2908,16 +3084,16 @@ version = "0.1.0" dependencies = [ "bumpalo", "either", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inkwell", "inlinable_string", "libc", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_build", "roc_builtins", "roc_can", @@ -2942,18 +3118,18 @@ name = "roc_gen_dev" version = "0.1.0" dependencies = [ "bumpalo", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inlinable_string", "itertools 0.9.0", "libc", - "libloading", + "libloading 0.6.7", "maplit", "object 0.22.0", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_build", "roc_builtins", "roc_can", @@ -2981,14 +3157,14 @@ version = "0.1.0" dependencies = [ "bumpalo", "crossbeam", - "indoc", + "indoc 0.3.6", "inlinable_string", "maplit", "num_cpus", - "parking_lot 0.11.1", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "parking_lot", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_builtins", "roc_can", "roc_collections", @@ -3011,11 +3187,11 @@ name = "roc_module" version = "0.1.0" dependencies = [ "bumpalo", - "indoc", + "indoc 0.3.6", "inlinable_string", "lazy_static", "maplit", - "pretty_assertions", + "pretty_assertions 0.5.1", "roc_collections", "roc_region", ] @@ -3025,12 +3201,12 @@ name = "roc_mono" version = "0.1.0" dependencies = [ "bumpalo", - "indoc", + "indoc 0.3.6", "linked-hash-map", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_builtins", "roc_can", "roc_collections", @@ -3052,11 +3228,11 @@ version = "0.1.0" dependencies = [ "bumpalo", "encode_unicode", - "indoc", + "indoc 0.3.6", "inlinable_string", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_collections", "roc_module", "roc_region", @@ -3066,12 +3242,12 @@ dependencies = [ name = "roc_problem" version = "0.1.0" dependencies = [ - "indoc", + "indoc 0.3.6", "inlinable_string", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_collections", "roc_module", "roc_parse", @@ -3088,14 +3264,14 @@ version = "0.1.0" dependencies = [ "bumpalo", "distance", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inlinable_string", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_builtins", "roc_can", "roc_collections", @@ -3115,11 +3291,11 @@ name = "roc_solve" version = "0.1.0" dependencies = [ "bumpalo", - "indoc", + "indoc 0.3.6", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_builtins", "roc_can", "roc_collections", @@ -3145,12 +3321,12 @@ dependencies = [ name = "roc_types" version = "0.1.0" dependencies = [ - "indoc", + "indoc 0.3.6", "inlinable_string", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_collections", "roc_module", "roc_region", @@ -3161,11 +3337,11 @@ dependencies = [ name = "roc_unify" version = "0.1.0" dependencies = [ - "indoc", + "indoc 0.3.6", "maplit", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", + "pretty_assertions 0.5.1", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_collections", "roc_module", "roc_types", @@ -3203,22 +3379,12 @@ dependencies = [ [[package]] name = "rusttype" -version = "0.7.9" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310942406a39981bed7e12b09182a221a29e0990f3e7e0c971f131922ed135d5" +checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" dependencies = [ - "rusttype 0.8.3", -] - -[[package]] -name = "rusttype" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0" -dependencies = [ - "approx 0.3.2", - "ordered-float 1.1.1", - "stb_truetype", + "ab_glyph_rasterizer", + "owned_ttf_parser 0.6.0", ] [[package]] @@ -3248,8 +3414,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db9dfbf470021de34cfaf6983067f460ea19164934a7c2d4b92eec0968eb95f1" dependencies = [ - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -3296,9 +3462,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.123" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" dependencies = [ "serde_derive", ] @@ -3327,20 +3493,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.123" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] name = "serde_json" -version = "1.0.62" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", @@ -3366,7 +3532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" dependencies = [ "lazy_static", - "parking_lot 0.11.1", + "parking_lot", "serial_test_derive", ] @@ -3377,8 +3543,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -3409,12 +3575,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "sized-chunks" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e65d6a9f13cd78f361ea5a2cf53a45d67cdda421ba0316b9be101560f3d207" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "slotmap" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46a3482db8f247956e464d783693ece164ca056e6e67563ee5505bdb86452cd" + [[package]] name = "smallvec" version = "1.6.1" @@ -3423,35 +3605,21 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "smithay-client-toolkit" -version = "0.6.6" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421c8dc7acf5cb205b88160f8b4cc2c5cfabe210e43b2f80f009f4c1ef910f1d" +checksum = "4750c76fd5d3ac95fa3ed80fe667d6a3d8590a960e5b575b98eea93339a80b80" dependencies = [ "andrew", "bitflags", - "dlib", - "lazy_static", - "memmap", - "nix 0.14.1", - "wayland-client 0.23.6", - "wayland-protocols 0.23.6", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "316e13a3eb853ce7bf72ad3530dc186cb2005c57c521ef5f4ada5ee4eed74de6" -dependencies = [ - "bitflags", - "dlib", + "calloop", + "dlib 0.4.2", "lazy_static", "log", "memmap2", "nix 0.18.0", - "wayland-client 0.28.3", + "wayland-client", "wayland-cursor", - "wayland-protocols 0.28.3", + "wayland-protocols", ] [[package]] @@ -3460,8 +3628,8 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06384dfaf645908220d976ae24ed39f6cf92efecb0225ea0a948e403014de527" dependencies = [ - "smithay-client-toolkit 0.12.2", - "wayland-client 0.28.3", + "smithay-client-toolkit", + "wayland-client", ] [[package]] @@ -3482,15 +3650,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] name = "spirv_cross" -version = "0.22.2" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebd49af36be83ecd6290b57147e2a0e26145b832634b17146d934b197ca3713" +checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" dependencies = [ "cc", "js-sys", @@ -3513,22 +3681,13 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stb_truetype" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" -dependencies = [ - "byteorder", -] - [[package]] name = "storage-map" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" dependencies = [ - "lock_api 0.4.2", + "lock_api", ] [[package]] @@ -3559,12 +3718,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", + "quote 1.0.9", "unicode-xid 0.2.1", ] @@ -3575,8 +3734,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -3586,8 +3745,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", "unicode-xid 0.2.1", ] @@ -3606,7 +3765,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "rand 0.8.3", - "redox_syscall 0.2.4", + "redox_syscall 0.2.5", "remove_dir_all", "winapi 0.3.9", ] @@ -3626,16 +3785,16 @@ version = "0.1.0" dependencies = [ "bumpalo", "either", - "im", - "im-rc", - "indoc", + "im 14.3.0", + "im-rc 14.3.0", + "indoc 0.3.6", "inkwell", "inlinable_string", "libc", - "libloading", + "libloading 0.6.7", "maplit", - "quickcheck", - "quickcheck_macros", + "quickcheck 0.8.5", + "quickcheck_macros 0.8.0", "roc_build", "roc_builtins", "roc_can", @@ -3668,22 +3827,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", ] [[package]] @@ -3714,9 +3873,9 @@ checksum = "7572415bd688d401c52f6e36f4c8e805b9ae1622619303b9fa835d531db0acae" [[package]] name = "tinytemplate" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", @@ -3732,7 +3891,7 @@ dependencies = [ "fnv", "memchr", "num_cpus", - "pin-project-lite 0.1.11", + "pin-project-lite 0.1.12", "slab", ] @@ -3747,15 +3906,27 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.23" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3" +checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" dependencies = [ "cfg-if 1.0.0", - "pin-project-lite 0.2.4", + "pin-project-lite 0.2.6", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a9bd1db7706f2373a190b0d067146caa39350c486f3d455b0e33b431f94c07" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.61", +] + [[package]] name = "tracing-core" version = "0.1.17" @@ -3767,9 +3938,15 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.11.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e7994fc4aed0ee366a4b0d01562c8a7cd5a5017088bceb6921b0c8c538f34e" +checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" + +[[package]] +name = "ttf-parser" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e00391c1f3d171490a3f8bd79999b0002ae38d3da0d6a3a306c754b053d71b" [[package]] name = "twox-hash" @@ -3889,12 +4066,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "vte" version = "0.3.3" @@ -3929,9 +4100,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.70" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" +checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3939,24 +4110,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.70" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" +checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.20" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" +checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3966,32 +4137,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.70" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" +checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" dependencies = [ - "quote 1.0.8", + "quote 1.0.9", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.70" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" +checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.60", + "quote 1.0.9", + "syn 1.0.61", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.70" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" +checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" [[package]] name = "wasmparser" @@ -4001,142 +4172,82 @@ checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6" [[package]] name = "wayland-client" -version = "0.23.6" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1080ebe0efabcf12aef2132152f616038f2d7dcbbccf7b2d8c5270fe14bcda" -dependencies = [ - "bitflags", - "calloop", - "downcast-rs", - "libc", - "mio", - "nix 0.14.1", - "wayland-commons 0.23.6", - "wayland-scanner 0.23.6", - "wayland-sys 0.23.6", -] - -[[package]] -name = "wayland-client" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbdbe01d03b2267809f3ed99495b37395387fde789e0f2ebb78e8b43f75b6d7" +checksum = "06ca44d86554b85cf449f1557edc6cc7da935cc748c8e4bf1c507cbd43bae02c" dependencies = [ "bitflags", "downcast-rs", "libc", - "nix 0.18.0", + "nix 0.20.0", "scoped-tls", - "wayland-commons 0.28.3", - "wayland-scanner 0.28.3", - "wayland-sys 0.28.3", + "wayland-commons", + "wayland-scanner", + "wayland-sys", ] [[package]] name = "wayland-commons" -version = "0.23.6" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" +checksum = "8bd75ae380325dbcff2707f0cd9869827ea1d2d6d534cff076858d3f0460fd5a" dependencies = [ - "nix 0.14.1", - "wayland-sys 0.23.6", -] - -[[package]] -name = "wayland-commons" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480450f76717edd64ad04a4426280d737fc3d10a236b982df7b1aee19f0e2d56" -dependencies = [ - "nix 0.18.0", + "nix 0.20.0", "once_cell", "smallvec", - "wayland-sys 0.28.3", + "wayland-sys", ] [[package]] name = "wayland-cursor" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6eb122c160223a7660feeaf949d0100281d1279acaaed3720eb3c9894496e5f" +checksum = "b37e5455ec72f5de555ec39b5c3704036ac07c2ecd50d0bffe02d5fe2d4e65ab" dependencies = [ - "nix 0.18.0", - "wayland-client 0.28.3", + "nix 0.20.0", + "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.23.6" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc286643656742777d55dc8e70d144fa4699e426ca8e9d4ef454f4bf15ffcf9" +checksum = "95df3317872bcf9eec096c864b69aa4769a1d5d6291a5b513f8ba0af0efbd52c" dependencies = [ "bitflags", - "wayland-client 0.23.6", - "wayland-commons 0.23.6", - "wayland-scanner 0.23.6", -] - -[[package]] -name = "wayland-protocols" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "319a82b4d3054dd25acc32d9aee0f84fa95b63bc983fffe4703b6b8d47e01a30" -dependencies = [ - "bitflags", - "wayland-client 0.28.3", - "wayland-commons 0.28.3", - "wayland-scanner 0.28.3", + "wayland-client", + "wayland-commons", + "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.23.6" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93b02247366f395b9258054f964fe293ddd019c3237afba9be2ccbe9e1651c3d" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "xml-rs", -] - -[[package]] -name = "wayland-scanner" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7010ba5767b3fcd350decc59055390b4ebe6bd1b9279a9feb1f1888987f1133d" +checksum = "389d680d7bd67512dc9c37f39560224327038deb0f0e8d33f870900441b68720" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.8", + "quote 1.0.9", "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.23.6" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d94e89a86e6d6d7c7c9b19ebf48a03afaac4af6bc22ae570e9a24124b75358f4" +checksum = "2907bd297eef464a95ba9349ea771611771aa285b932526c633dc94d5400a8e2" dependencies = [ - "dlib", - "lazy_static", -] - -[[package]] -name = "wayland-sys" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6793834e0c35d11fd96a97297abe03d37be627e1847da52e17d7e0e3b51cc099" -dependencies = [ - "dlib", + "dlib 0.5.0", "lazy_static", "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.47" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3" +checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" dependencies = [ "js-sys", "wasm-bindgen", @@ -4144,20 +4255,17 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991903e4c9f5b7319732b30a3d0339e27a51ea992cea22769b5f6c7f7076af6d" +checksum = "c60007fc3748278a36b458d96f86105f43aa5f0e412b15a5f934950d61ec26a9" dependencies = [ "arrayvec", - "futures", - "gfx-backend-vulkan", "js-sys", - "objc", - "parking_lot 0.11.1", + "naga", + "parking_lot", "raw-window-handle", "smallvec", "tracing", - "typed-arena", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4167,24 +4275,26 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea487deeae90e06d77eb8e6cef945247774e7c0a0a226d238b31e90633594365" +checksum = "c89fa2cc5d72236461ac09c5be967012663e29cb62f1a972654cbf35e49dffa8" dependencies = [ "arrayvec", "bitflags", + "cfg_aliases", "copyless", "fxhash", "gfx-backend-dx11", "gfx-backend-dx12", "gfx-backend-empty", + "gfx-backend-gl", "gfx-backend-metal", "gfx-backend-vulkan", - "gfx-descriptor", "gfx-hal", - "gfx-memory", + "gpu-alloc", + "gpu-descriptor", "naga", - "parking_lot 0.11.1", + "parking_lot", "raw-window-handle", "smallvec", "thiserror", @@ -4194,23 +4304,23 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e3529528e608b54838ee618c3923b0f46e6db0334cfc6c42a16cf4ceb3bdb57" +checksum = "72fa9ba80626278fd87351555c363378d08122d7601e58319be3d6fa85a87747" dependencies = [ "bitflags", ] [[package]] name = "wgpu_glyph" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27812a263e1298d3330795af62faf5daf5852beb632794acf93e4494234fc9f4" +checksum = "354c1f79e09923724a6006a6953c3946522b84f7a9ec202b7e8001f274fd4bd5" dependencies = [ + "bytemuck", "glyph_brush", "log", "wgpu", - "zerocopy", ] [[package]] @@ -4258,14 +4368,14 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winit" -version = "0.22.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4ccbf7ddb6627828eace16cacde80fc6bf4dbb3469f88487262a02cf8e7862" +checksum = "da4eda6fce0eb84bd0a33e3c8794eb902e1033d0a1d5a31bc4f19b1b4bbff597" dependencies = [ "bitflags", "cocoa", - "core-foundation 0.7.0", - "core-graphics", + "core-foundation 0.9.1", + "core-graphics 0.22.2", "core-video-sys", "dispatch", "instant", @@ -4278,11 +4388,11 @@ dependencies = [ "ndk-glue", "ndk-sys", "objc", - "parking_lot 0.10.2", + "parking_lot", "percent-encoding", "raw-window-handle", - "smithay-client-toolkit 0.6.6", - "wayland-client 0.23.6", + "smithay-client-toolkit", + "wayland-client", "winapi 0.3.9", "x11-dl", ] @@ -4315,16 +4425,6 @@ dependencies = [ "rand_core 0.4.2", ] -[[package]] -name = "x11" -version = "2.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ecd092546cb16f25783a5451538e73afc8d32e242648d54f4ae5459ba1e773" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "x11-clipboard" version = "0.5.1" @@ -4409,6 +4509,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" dependencies = [ "proc-macro2 1.0.24", - "syn 1.0.60", + "syn 1.0.61", "synstructure", ] diff --git a/Earthfile b/Earthfile index d4f41a014c..c297d4c3e2 100644 --- a/Earthfile +++ b/Earthfile @@ -8,7 +8,7 @@ install-other-libs: FROM +prep-debian RUN apt -y install wget git RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard - RUN apt -y install libc++-dev libc++abi-dev libunwind-dev pkg-config libx11-dev zlib1g-dev + RUN apt -y install libc++-dev libc++abi-dev g++ libunwind-dev pkg-config libx11-dev zlib1g-dev install-zig-llvm-valgrind-clippy-rustfmt: FROM +install-other-libs @@ -30,10 +30,10 @@ install-zig-llvm-valgrind-clippy-rustfmt: RUN wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2 RUN tar -xf valgrind-3.16.1.tar.bz2 # need to cd every time, every command starts at WORKDIR - RUN cd valgrind-3.16.1; ./autogen.sh - RUN cd valgrind-3.16.1; ./configure --disable-dependency-tracking - RUN cd valgrind-3.16.1; make -j`nproc` - RUN cd valgrind-3.16.1; make install + RUN cd valgrind-3.16.1 && ./autogen.sh + RUN cd valgrind-3.16.1 && ./configure --disable-dependency-tracking + RUN cd valgrind-3.16.1 && make -j`nproc` + RUN cd valgrind-3.16.1 && make install # clippy RUN rustup component add clippy # rustfmt @@ -75,16 +75,16 @@ save-cache: FROM +install-zig-llvm-valgrind-clippy-rustfmt COPY +prepare-cache/recipe.json ./ RUN --mount=type=cache,target=$SCCACHE_DIR \ - cargo chef cook; sccache --show-stats # for clippy + cargo chef cook && sccache --show-stats # for clippy RUN --mount=type=cache,target=$SCCACHE_DIR \ - cargo chef cook --release --tests; sccache --show-stats + cargo chef cook --release --tests && sccache --show-stats SAVE ARTIFACT target SAVE ARTIFACT $CARGO_HOME cargo_home test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt COPY --dir compiler/builtins/bitcode ./ - RUN cd bitcode; ./run-tests.sh; + RUN cd bitcode && ./run-tests.sh check-clippy: FROM +copy-dirs-and-cache @@ -101,7 +101,7 @@ test-rust: FROM +copy-dirs-and-cache ENV RUST_BACKTRACE=1 RUN --mount=type=cache,target=$SCCACHE_DIR \ - cargo test --release; sccache --show-stats + cargo test --release && sccache --show-stats test-all: BUILD +test-zig diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index fcd5f42cf8..139163af1d 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -114,6 +114,7 @@ pub const RocList = extern struct { const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; +const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; pub fn listMap(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, old_element_width: usize, new_element_width: usize) callconv(.C) RocList { if (list.bytes) |source_ptr| { @@ -213,6 +214,129 @@ pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Calle } } +pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaque, caller: Caller3, alignment: usize, a_width: usize, b_width: usize, c_width: usize, d_width: usize, dec_a: Dec, dec_b: Dec, dec_c: Dec) callconv(.C) RocList { + const smaller_length = std.math.min(list1.len(), list2.len()); + const output_length = std.math.min(smaller_length, list3.len()); + + if (list1.bytes) |source_a| { + if (list2.bytes) |source_b| { + if (list3.bytes) |source_c| { + const output = RocList.allocate(std.heap.c_allocator, alignment, output_length, d_width); + const target_ptr = output.bytes orelse unreachable; + + var i: usize = 0; + while (i < output_length) : (i += 1) { + const element_a = source_a + i * a_width; + const element_b = source_b + i * b_width; + const element_c = source_c + i * c_width; + const target = target_ptr + i * d_width; + + caller(transform, element_a, element_b, element_c, target); + } + + // if the lists don't have equal length, we must consume the remaining elements + // In this case we consume by (recursively) decrementing the elements + if (list1.len() > output_length) { + i = output_length; + while (i < list1.len()) : (i += 1) { + const element_a = source_a + i * a_width; + dec_a(element_a); + } + } + + if (list2.len() > output_length) { + i = output_length; + while (i < list2.len()) : (i += 1) { + const element_b = source_b + i * b_width; + dec_b(element_b); + } + } + + if (list3.len() > output_length) { + i = output_length; + while (i < list3.len()) : (i += 1) { + const element_c = source_c + i * c_width; + dec_c(element_c); + } + } + + utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); + utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); + utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width); + + return output; + } else { + // consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null + var i: usize = 0; + while (i < list1.len()) : (i += 1) { + const element_a = source_a + i * a_width; + dec_a(element_a); + } + utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); + + // consume list2 elements (we know there is at least one because the list1.bytes pointer is non-null + i = 0; + while (i < list2.len()) : (i += 1) { + const element_b = source_b + i * b_width; + dec_b(element_b); + } + utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); + + return RocList.empty(); + } + } else { + // consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null + var i: usize = 0; + while (i < list1.len()) : (i += 1) { + const element_a = source_a + i * a_width; + dec_a(element_a); + } + + utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); + + // consume list3 elements (if any) + if (list3.bytes) |source_c| { + i = 0; + + while (i < list2.len()) : (i += 1) { + const element_c = source_c + i * c_width; + dec_c(element_c); + } + + utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width); + } + + return RocList.empty(); + } + } else { + // consume list2 elements (if any) + if (list2.bytes) |source_b| { + var i: usize = 0; + + while (i < list2.len()) : (i += 1) { + const element_b = source_b + i * b_width; + dec_b(element_b); + } + + utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); + } + + // consume list3 elements (if any) + if (list3.bytes) |source_c| { + var i: usize = 0; + + while (i < list2.len()) : (i += 1) { + const element_c = source_c + i * c_width; + dec_c(element_c); + } + + utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width); + } + + return RocList.empty(); + } +} + pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, element_width: usize, inc: Inc, dec: Dec) callconv(.C) RocList { if (list.bytes) |source_ptr| { const size = list.len(); diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index e0f501654b..4cd57a84a5 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -8,6 +8,7 @@ const list = @import("list.zig"); comptime { exportListFn(list.listMap, "map"); exportListFn(list.listMap2, "map2"); + exportListFn(list.listMap3, "map3"); exportListFn(list.listMapWithIndex, "map_with_index"); exportListFn(list.listKeepIf, "keep_if"); exportListFn(list.listWalk, "walk"); diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 2905ec52df..0fd6cb1f2f 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -64,6 +64,7 @@ pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list"; pub const LIST_MAP: &str = "roc_builtins.list.map"; pub const LIST_MAP2: &str = "roc_builtins.list.map2"; +pub const LIST_MAP3: &str = "roc_builtins.list.map3"; pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index"; pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if"; pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 865d61d401..16b416cc71 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -821,6 +821,21 @@ pub fn types() -> MutMap { ) }); + // map3 : List a, List b, List c, (a, b, c -> d) -> List d + add_type(Symbol::LIST_MAP3, { + let_tvars! {a, b, c, d, cvar}; + + top_level_function( + vec![ + list_type(flex(a)), + list_type(flex(b)), + list_type(flex(c)), + closure(vec![flex(a), flex(b), flex(c)], cvar, Box::new(flex(d))), + ], + Box::new(list_type(flex(d))), + ) + }); + // append : List elem, elem -> List elem add_type( Symbol::LIST_APPEND, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 47cd658bb8..25abe90829 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -81,6 +81,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_JOIN => list_join, LIST_MAP => list_map, LIST_MAP2 => list_map2, + LIST_MAP3 => list_map3, LIST_MAP_WITH_INDEX => list_map_with_index, LIST_KEEP_IF => list_keep_if, LIST_KEEP_OKS => list_keep_oks, @@ -219,6 +220,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::LIST_JOIN => list_join, Symbol::LIST_MAP => list_map, Symbol::LIST_MAP2 => list_map2, + Symbol::LIST_MAP3 => list_map3, Symbol::LIST_MAP_WITH_INDEX => list_map_with_index, Symbol::LIST_KEEP_IF => list_keep_if, Symbol::LIST_KEEP_OKS => list_keep_oks, @@ -372,6 +374,38 @@ fn lowlevel_3(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { ) } +fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { + let arg1_var = var_store.fresh(); + let arg2_var = var_store.fresh(); + let arg3_var = var_store.fresh(); + let arg4_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + let body = RunLowLevel { + op, + args: vec![ + (arg1_var, Var(Symbol::ARG_1)), + (arg2_var, Var(Symbol::ARG_2)), + (arg3_var, Var(Symbol::ARG_3)), + (arg4_var, Var(Symbol::ARG_4)), + ], + ret_var, + }; + + defn( + symbol, + vec![ + (arg1_var, Symbol::ARG_1), + (arg2_var, Symbol::ARG_2), + (arg3_var, Symbol::ARG_3), + (arg4_var, Symbol::ARG_4), + ], + var_store, + body, + ret_var, + ) +} + /// Num.maxInt : Int fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); @@ -2151,6 +2185,11 @@ fn list_map2(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_3(symbol, LowLevel::ListMap2, var_store) } +/// List.map3 : List a, List b, (a, b -> c) -> List c +fn list_map3(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_4(symbol, LowLevel::ListMap3, var_store) +} + /// Dict.hashTestOnly : k, v -> Nat pub fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::Hash, var_store) diff --git a/compiler/can/tests/helpers/mod.rs b/compiler/can/tests/helpers/mod.rs index 679d341204..8540427d98 100644 --- a/compiler/can/tests/helpers/mod.rs +++ b/compiler/can/tests/helpers/mod.rs @@ -8,9 +8,6 @@ use roc_can::operator; use roc_can::scope::Scope; use roc_collections::all::MutMap; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; -use roc_parse::ast::{self, Attempting}; -use roc_parse::blankspace::space0_before; -use roc_parse::parser::{loc, Parser, State, SyntaxError}; use roc_problem::can::Problem; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; @@ -20,25 +17,6 @@ pub fn test_home() -> ModuleId { ModuleIds::default().get_or_insert(&"Test".into()) } -#[allow(dead_code)] -pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, SyntaxError<'a>> { - parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) -} - -#[allow(dead_code)] -pub fn parse_loc_with<'a>( - arena: &'a Bump, - input: &'a str, -) -> Result>, SyntaxError<'a>> { - let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module); - let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); - let answer = parser.parse(&arena, state); - - answer - .map(|(_, loc_expr, _)| loc_expr) - .map_err(|(_, fail, _)| fail) -} - #[allow(dead_code)] pub fn can_expr(expr_str: &str) -> CanExprOut { can_expr_with(&Bump::new(), test_home(), expr_str) @@ -56,7 +34,7 @@ pub struct CanExprOut { #[allow(dead_code)] pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { - let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| { + let loc_expr = roc_parse::test_helpers::parse_loc_with(&arena, expr_str).unwrap_or_else(|e| { panic!( "can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}", expr_str, e diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index ee17739af7..151dd27635 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -4,8 +4,6 @@ extern crate pretty_assertions; extern crate indoc; extern crate bumpalo; extern crate roc_fmt; -#[macro_use] -extern crate roc_parse; #[cfg(test)] mod test_fmt { @@ -14,27 +12,15 @@ mod test_fmt { use roc_fmt::annotation::{Formattable, Newlines, Parens}; use roc_fmt::def::fmt_def; use roc_fmt::module::fmt_module; - use roc_parse::ast::{Attempting, Expr}; - use roc_parse::blankspace::space0_before; use roc_parse::module::{self, module_defs}; - use roc_parse::parser::{Parser, State, SyntaxError}; - - fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, SyntaxError<'a>> { - let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module); - let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0); - let answer = parser.parse(&arena, state); - - answer - .map(|(_, loc_expr, _)| loc_expr.value) - .map_err(|(_, fail, _)| fail) - } + use roc_parse::parser::{Parser, State}; fn expr_formats_to(input: &str, expected: &str) { let arena = Bump::new(); let input = input.trim_end(); let expected = expected.trim_end(); - match parse_with(&arena, input) { + match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) { Ok(actual) => { let mut buf = String::new_in(&arena); @@ -55,8 +41,8 @@ mod test_fmt { let src = src.trim_end(); let expected = expected.trim_end(); - match module::header().parse(&arena, State::new_in(&arena, src.as_bytes(), Attempting::Module)) { - Ok((_, actual, state)) => { + match module::parse_header(&arena, State::new(src.as_bytes())) { + Ok((actual, state)) => { let mut buf = String::new_in(&arena); fmt_module(&mut buf, &actual); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 1beb7372cb..9f42b8e8d3 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, - list_map2, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, list_single, - list_sum, list_walk, list_walk_backwards, + list_map2, list_map3, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, + list_single, list_sum, list_walk, list_walk_backwards, }; use crate::llvm::build_str::{ str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, @@ -3743,6 +3743,38 @@ fn run_low_level<'a, 'ctx, 'env>( _ => unreachable!("invalid list layout"), } } + ListMap3 => { + debug_assert_eq!(args.len(), 4); + + let (list1, list1_layout) = load_symbol_and_layout(scope, &args[0]); + let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]); + let (list3, list3_layout) = load_symbol_and_layout(scope, &args[2]); + + let (func, func_layout) = load_symbol_and_layout(scope, &args[3]); + + match (list1_layout, list2_layout, list3_layout) { + ( + Layout::Builtin(Builtin::List(_, element1_layout)), + Layout::Builtin(Builtin::List(_, element2_layout)), + Layout::Builtin(Builtin::List(_, element3_layout)), + ) => list_map3( + env, + layout_ids, + func, + func_layout, + list1, + list2, + list3, + element1_layout, + element2_layout, + element3_layout, + ), + (Layout::Builtin(Builtin::EmptyList), _, _) + | (_, Layout::Builtin(Builtin::EmptyList), _) + | (_, _, Layout::Builtin(Builtin::EmptyList)) => empty_list(env), + _ => unreachable!("invalid list layout"), + } + } ListMapWithIndex => { // List.map : List before, (before -> after) -> List after debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 4124891cf0..fb3232e0b8 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -1305,6 +1305,114 @@ pub fn list_map2<'a, 'ctx, 'env>( ) } +pub fn list_map3<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + transform: BasicValueEnum<'ctx>, + transform_layout: &Layout<'a>, + list1: BasicValueEnum<'ctx>, + list2: BasicValueEnum<'ctx>, + list3: BasicValueEnum<'ctx>, + element1_layout: &Layout<'a>, + element2_layout: &Layout<'a>, + element3_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + + let return_layout = match transform_layout { + Layout::FunctionPointer(_, ret) => ret, + Layout::Closure(_, _, ret) => ret, + _ => unreachable!("not a callable layout"), + }; + + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let list1_i128 = complex_bitcast( + env.builder, + list1, + env.context.i128_type().into(), + "to_i128", + ); + + let list2_i128 = complex_bitcast( + env.builder, + list2, + env.context.i128_type().into(), + "to_i128", + ); + + let list3_i128 = complex_bitcast( + env.builder, + list3, + env.context.i128_type().into(), + "to_i128", + ); + + let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr"); + env.builder.build_store(transform_ptr, transform); + + let argument_layouts = [ + element1_layout.clone(), + element2_layout.clone(), + element3_layout.clone(), + ]; + let stepper_caller = + build_transform_caller(env, layout_ids, transform_layout, &argument_layouts) + .as_global_value() + .as_pointer_value(); + + let a_width = env + .ptr_int() + .const_int(element1_layout.stack_size(env.ptr_bytes) as u64, false); + + let b_width = env + .ptr_int() + .const_int(element2_layout.stack_size(env.ptr_bytes) as u64, false); + + let c_width = env + .ptr_int() + .const_int(element3_layout.stack_size(env.ptr_bytes) as u64, false); + + let d_width = env + .ptr_int() + .const_int(return_layout.stack_size(env.ptr_bytes) as u64, false); + + let alignment = return_layout.alignment_bytes(env.ptr_bytes); + let alignment_iv = env.ptr_int().const_int(alignment as u64, false); + + let dec_a = build_dec_wrapper(env, layout_ids, element1_layout); + let dec_b = build_dec_wrapper(env, layout_ids, element2_layout); + let dec_c = build_dec_wrapper(env, layout_ids, element3_layout); + + let output = call_bitcode_fn( + env, + &[ + list1_i128, + list2_i128, + list3_i128, + env.builder + .build_bitcast(transform_ptr, u8_ptr, "to_opaque"), + stepper_caller.into(), + alignment_iv.into(), + a_width.into(), + b_width.into(), + c_width.into(), + d_width.into(), + dec_a.as_global_value().as_pointer_value().into(), + dec_b.as_global_value().as_pointer_value().into(), + dec_c.as_global_value().as_pointer_value().into(), + ], + bitcode::LIST_MAP3, + ); + + complex_bitcast( + env.builder, + output, + collection(env.context, env.ptr_bytes).into(), + "from_i128", + ) +} + /// List.concat : List elem, List elem -> List elem pub fn list_concat<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index dc0dc9ec12..9082abf113 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -22,7 +22,7 @@ use roc_mono::ir::{ CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs, }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; -use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation}; +use roc_parse::ast::{self, StrLiteral, TypeAnnotation}; use roc_parse::header::{ ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent, }; @@ -2304,8 +2304,8 @@ fn load_pkg_config<'a>( Ok(bytes_vec) => { let parse_start = SystemTime::now(); let bytes = arena.alloc(bytes_vec); - let parse_state = parser::State::new_in(arena, bytes, Attempting::Module); - let parsed = roc_parse::module::header().parse(&arena, parse_state); + let parse_state = parser::State::new(bytes); + let parsed = roc_parse::module::parse_header(&arena, parse_state); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings @@ -2319,19 +2319,19 @@ fn load_pkg_config<'a>( effect_module_timing.parse_header = parse_header_duration; match parsed { - Ok((_, ast::Module::Interface { header }, _parse_state)) => { + Ok((ast::Module::Interface { header }, _parse_state)) => { Err(LoadingProblem::UnexpectedHeader(format!( "expected platform/package module, got Interface with header\n{:?}", header ))) } - Ok((_, ast::Module::App { header }, _parse_state)) => { + Ok((ast::Module::App { header }, _parse_state)) => { Err(LoadingProblem::UnexpectedHeader(format!( "expected platform/package module, got App with header\n{:?}", header ))) } - Ok((_, ast::Module::Platform { header }, parser_state)) => { + Ok((ast::Module::Platform { header }, parser_state)) => { // make a Pkg-Config module that ultimately exposes `main` to the host let pkg_config_module_msg = fabricate_pkg_config_module( arena, @@ -2359,8 +2359,8 @@ fn load_pkg_config<'a>( Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) } - Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed( - fail.into_parse_problem(filename, bytes), + Err(fail) => Err(LoadingProblem::ParsingFailed( + SyntaxError::Header(fail).into_parse_problem(filename, bytes), )), } } @@ -2474,8 +2474,8 @@ fn parse_header<'a>( start_time: SystemTime, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let parse_start = SystemTime::now(); - let parse_state = parser::State::new_in(arena, src_bytes, Attempting::Module); - let parsed = roc_parse::module::header().parse(&arena, parse_state); + let parse_state = parser::State::new(src_bytes); + let parsed = roc_parse::module::parse_header(&arena, parse_state); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings @@ -2485,7 +2485,7 @@ fn parse_header<'a>( module_timing.parse_header = parse_header_duration; match parsed { - Ok((_, ast::Module::Interface { header }, parse_state)) => { + Ok((ast::Module::Interface { header }, parse_state)) => { let header_src = unsafe { let chomped = src_bytes.len() - parse_state.bytes.len(); std::str::from_utf8_unchecked(&src_bytes[..chomped]) @@ -2514,7 +2514,7 @@ fn parse_header<'a>( module_timing, )) } - Ok((_, ast::Module::App { header }, parse_state)) => { + Ok((ast::Module::App { header }, parse_state)) => { let mut pkg_config_dir = filename.clone(); pkg_config_dir.pop(); @@ -2623,7 +2623,7 @@ fn parse_header<'a>( }, } } - Ok((_, ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module( + Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module( arena, &"", module_ids, @@ -2632,8 +2632,8 @@ fn parse_header<'a>( header, module_timing, )), - Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed( - fail.into_parse_problem(filename, src_bytes), + Err(fail) => Err(LoadingProblem::ParsingFailed( + SyntaxError::Header(fail).into_parse_problem(filename, src_bytes), )), } } diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index c597c42076..b8a2ed31c6 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -28,6 +28,7 @@ pub enum LowLevel { ListJoin, ListMap, ListMap2, + ListMap3, ListMapWithIndex, ListKeepIf, ListWalk, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 100d197578..58423611dd 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -911,6 +911,7 @@ define_builtins! { 22 LIST_KEEP_ERRS: "keepErrs" 23 LIST_MAP_WITH_INDEX: "mapWithIndex" 24 LIST_MAP2: "map2" + 25 LIST_MAP3: "map3" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 486205fcb3..b3785ddaa9 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -652,6 +652,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListJoin => arena.alloc_slice_copy(&[irrelevant]), ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]), ListMap2 => arena.alloc_slice_copy(&[owned, owned, irrelevant]), + ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]), ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]), diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 86b35524bf..bc0e0d1259 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -589,33 +589,6 @@ impl<'a> Spaceable<'a> for Def<'a> { } } -/// What we're currently attempting to parse, e.g. -/// "currently attempting to parse a list." This helps error messages! -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Attempting { - LineComment, - List, - Keyword, - StrLiteral, - RecordLiteral, - RecordFieldLabel, - InterpolatedString, - NumberLiteral, - UnicodeEscape, - ClosureParams, - ClosureBody, - Def, - Module, - Record, - Identifier, - HexDigit, - ConcreteType, - TypeVariable, - WhenCondition, - WhenBranch, - TODO, -} - impl<'a> Expr<'a> { pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> { Loc { diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 93e6653a3b..8fd5dac813 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -1,64 +1,13 @@ -use crate::ast::CommentOrNewline::{self, *}; -use crate::ast::{Attempting, Spaceable}; +use crate::ast::CommentOrNewline; +use crate::ast::Spaceable; use crate::parser::{ - self, and, ascii_char, ascii_string, backtrackable, bad_input_to_syntax_error, optional, - parse_utf8, peek_utf8_char, then, unexpected, unexpected_eof, BadInputError, Col, Parser, + self, and, BadInputError, Col, Parser, Progress::{self, *}, - Row, State, SyntaxError, + Row, State, }; -use bumpalo::collections::string::String; use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_region::all::{Located, Region}; - -/// Parses the given expression with 0 or more (spaces/comments/newlines) before and/or after it. -/// Returns a Located where the location is around the Expr, ignoring the spaces. -/// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or -/// SpaceAfter as appropriate. -pub fn space0_around<'a, P, S>( - parser: P, - min_indent: u16, -) -> impl Parser<'a, Located, SyntaxError<'a>> -where - S: Spaceable<'a>, - S: 'a, - S: Sized, - P: Parser<'a, Located, SyntaxError<'a>>, - P: 'a, -{ - parser::map_with_arena( - and(space0(min_indent), and(parser, space0(min_indent))), - move |arena: &'a Bump, - tuples: ( - &'a [CommentOrNewline<'a>], - (Located, &'a [CommentOrNewline<'a>]), - )| { - let (spaces_before, (loc_val, spaces_after)) = tuples; - - if spaces_before.is_empty() { - if spaces_after.is_empty() { - loc_val - } else { - arena - .alloc(loc_val.value) - .with_spaces_after(spaces_after, loc_val.region) - } - } else if spaces_after.is_empty() { - arena - .alloc(loc_val.value) - .with_spaces_before(spaces_before, loc_val.region) - } else { - let wrapped_expr = arena - .alloc(loc_val.value) - .with_spaces_after(spaces_after, loc_val.region); - - arena - .alloc(wrapped_expr.value) - .with_spaces_before(spaces_before, wrapped_expr.region) - } - }, - ) -} +use roc_region::all::Located; pub fn space0_around_ee<'a, P, S, E>( parser: P, @@ -114,76 +63,6 @@ where ) } -/// Parses the given expression with 1 or more (spaces/comments/newlines) before it, -/// and also 1 or more spaces after it. -/// Returns a Located where the location is around the Expr, ignoring the spaces. -/// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or -/// SpaceAfter as appropriate. -pub fn space1_around<'a, P, S>( - parser: P, - min_indent: u16, -) -> impl Parser<'a, Located, SyntaxError<'a>> -where - S: Spaceable<'a>, - S: 'a, - P: Parser<'a, Located, SyntaxError<'a>>, - P: 'a, -{ - parser::map_with_arena( - and(space1(min_indent), and(parser, space1(min_indent))), - |arena, (spaces_before, (loc_expr, spaces_after))| { - if spaces_before.is_empty() { - if spaces_after.is_empty() { - loc_expr - } else { - arena - .alloc(loc_expr.value) - .with_spaces_after(spaces_after, loc_expr.region) - } - } else if spaces_after.is_empty() { - arena - .alloc(loc_expr.value) - .with_spaces_before(spaces_before, loc_expr.region) - } else { - let loc_wrapped_expr = arena - .alloc(loc_expr.value) - .with_spaces_after(spaces_after, loc_expr.region); - - arena - .alloc(loc_wrapped_expr.value) - .with_spaces_before(spaces_before, loc_wrapped_expr.region) - } - }, - ) -} - -/// Parses the given expression with 0 or more (spaces/comments/newlines) before it. -/// Returns a Located where the location is around the Expr, ignoring the spaces. -/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. -pub fn space0_before<'a, P, S>( - parser: P, - min_indent: u16, -) -> impl Parser<'a, Located, SyntaxError<'a>> -where - S: Spaceable<'a>, - S: 'a, - P: Parser<'a, Located, SyntaxError<'a>>, - P: 'a, -{ - parser::map_with_arena( - and!(space0(min_indent), parser), - |arena: &'a Bump, (space_list, loc_expr): (&'a [CommentOrNewline<'a>], Located)| { - if space_list.is_empty() { - loc_expr - } else { - arena - .alloc(loc_expr.value) - .with_spaces_before(space_list, loc_expr.region) - } - }, - ) -} - pub fn space0_before_e<'a, P, S, E>( parser: P, min_indent: u16, @@ -238,92 +117,6 @@ where ) } -/// Parses the given expression with 1 or more (spaces/comments/newlines) before it. -/// Returns a Located where the location is around the Expr, ignoring the spaces. -/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. -pub fn space1_before<'a, P, S>( - parser: P, - min_indent: u16, -) -> impl Parser<'a, Located, SyntaxError<'a>> -where - S: Spaceable<'a>, - S: 'a, - P: Parser<'a, Located, SyntaxError<'a>>, - P: 'a, -{ - parser::map_with_arena( - and!(backtrackable(space1(min_indent)), parser), - |arena, (space_list, loc_expr)| { - if space_list.is_empty() { - loc_expr - } else { - arena - .alloc(loc_expr.value) - .with_spaces_before(space_list, loc_expr.region) - } - }, - ) -} - -/// Parses the given expression with 0 or more (spaces/comments/newlines) after it. -/// Returns a Located where the location is around the Expr, ignoring the spaces. -/// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found. -pub fn space0_after<'a, P, S>( - parser: P, - min_indent: u16, -) -> impl Parser<'a, Located, SyntaxError<'a>> -where - S: Spaceable<'a>, - S: 'a, - P: Parser<'a, Located, SyntaxError<'a>>, - P: 'a, -{ - parser::map_with_arena( - and!(parser, space0(min_indent)), - |arena, (loc_expr, space_list)| { - if space_list.is_empty() { - loc_expr - } else { - arena - .alloc(loc_expr.value) - .with_spaces_after(space_list, loc_expr.region) - } - }, - ) -} - -/// Parses the given expression with 1 or more (spaces/comments/newlines) after it. -/// Returns a Located where the location is around the Expr, ignoring the spaces. -/// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found. -pub fn space1_after<'a, P, S>( - parser: P, - min_indent: u16, -) -> impl Parser<'a, Located, SyntaxError<'a>> -where - S: Spaceable<'a>, - S: 'a, - P: Parser<'a, Located, SyntaxError<'a>>, - P: 'a, -{ - parser::map_with_arena( - and!(parser, space1(min_indent)), - |arena, (loc_expr, space_list)| { - if space_list.is_empty() { - loc_expr - } else { - arena - .alloc(loc_expr.value) - .with_spaces_after(space_list, loc_expr.region) - } - }, - ) -} - -/// Zero or more (spaces/comments/newlines). -pub fn space0<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>], SyntaxError<'a>> { - spaces(false, min_indent) -} - pub fn space0_e<'a, E>( min_indent: u16, space_problem: fn(BadInputError, Row, Col) -> E, @@ -332,9 +125,7 @@ pub fn space0_e<'a, E>( where E: 'a, { - spaces_help(false, min_indent, space_problem, indent_problem, |_, _| { - unreachable!("no spaces are required, so this is unreachable") - }) + spaces_help_help(min_indent, space_problem, indent_problem) } pub fn space1_e<'a, E>( @@ -359,145 +150,154 @@ where } } -/// One or more (spaces/comments/newlines). -pub fn space1<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>], SyntaxError<'a>> { - // TODO try benchmarking a short-circuit for the typical case: see if there is - // exactly one space followed by char that isn't [' ', '\n', or '#'], and - // if so, return empty slice. The case where there's exactly 1 space should - // be by far the most common. - spaces(true, min_indent) -} +pub fn spaces_till_end_of_line<'a, E: 'a>( + tab_problem: fn(Row, Col) -> E, +) -> impl Parser<'a, Option<&'a str>, E> { + move |_, mut state: State<'a>| { + let mut bytes = state.bytes; + let mut row = state.line; + let mut col = state.column; -#[derive(Debug, Clone, Copy)] -enum LineState { - Normal, - Comment, - DocComment, -} - -pub fn line_comment<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> { - then( - and!(ascii_char(b'#'), optional(ascii_string("# "))), - |arena: &'a Bump, state: State<'a>, _, (_, opt_doc)| { - if opt_doc != None { - return Err(unexpected(3, Attempting::LineComment, state)); - } - let mut length = 0; - - for &byte in state.bytes.iter() { - if byte != b'\n' { - length += 1; - } else { - break; + for c in bytes { + match c { + b' ' => { + bytes = &bytes[1..]; + col += 1; } - } + b'\n' => { + bytes = &bytes[1..]; + row += 1; + col = 0; - let comment = &state.bytes[..length]; - let state = state.advance_without_indenting(length + 1)?; - match parse_utf8(comment) { - Ok(comment_str) => Ok((MadeProgress, comment_str, state)), - Err(reason) => state.fail(arena, MadeProgress, reason), - } - }, - ) -} + state.line = row; + state.column = col; + state.bytes = bytes; -#[inline(always)] -pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, (), SyntaxError<'a>> { - move |arena: &'a Bump, state: State<'a>| { - if spaces_expected == 0 { - return Ok((NoProgress, (), state)); - } + return Ok((MadeProgress, None, state)); + } + b'\r' => { + bytes = &bytes[1..]; + } + b'\t' => { + return Err(( + MadeProgress, + tab_problem(row, col), + State { + line: row, + column: col, + ..state + }, + )) + } + b'#' => match chomp_line_comment(bytes) { + Ok(comment) => { + state.line += 1; + state.column = 0; - let mut state = state; - let mut spaces_seen: u16 = 0; + let width = 1 + comment.len(); + if let Some(b'\n') = bytes.get(width) { + state.bytes = &bytes[width + 1..]; + } else { + state.bytes = &bytes[width..]; + } - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((' ', _)) => { - spaces_seen += 1; - state = state.advance_spaces(arena, 1)?; - if spaces_seen == spaces_expected { - return Ok((MadeProgress, (), state)); + return Ok((MadeProgress, Some(comment), state)); } - } - Ok(_) => { - return Err(unexpected( - spaces_seen.into(), - Attempting::TODO, - state.clone(), - )); - } - - Err(SyntaxError::BadUtf8) => { - // If we hit an invalid UTF-8 character, bail out immediately. - let progress = Progress::progress_when(spaces_seen != 0); - return state.fail(arena, progress, SyntaxError::BadUtf8); - } - Err(_) => { - if spaces_seen == 0 { - return Err(unexpected_eof(arena, state, 0)); - } else { - return Err(unexpected( - spaces_seen.into(), - Attempting::TODO, - state.clone(), - )); - } - } + Err(_) => unreachable!("we check the first character is a #"), + }, + _ => break, } } - if spaces_seen == 0 { - Err(unexpected_eof(arena, state, 0)) + if state.column == col { + Ok((NoProgress, None, state)) } else { - Err(unexpected(spaces_seen.into(), Attempting::TODO, state)) + Ok(( + MadeProgress, + None, + State { + column: col, + bytes, + ..state + }, + )) } } } +fn chomp_line_comment(buffer: &[u8]) -> Result<&str, Progress> { + if let Some(b'#') = buffer.get(0) { + if (&buffer[1..]).starts_with(b"# ") { + // this is a doc comment, not a line comment + Err(NoProgress) + } else { + use encode_unicode::CharExt; + + let mut chomped = 1; + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if ch == '\n' { + break; + } else { + chomped += width; + } + } + + let comment_bytes = &buffer[1..chomped]; + let comment = unsafe { std::str::from_utf8_unchecked(comment_bytes) }; + + Ok(comment) + } + } else { + Err(NoProgress) + } +} + +/// Advance the parser while also indenting as appropriate. +/// This assumes we are only advancing with spaces, since they can indent. +fn advance_spaces_e( + state: State, + spaces: usize, + to_error: TE, +) -> Result +where + TE: Fn(Row, Col) -> E, +{ + match (state.column as usize).checked_add(spaces) { + Some(column_usize) => Ok(State { + bytes: &state.bytes[spaces..], + column: column_usize as u16, + ..state + }), + _ => Err((NoProgress, to_error(state.line, state.column), state)), + } +} + #[inline(always)] pub fn spaces_exactly_e<'a>(spaces_expected: u16) -> impl Parser<'a, (), parser::EExpr<'a>> { use parser::EExpr; - move |arena: &'a Bump, state: State<'a>| { + move |_, state: State<'a>| { if spaces_expected == 0 { return Ok((NoProgress, (), state)); } - let mut state = state; let mut spaces_seen: u16 = 0; - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((' ', _)) => { + for c in state.bytes { + match c { + b' ' => { spaces_seen += 1; - state = state.advance_spaces_e(arena, 1, EExpr::IndentStart)?; if spaces_seen == spaces_expected { + let state = + advance_spaces_e(state, spaces_expected as usize, EExpr::IndentStart)?; return Ok((MadeProgress, (), state)); } } - Ok(_) => { + _ => { return Err(( NoProgress, - EExpr::IndentStart(state.line, state.column), - state, - )) - } - - Err(SyntaxError::BadUtf8) => { - // If we hit an invalid UTF-8 character, bail out immediately. - let progress = Progress::progress_when(spaces_seen != 0); - return Err(( - progress, - EExpr::Space(BadInputError::BadUtf8, state.line, state.column), - state, - )); - } - Err(_) => { - return Err(( - NoProgress, - EExpr::IndentStart(state.line, state.column), + EExpr::IndentStart(state.line, state.column + spaces_seen), state, )) } @@ -506,340 +306,200 @@ pub fn spaces_exactly_e<'a>(spaces_expected: u16) -> impl Parser<'a, (), parser: Err(( NoProgress, - EExpr::IndentStart(state.line, state.column), + EExpr::IndentStart(state.line, state.column + spaces_seen), state, )) } } #[inline(always)] -fn spaces<'a>( - require_at_least_one: bool, - min_indent: u16, -) -> impl Parser<'a, &'a [CommentOrNewline<'a>], SyntaxError<'a>> { - spaces_help( - require_at_least_one, - min_indent, - bad_input_to_syntax_error, - |_, _| SyntaxError::OutdentedTooFar, - |_, _| SyntaxError::Eof(Region::zero()), - ) -} - -#[inline(always)] -fn spaces_help<'a, E>( - require_at_least_one: bool, +fn spaces_help_help<'a, E>( min_indent: u16, space_problem: fn(BadInputError, Row, Col) -> E, indent_problem: fn(Row, Col) -> E, - missing_space_problem: fn(Row, Col) -> E, ) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E> where E: 'a, { - move |arena: &'a Bump, state: State<'a>| { - let original_state = state.clone(); - let mut space_list = Vec::new_in(arena); - let mut bytes_parsed = 0; - let mut comment_line_buf = String::new_in(arena); - let mut line_state = LineState::Normal; - let mut state = state; - let mut any_newlines = false; + use SpaceState::*; - let start_row = original_state.line; - let start_col = original_state.column; + move |arena, mut state: State<'a>| { + let comments_and_newlines = Vec::new_in(arena); - let start_bytes_len = state.bytes.len(); - - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((ch, utf8_len)) => { - bytes_parsed += utf8_len; - - match line_state { - LineState::Normal => { - match ch { - ' ' => { - // Don't check indentation here; it might not be enough - // indentation yet, but maybe it will be after more spaces happen! - state = state.advance_spaces_e(arena, 1, |r, c| { - space_problem(BadInputError::LineTooLong, r, c) - })?; - } - '\r' => { - // Ignore carriage returns. - state = state.advance_spaces_e(arena, 1, |r, c| { - space_problem(BadInputError::LineTooLong, r, c) - })?; - } - '\n' => { - // don't need to check the indent here since we'll reset it - // anyway - - state = state.newline_e(arena, space_problem)?; - - // Newlines only get added to the list when they're outside comments. - space_list.push(Newline); - - any_newlines = true; - } - '\t' => { - return Err(( - MadeProgress, - space_problem( - BadInputError::HasTab, - state.line, - state.column, - ), - state, - )); - } - '#' => { - // Check indentation to make sure we were indented enough - // before this comment began. - let progress = - Progress::from_lengths(start_bytes_len, state.bytes.len()); - state = state - .check_indent_e( - arena, - min_indent, - indent_problem, - start_row, - start_col, - ) - .map_err(|(fail, _)| { - (progress, fail, original_state.clone()) - })? - .advance_without_indenting_e(1, space_problem)?; - - // We're now parsing a line comment! - line_state = LineState::Comment; - } - _ => { - return if require_at_least_one && bytes_parsed <= 1 { - // We've parsed 1 char and it was not a space, - // but we require parsing at least one space! - Err(( - NoProgress, - missing_space_problem(state.line, state.column), - state, - )) - } else { - // First make sure we were indented enough! - // - // (We only do this if we've encountered any newlines. - // Otherwise, we assume indentation is already correct. - // It's actively important for correctness that we skip - // this check if there are no newlines, because otherwise - // we would have false positives for single-line defs.) - let progress = Progress::from_lengths( - start_bytes_len, - state.bytes.len(), - ); - if any_newlines { - state = state - .check_indent_e( - arena, - min_indent, - indent_problem, - start_row, - start_col, - ) - .map_err(|(fail, _)| { - (progress, fail, original_state.clone()) - })?; - } - - Ok((progress, space_list.into_bump_slice(), state)) - }; - } - } - } - LineState::Comment => { - match ch { - ' ' => { - // If we're in a line comment, this won't affect indentation anyway. - state = state.advance_without_indenting_e(1, space_problem)?; - - if comment_line_buf.len() == 1 { - match comment_line_buf.chars().next() { - Some('#') => { - // This is a comment begining with `## ` - that is, - // a doc comment. - // - // (The space is important; otherwise, this is not - // a doc comment, but rather something like a - // big separator block, e.g. ############) - line_state = LineState::DocComment; - - // This is now the beginning of the doc comment. - comment_line_buf.clear(); - } - _ => { - comment_line_buf.push(ch); - } - } - } else { - comment_line_buf.push(ch); - } - } - '\n' => { - state = state.newline_e(arena, space_problem)?; - - match (comment_line_buf.len(), comment_line_buf.chars().next()) - { - (1, Some('#')) => { - // This is a line with `##` - that is, - // a doc comment new line. - space_list.push(DocComment("")); - comment_line_buf = String::new_in(arena); - - line_state = LineState::Normal; - } - _ => { - // This was a newline, so end this line comment. - space_list.push(LineComment( - comment_line_buf.into_bump_str(), - )); - comment_line_buf = String::new_in(arena); - - line_state = LineState::Normal; - } - } - } - '\t' => { - return Err(( - MadeProgress, - space_problem( - BadInputError::HasTab, - state.line, - state.column, - ), - state, - )); - } - nonblank => { - // Chars can have btye lengths of more than 1! - state = state.advance_without_indenting_e( - nonblank.len_utf8(), - space_problem, - )?; - - comment_line_buf.push(nonblank); - } - } - } - LineState::DocComment => { - match ch { - ' ' => { - // If we're in a doc comment, this won't affect indentation anyway. - state = state.advance_without_indenting_e(1, space_problem)?; - - comment_line_buf.push(ch); - } - '\n' => { - state = state.newline_e(arena, space_problem)?; - - // This was a newline, so end this doc comment. - space_list.push(DocComment(comment_line_buf.into_bump_str())); - comment_line_buf = String::new_in(arena); - - line_state = LineState::Normal; - } - '\t' => { - return Err(( - MadeProgress, - space_problem( - BadInputError::HasTab, - state.line, - state.column, - ), - state, - )); - } - nonblank => { - state = state - .advance_without_indenting_e(utf8_len, space_problem)?; - - comment_line_buf.push(nonblank); - } - } - } - } - } - Err(SyntaxError::BadUtf8) => { - // If we hit an invalid UTF-8 character, bail out immediately. - let progress = Progress::from_lengths(start_bytes_len, state.bytes.len()); - let row = state.line; - let col = state.column; - return state.fail( - arena, - progress, - space_problem(BadInputError::BadUtf8, row, col), - ); - } - Err(_) => { - if require_at_least_one && bytes_parsed == 0 { - return Err(( - NoProgress, - missing_space_problem(state.line, state.column), - state, - )); - } else { - let space_slice = space_list.into_bump_slice(); - - // First make sure we were indented enough! - // - // (We only do this if we've encountered any newlines. - // Otherwise, we assume indentation is already correct. - // It's actively important for correctness that we skip - // this check if there are no newlines, because otherwise - // we would have false positives for single-line defs.) - let progress = Progress::from_lengths(start_bytes_len, state.bytes.len()); - if any_newlines { - return Ok(( - progress, - space_slice, - state - .check_indent_e( - arena, - min_indent, - indent_problem, - start_row, - start_col, - ) - .map_err(|(fail, _)| (progress, fail, original_state))?, - )); - } - - return Ok((progress, space_slice, state)); - } - } - }; - } - - if require_at_least_one && original_state.bytes.len() == state.bytes.len() { - Err(( - NoProgress, - missing_space_problem(state.line, state.column), - state, - )) - } else { - // First make sure we were indented enough! - // - // (We only do this if we've encountered any newlines. - // Otherwise, we assume indentation is already correct. - // It's actively important for correctness that we skip - // this check if there are no newlines, because otherwise - // we would have false positives for single-line defs.) - let progress = Progress::from_lengths(start_bytes_len, state.bytes.len()); - if any_newlines { - state = state - .check_indent_e(arena, min_indent, indent_problem, start_row, start_col) - .map_err(|(fail, _)| (progress, fail, original_state))?; + match eat_spaces(state.bytes, state.line, state.column, comments_and_newlines) { + HasTab { row, col } => { + // there was a tab character + Err(( + MadeProgress, + space_problem(BadInputError::HasTab, row, col), + State { + line: row, + column: col, + ..state + }, + )) } + Good { + row, + col, + bytes, + comments_and_newlines, + } => { + if bytes == state.bytes { + Ok((NoProgress, &[] as &[_], state)) + } else if state.line != row { + // we parsed at least one newline - Ok((progress, space_list.into_bump_slice(), state)) + state.indent_col = col; + + if col >= min_indent { + state.line = row; + state.column = col; + state.bytes = bytes; + + Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) + } else { + Err(( + MadeProgress, + indent_problem(state.line, state.column), + state, + )) + } + } else { + state.column = col; + state.bytes = bytes; + + Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) + } + } } } } + +enum SpaceState<'a> { + Good { + row: Row, + col: Col, + bytes: &'a [u8], + comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, + }, + HasTab { + row: Row, + col: Col, + }, +} + +fn eat_spaces<'a>( + mut bytes: &'a [u8], + mut row: Row, + mut col: Col, + mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, +) -> SpaceState<'a> { + use SpaceState::*; + + for c in bytes { + match c { + b' ' => { + bytes = &bytes[1..]; + col += 1; + } + b'\n' => { + bytes = &bytes[1..]; + row += 1; + col = 0; + comments_and_newlines.push(CommentOrNewline::Newline); + } + b'\r' => { + bytes = &bytes[1..]; + } + b'\t' => { + return HasTab { row, col }; + } + b'#' => { + return eat_line_comment(&bytes[1..], row, col + 1, comments_and_newlines); + } + _ => break, + } + } + + Good { + row, + col, + bytes, + comments_and_newlines, + } +} + +fn eat_line_comment<'a>( + mut bytes: &'a [u8], + row: Row, + mut col: Col, + mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, +) -> SpaceState<'a> { + use SpaceState::*; + + let is_doc_comment = if let Some(b'#') = bytes.get(0) { + match bytes.get(1) { + Some(b' ') => { + bytes = &bytes[2..]; + col += 2; + + true + } + Some(b'\n') => { + // consume the second # and the \n + bytes = &bytes[2..]; + + comments_and_newlines.push(CommentOrNewline::DocComment("")); + return eat_spaces(bytes, row + 1, 0, comments_and_newlines); + } + None => { + // consume the second # + col += 1; + bytes = &bytes[1..]; + + return Good { + row, + col, + bytes, + comments_and_newlines, + }; + } + + _ => false, + } + } else { + false + }; + + let initial = bytes; + let initial_col = col; + + for c in bytes { + match c { + b'\t' => return HasTab { row, col }, + b'\n' => { + let delta = (col - initial_col) as usize; + let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; + + if is_doc_comment { + comments_and_newlines.push(CommentOrNewline::DocComment(comment)); + } else { + comments_and_newlines.push(CommentOrNewline::LineComment(comment)); + } + return eat_spaces(&bytes[1..], row + 1, 0, comments_and_newlines); + } + _ => { + bytes = &bytes[1..]; + col += 1; + } + } + } + + Good { + row, + col, + bytes, + comments_and_newlines, + } +} diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 18e70a736d..88475f39e3 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -1,15 +1,14 @@ use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation}; use crate::blankspace::{ - line_comment, space0_after_e, space0_around_ee, space0_before_e, space0_e, space1_e, - spaces_exactly_e, + space0_after_e, space0_around_ee, space0_before_e, space0_e, space1_e, spaces_exactly_e, }; -use crate::ident::{ident, lowercase_ident, Ident}; +use crate::ident::{lowercase_ident, parse_ident_help, Ident}; use crate::keyword; use crate::parser::{ - self, allocated, and_then_with_indent_level, ascii_char, backtrackable, map, newline_char, - optional, sep_by1, sep_by1_e, specialize, specialize_ref, then, trailing_sep_by0, word1, word2, - EExpr, EInParens, ELambda, EPattern, ERecord, EString, Either, If, List, Number, ParseResult, - Parser, State, SyntaxError, Type, When, + self, allocated, and_then_with_indent_level, backtrackable, map, optional, sep_by1, sep_by1_e, + specialize, specialize_ref, then, trailing_sep_by0, word1, word2, EExpr, EInParens, ELambda, + EPattern, ERecord, EString, Either, If, List, Number, ParseResult, Parser, State, SyntaxError, + Type, When, }; use crate::pattern::loc_closure_param; use crate::type_annotation; @@ -20,6 +19,25 @@ use roc_region::all::{Located, Region}; use crate::parser::Progress::{self, *}; +pub fn test_parse_expr<'a>( + min_indent: u16, + arena: &'a bumpalo::Bump, + state: State<'a>, +) -> Result>, EExpr<'a>> { + let parser = space0_before_e( + loc!(|a, s| parse_expr_help(min_indent, a, s)), + min_indent, + EExpr::Space, + EExpr::IndentStart, + ); + + match parser.parse(arena, state) { + Ok((_, expression, _)) => Ok(expression), + Err((_, fail, _)) => Err(fail), + } +} + +// public for testing purposes pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> { // Recursive parsers must not directly invoke functions which return (impl Parser), // as this causes rustc to stack overflow. Thus, parse_expr must be a @@ -30,6 +48,10 @@ pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> { ) } +pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { + move |arena, state: State<'a>| parse_expr_help(min_indent, arena, state) +} + fn loc_expr_in_parens_help<'a>( min_indent: u16, ) -> impl Parser<'a, Located>, EInParens<'a>> { @@ -155,9 +177,9 @@ fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, &'a str>, EExpr<'a } fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> { - specialize( - |_, r, c| EExpr::Access(r, c), - skip_first!(ascii_char(b'.'), lowercase_ident()), + skip_first!( + word1(b'.', EExpr::Access), + specialize(|_, r, c| EExpr::Access(r, c), lowercase_ident()) ) } @@ -487,7 +509,7 @@ fn parse_expr_help<'a>( ] .parse(arena, state)?; - let initial = state.clone(); + let initial = state; match space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state) { Err((_, _, state)) => Ok((MadeProgress, loc_expr1.value, state)), @@ -717,47 +739,6 @@ fn assigned_expr_field_to_pattern_help<'a>( }) } -/// A def beginning with a parenthetical pattern, for example: -/// -/// (UserId userId) = ... -/// -/// Note: Parenthetical patterns are a shorthand convenience, and may not have type annotations. -/// It would be too weird to parse; imagine `(UserId userId) : ...` above `(UserId userId) = ...` -/// !!!! THIS IS NOT USED !!!! -// fn loc_parenthetical_def<'a>(min_indent: u16) -> impl Parser<'a, Located>> { -// move |arena, state| { -// let (loc_tuple, state) = loc!(and!( -// space0_after( -// between!( -// ascii_char(b'('), -// space0_around(loc_pattern(min_indent), min_indent), -// ascii_char(b')') -// ), -// min_indent, -// ), -// equals_with_indent() -// )) -// .parse(arena, state)?; - -// let region = loc_tuple.region; -// let (loc_first_pattern, equals_sign_indent) = loc_tuple.value; - -// // Continue parsing the expression as a Def. -// let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?; -// let (value, state) = parse_def_expr( -// region.start_col, -// min_indent, -// equals_sign_indent, -// arena, -// state, -// loc_first_pattern, -// spaces_after_equals, -// )?; - -// Ok((Located { value, region }, state)) -// } -// } - fn parse_defs_help<'a>( min_indent: u16, ) -> impl Parser<'a, Vec<'a, &'a Located>>, EExpr<'a>> { @@ -794,7 +775,7 @@ pub fn def<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>, SyntaxError<'a>> { specialize(|e, _, _| SyntaxError::Expr(e), def_help(min_indent)) } -fn def_help<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>, EExpr<'a>> { +pub fn def_help<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>, EExpr<'a>> { let indented_more = min_indent + 1; enum DefKind { @@ -834,7 +815,7 @@ fn def_help<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>, EExpr<'a>> { // see if there is a definition (assuming the preceding characters were a type // annotation let (_, opt_rest, state) = optional(and!( - spaces_then_comment_or_newline_help(), + spaces_till_end_of_line(), body_at_indent_help(min_indent) )) .parse(arena, state)?; @@ -889,20 +870,10 @@ fn pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Located>, EE ) } -fn spaces_then_comment_or_newline_help<'a>() -> impl Parser<'a, Option<&'a str>, EExpr<'a>> { - specialize_ref( - EExpr::Syntax, - skip_first!( - zero_or_more!(ascii_char(b' ')), - map!( - either!(newline_char(), line_comment()), - |either_comment_or_newline| match either_comment_or_newline { - Either::First(_) => None, - Either::Second(comment) => Some(comment), - } - ) - ), - ) +fn spaces_till_end_of_line<'a>() -> impl Parser<'a, Option<&'a str>, EExpr<'a>> { + crate::blankspace::spaces_till_end_of_line(|r, c| { + EExpr::Space(parser::BadInputError::HasTab, r, c) + }) } type Body<'a> = (Located>, Located>); @@ -1193,7 +1164,7 @@ fn parse_def_signature_help<'a>( // Indented more beyond the original indent. let indented_more = original_indent + 1; - and!( + let parser1 = { // Parse the first annotation. It doesn't need any spaces // around it parsed, because both the subsquent defs and the // final body will have space1_before on them. @@ -1205,23 +1176,28 @@ fn parse_def_signature_help<'a>( specialize(EExpr::Type, type_annotation::located_help(indented_more)), min_indent, EExpr::Space, - EExpr::IndentAnnotation + EExpr::IndentAnnotation, ), // The first annotation may be immediately (spaces_then_comment_or_newline()) // followed by a body at the exact same indent_level // leading to an AnnotatedBody in this case - |_progress, type_ann, indent_level| map( - optional(and!( - backtrackable(spaces_then_comment_or_newline_help()), - body_at_indent_help(indent_level) - )), - move |opt_body| (type_ann.clone(), opt_body) - ) - ), + |_progress, type_ann, indent_level| { + map( + optional(and!( + backtrackable(spaces_till_end_of_line()), + body_at_indent_help(indent_level) + )), + move |opt_body| (type_ann.clone(), opt_body), + ) + }, + ) + }; + + let parser2 = { and!( // Optionally parse additional defs. zero_or_more!(backtrackable(allocated(space0_before_e( - loc!(specialize_ref(EExpr::Syntax, def(original_indent))), + loc!(def_help(original_indent)), original_indent, EExpr::Space, EExpr::IndentStart, @@ -1229,15 +1205,22 @@ fn parse_def_signature_help<'a>( // Parse the final expression that will be returned. // It should be indented the same amount as the original. space0_before_e( - loc!(|arena, state| parse_expr_help(original_indent, arena, state)), + loc!(one_of![ + |arena, state| parse_expr_help(original_indent, arena, state), + |_, state: State<'a>| Err(( + MadeProgress, + EExpr::DefMissingFinalExpr(state.line, state.column), + state + )), + ]), original_indent, EExpr::Space, EExpr::IndentEnd, ) ) - ) - .parse(arena, state) - .map( + }; + + and!(parser1, parser2).parse(arena, state).map( move |(progress, ((loc_first_annotation, opt_body), (mut defs, loc_ret)), state)| { let loc_first_def: Located> = match opt_body { None => { @@ -1713,7 +1696,7 @@ fn unary_negate_function_arg_help<'a>( fn loc_function_args_help<'a>( min_indent: u16, ) -> impl Parser<'a, Vec<'a, Located>>, EExpr<'a>> { - one_or_more_e!( + one_or_more!( move |arena: &'a Bump, s| { map!( and!( @@ -1966,11 +1949,11 @@ fn ident_then_args<'a>( } fn ident_without_apply_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { - specialize_ref( - EExpr::Syntax, - then(loc!(ident()), move |arena, state, progress, loc_ident| { + then( + loc!(parse_ident_help), + move |arena, state, progress, loc_ident| { Ok((progress, ident_to_expr(arena, loc_ident.value), state)) - }), + }, ) } @@ -2079,7 +2062,7 @@ fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>> move |arena, state| { let (_, (parsed_elems, final_comments), state) = collection_trailing_sep_e!( word1(b'[', List::Open), - specialize_ref(List::Syntax, loc!(expr(min_indent))), + specialize_ref(List::Expr, loc!(expr_help(min_indent))), word1(b',', List::End), word1(b']', List::End), min_indent, @@ -2127,7 +2110,7 @@ fn record_field_help<'a>( word1(b'?', ERecord::QuestionMark) ), space0_before_e( - specialize_ref(ERecord::Syntax, loc!(expr(min_indent))), + specialize_ref(ERecord::Expr, loc!(expr_help(min_indent))), min_indent, ERecord::Space, ERecord::IndentEnd, @@ -2162,7 +2145,7 @@ fn record_field_help<'a>( fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> { specialize( |_, r, c| ERecord::Updateable(r, c), - map_with_arena!(ident(), ident_to_expr), + map_with_arena!(parse_ident_help, ident_to_expr), ) } diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index d90501564b..5442509877 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -1,12 +1,11 @@ -use crate::blankspace::space0; +use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation}; +use crate::blankspace::space0_e; use crate::ident::lowercase_ident; -use crate::module::package_name; -use crate::parser::{ascii_char, optional, Either, Parser, Progress::*, State, SyntaxError}; -use crate::string_literal; -use crate::{ - ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation}, - parser::specialize, +use crate::parser::Progress::{self, *}; +use crate::parser::{ + specialize, word1, EPackageEntry, EPackageName, EPackageOrPath, Parser, State, }; +use crate::string_literal; use bumpalo::collections::Vec; use inlinable_string::InlinableString; use roc_region::all::Loc; @@ -242,18 +241,32 @@ impl<'a> Spaceable<'a> for PackageEntry<'a> { } } -pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>> { +pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a>> { move |arena, state| { // You may optionally have a package shorthand, // e.g. "uc" in `uc: roc/unicode 1.0.0` // // (Indirect dependencies don't have a shorthand.) - let (_, opt_shorthand, state) = optional(and!( - skip_second!(lowercase_ident(), ascii_char(b':')), - space0(1) + let min_indent = 1; + + let (_, opt_shorthand, state) = maybe!(and!( + skip_second!( + specialize(|_, r, c| EPackageEntry::Shorthand(r, c), lowercase_ident()), + word1(b':', EPackageEntry::Colon) + ), + space0_e( + min_indent, + EPackageEntry::Space, + EPackageEntry::IndentPackageOrPath + ) + )) + .parse(arena, state)?; + + let (_, package_or_path, state) = loc!(specialize( + EPackageEntry::BadPackageOrPath, + package_or_path() )) .parse(arena, state)?; - let (_, package_or_path, state) = loc!(package_or_path()).parse(arena, state)?; let entry = match opt_shorthand { Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry { @@ -272,27 +285,117 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>> } } -pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, SyntaxError<'a>> { - map!( - either!( - specialize( - |e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)), - string_literal::parse() - ), - and!( - package_name(), - skip_first!(one_or_more!(ascii_char(b' ')), package_version()) - ) +pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, EPackageOrPath<'a>> { + one_of![ + map!( + specialize(EPackageOrPath::BadPath, string_literal::parse()), + PackageOrPath::Path ), - |answer| { - match answer { - Either::First(str_literal) => PackageOrPath::Path(str_literal), - Either::Second((name, version)) => PackageOrPath::Package(name, version), - } - } - ) + map!( + and!( + specialize(EPackageOrPath::BadPackage, package_name()), + skip_first!(skip_spaces(), package_version()) + ), + |(name, version)| { PackageOrPath::Package(name, version) } + ) + ] } -fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> { +fn skip_spaces<'a, T>() -> impl Parser<'a, (), T> +where + T: 'a, +{ + |_, mut state: State<'a>| { + let mut chomped = 0; + let mut it = state.bytes.iter(); + + while let Some(b' ') = it.next() { + chomped += 1; + } + + if chomped == 0 { + Ok((NoProgress, (), state)) + } else { + state.column += chomped; + state.bytes = it.as_slice(); + + Ok((MadeProgress, (), state)) + } + } +} + +fn package_version<'a, T>() -> impl Parser<'a, Version<'a>, T> +where + T: 'a, +{ move |_, _| todo!("TODO parse package version") } + +#[inline(always)] +pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName> { + use encode_unicode::CharExt; + // e.g. rtfeldman/blah + // + // Package names and accounts can be capitalized and can contain dashes. + // They cannot contain underscores or other special characters. + // They must be ASCII. + + |_, mut state: State<'a>| match chomp_package_part(state.bytes) { + Err(progress) => Err(( + progress, + EPackageName::Account(state.line, state.column), + state, + )), + Ok(account) => { + let mut chomped = account.len(); + if let Ok(('/', width)) = char::from_utf8_slice_start(&state.bytes[chomped..]) { + chomped += width; + match chomp_package_part(&state.bytes[chomped..]) { + Err(progress) => Err(( + progress, + EPackageName::Pkg(state.line, state.column + chomped as u16), + state, + )), + Ok(pkg) => { + chomped += pkg.len(); + + state.column += chomped as u16; + state.bytes = &state.bytes[chomped..]; + + let value = PackageName { account, pkg }; + Ok((MadeProgress, value, state)) + } + } + } else { + Err(( + MadeProgress, + EPackageName::MissingSlash(state.line, state.column + chomped as u16), + state, + )) + } + } + } +} + +fn chomp_package_part(buffer: &[u8]) -> Result<&str, Progress> { + use encode_unicode::CharExt; + + let mut chomped = 0; + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if ch == '-' || ch.is_ascii_alphanumeric() { + chomped += width; + } else { + // we're done + break; + } + } + + if chomped == 0 { + Err(Progress::NoProgress) + } else { + let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + + Ok(name) + } +} diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index bb3143b53d..21c55123f1 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -1,14 +1,7 @@ -use crate::ast::Attempting; -use crate::keyword; use crate::parser::Progress::{self, *}; -use crate::parser::{ - peek_utf8_char, unexpected, BadInputError, Col, EExpr, ParseResult, Parser, Row, State, - SyntaxError, -}; -use bumpalo::collections::string::String; +use crate::parser::{BadInputError, Col, EExpr, ParseResult, Parser, Row, State}; use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_region::all::Region; /// The parser accepts all of these in any position where any one of them could /// appear. This way, canonicalization can give more helpful error messages like @@ -61,82 +54,43 @@ impl<'a> Ident<'a> { } } -pub fn ident<'a>() -> impl Parser<'a, Ident<'a>, SyntaxError<'a>> { - crate::parser::specialize(|e, _, _| SyntaxError::Expr(e), parse_ident_help) -} - -pub fn global_tag_or_ident<'a, F>(pred: F) -> impl Parser<'a, &'a str, SyntaxError<'a>> -where - F: Fn(char) -> bool, -{ - move |arena, mut state: State<'a>| { - // pred will determine if this is a tag or ident (based on capitalization) - let (first_letter, bytes_parsed) = match peek_utf8_char(&state) { - Ok((first_letter, bytes_parsed)) => { - if !pred(first_letter) { - return Err(unexpected(0, Attempting::RecordFieldLabel, state)); - } - - (first_letter, bytes_parsed) - } - Err(reason) => return state.fail(arena, NoProgress, reason), - }; - - let mut buf = String::with_capacity_in(1, arena); - - buf.push(first_letter); - - state = state.advance_without_indenting(bytes_parsed)?; - - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((ch, bytes_parsed)) => { - // After the first character, only these are allowed: - // - // * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers - // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() - // * A ':' indicating the end of the field - if ch.is_alphabetic() || ch.is_ascii_digit() { - buf.push(ch); - - state = state.advance_without_indenting(bytes_parsed)?; - } else { - // This is the end of the field. We're done! - break; - } - } - Err(reason) => return state.fail(arena, MadeProgress, reason), - }; - } - - Ok((MadeProgress, buf.into_bump_str(), state)) - } -} - /// This could be: /// /// * A record field, e.g. "email" in `.email` or in `email:` /// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->` -pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> { +pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { + move |_, state: State<'a>| match chomp_lowercase_part(state.bytes) { + Err(progress) => Err((progress, (), state)), + Ok(ident) => { + if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) { + Err((NoProgress, (), state)) + } else { + let width = ident.len(); + match state.advance_without_indenting_ee(width, |_, _| ()) { + Ok(state) => Ok((MadeProgress, ident, state)), + Err(bad) => Err(bad), + } + } + } + } +} + +pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { move |arena, state: State<'a>| { - let (progress, ident, state) = - global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?; - - // to parse a valid ident, progress must be made - debug_assert_eq!(progress, MadeProgress); - - if (ident == keyword::IF) - || (ident == keyword::THEN) - || (ident == keyword::ELSE) - || (ident == keyword::WHEN) - || (ident == keyword::IS) - || (ident == keyword::AS) - { - // TODO Calculate the correct region based on state - let region = Region::zero(); - Err((MadeProgress, SyntaxError::ReservedKeyword(region), state)) + if state.bytes.starts_with(b"@") { + match chomp_private_tag(state.bytes, state.line, state.column) { + Err(BadIdent::Start(_, _)) => Err((NoProgress, (), state)), + Err(_) => Err((MadeProgress, (), state)), + Ok(ident) => { + let width = ident.len(); + match state.advance_without_indenting_ee(width, |_, _| ()) { + Ok(state) => Ok((MadeProgress, ident, state)), + Err(bad) => Err(bad), + } + } + } } else { - Ok((MadeProgress, ident, state)) + uppercase_ident().parse(arena, state) } } } @@ -146,30 +100,34 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> { /// * A module name /// * A type name /// * A global tag -pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> { - global_tag_or_ident(|first_char| first_char.is_uppercase()) -} - -pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> { - global_tag_or_ident(|first_char| first_char.is_alphabetic()) -} - -pub fn join_module_parts<'a>(arena: &'a Bump, module_parts: &[&str]) -> &'a str { - let capacity = module_parts.len() * 3; // Module parts tend to be 3+ characters. - let mut buf = String::with_capacity_in(capacity, arena); - let mut any_parts_added = false; - - for part in module_parts { - if any_parts_added { - buf.push('.'); - } else { - any_parts_added = true; +pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { + move |_, state: State<'a>| match chomp_uppercase_part(state.bytes) { + Err(progress) => Err((progress, (), state)), + Ok(ident) => { + let width = ident.len(); + match state.advance_without_indenting_ee(width, |_, _| ()) { + Ok(state) => Ok((MadeProgress, ident, state)), + Err(bad) => Err(bad), + } } - - buf.push_str(part); } +} - buf.into_bump_str() +pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> { + move |_, state: State<'a>| match chomp_part(|c| c.is_alphabetic(), state.bytes) { + Err(progress) => Err((progress, (), state)), + Ok(ident) => { + if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) { + Err((MadeProgress, (), state)) + } else { + let width = ident.len(); + match state.advance_without_indenting_ee(width, |_, _| ()) { + Ok(state) => Ok((MadeProgress, ident, state)), + Err(bad) => Err(bad), + } + } + } + } } macro_rules! advance_state { @@ -184,10 +142,10 @@ pub fn parse_ident_help<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Ident<'a>, EExpr<'a>> { - let initial = state.clone(); + let initial = state; match parse_ident_help_help(arena, state) { - Ok((progress, (ident, _), state)) => { + Ok((progress, ident, state)) => { if let Ident::Access { module_name, parts } = ident { if module_name.is_empty() { if let Some(first) = parts.first() { @@ -212,7 +170,7 @@ pub fn parse_ident_help<'a>( Err((MadeProgress, fail, state)) => match fail { BadIdent::Start(r, c) => Err((NoProgress, EExpr::Start(r, c), state)), BadIdent::Space(e, r, c) => Err((NoProgress, EExpr::Space(e, r, c), state)), - _ => malformed_identifier(initial.bytes, fail, arena, state), + _ => malformed_identifier(initial.bytes, fail, state), }, } } @@ -220,294 +178,367 @@ pub fn parse_ident_help<'a>( fn malformed_identifier<'a>( initial_bytes: &'a [u8], problem: BadIdent, - _arena: &'a Bump, mut state: State<'a>, ) -> ParseResult<'a, Ident<'a>, EExpr<'a>> { - // skip forward to the next non-identifier character - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((ch, bytes_parsed)) => { - // We can't use ch.is_alphanumeric() here because that passes for - // things that are "numeric" but not ASCII digits, like `¾` - if ch == '.' || ch == '_' || ch.is_alphabetic() || ch.is_ascii_digit() { - state = state.advance_without_indenting_ee(bytes_parsed, |r, c| { - EExpr::Space(crate::parser::BadInputError::LineTooLong, r, c) - })?; - continue; - } else { - break; - } - } - Err(_reason) => { - break; - } + let chomped = chomp_malformed(state.bytes); + let delta = initial_bytes.len() - state.bytes.len(); + let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) }; + + state = state.advance_without_indenting_ee(chomped, |r, c| { + EExpr::Space(crate::parser::BadInputError::LineTooLong, r, c) + })?; + + Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state)) +} + +/// skip forward to the next non-identifier character +pub fn chomp_malformed(bytes: &[u8]) -> usize { + use encode_unicode::CharExt; + let mut chomped = 0; + while let Ok((ch, width)) = char::from_utf8_slice_start(&bytes[chomped..]) { + // We can't use ch.is_alphanumeric() here because that passes for + // things that are "numeric" but not ASCII digits, like `¾` + if ch == '.' || ch == '_' || ch.is_alphabetic() || ch.is_ascii_digit() { + chomped += width; + continue; + } else { + break; } } - let parsed = &initial_bytes[..(initial_bytes.len() - state.bytes.len())]; - - let parsed_str = unsafe { std::str::from_utf8_unchecked(parsed) }; - - Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state)) + chomped } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BadIdent { Start(Row, Col), Space(BadInputError, Row, Col), + Underscore(Row, Col), QualifiedTag(Row, Col), - PrivateTagNotUppercase(Row, Col), - PartStartsWithNumber(Row, Col), WeirdAccessor(Row, Col), - PrivateTagFieldAccess(Row, Col), - WeirdDotAccess(Row, Col), WeirdDotQualified(Row, Col), - DoubleDot(Row, Col), StrayDot(Row, Col), + BadPrivateTag(Row, Col), } -/// Parse an identifier into a string. -/// -/// This is separate from the `ident` Parser because string interpolation -/// wants to use it this way. -pub fn parse_ident_help_help<'a>( +fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> { + chomp_part(|c: char| c.is_lowercase(), buffer) +} + +fn chomp_uppercase_part(buffer: &[u8]) -> Result<&str, Progress> { + chomp_part(|c: char| c.is_uppercase(), buffer) +} + +#[inline(always)] +fn chomp_part(leading_is_good: F, buffer: &[u8]) -> Result<&str, Progress> +where + F: Fn(char) -> bool, +{ + use encode_unicode::CharExt; + + let mut chomped = 0; + + if let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if leading_is_good(ch) { + chomped += width; + } else { + return Err(NoProgress); + } + } + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if ch.is_alphabetic() || ch.is_ascii_digit() { + chomped += width; + } else { + // we're done + break; + } + } + + if chomped == 0 { + Err(NoProgress) + } else { + let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + + Ok(name) + } +} + +/// a `.foo` accessor function +fn chomp_accessor(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> { + // assumes the leading `.` has been chomped already + use encode_unicode::CharExt; + + match chomp_lowercase_part(buffer) { + Ok(name) => { + let chomped = name.len(); + + if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) { + Err(BadIdent::WeirdAccessor(row, col)) + } else { + Ok(name) + } + } + Err(_) => { + // we've already made progress with the initial `.` + Err(BadIdent::StrayDot(row, col + 1)) + } + } +} + +/// a `@Token` private tag +fn chomp_private_tag(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> { + // assumes the leading `@` has NOT been chomped already + debug_assert_eq!(buffer.get(0), Some(&b'@')); + use encode_unicode::CharExt; + + match chomp_uppercase_part(&buffer[1..]) { + Ok(name) => { + let width = 1 + name.len(); + + if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[width..]) { + Err(BadIdent::BadPrivateTag(row, col + width as u16)) + } else { + let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) }; + Ok(value) + } + } + Err(_) => Err(BadIdent::BadPrivateTag(row, col + 1)), + } +} + +fn chomp_identifier_chain<'a>( arena: &'a Bump, - mut state: State<'a>, -) -> ParseResult<'a, (Ident<'a>, Option), BadIdent> { - let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) - let mut capitalized_parts: Vec<&'a str> = Vec::new_in(arena); - let mut noncapitalized_parts: Vec<&'a str> = Vec::new_in(arena); - let mut is_capitalized; - let is_accessor_fn; - let mut is_private_tag = false; + buffer: &'a [u8], + row: Row, + col: Col, +) -> Result<(u16, Ident<'a>), (u16, BadIdent)> { + use encode_unicode::CharExt; - // Identifiers and accessor functions must start with either a letter or a dot. - // If this starts with neither, it must be something else! - match peek_utf8_char(&state) { - Ok((first_ch, bytes_parsed)) => { - if first_ch.is_alphabetic() { - part_buf.push(first_ch); + let first_is_uppercase; + let mut chomped = 0; - is_capitalized = first_ch.is_uppercase(); - is_accessor_fn = false; + match char::from_utf8_slice_start(&buffer[chomped..]) { + Ok((ch, width)) => match ch { + '.' => match chomp_accessor(&buffer[1..], row, col) { + Ok(accessor) => { + let bytes_parsed = 1 + accessor.len(); - state = advance_state!(state, bytes_parsed)?; - } else if first_ch == '.' { - is_capitalized = false; - is_accessor_fn = true; - - state = advance_state!(state, bytes_parsed)?; - } else if first_ch == '@' { - state = advance_state!(state, bytes_parsed)?; - - // '@' must always be followed by a capital letter! - match peek_utf8_char(&state) { - Ok((next_ch, next_bytes_parsed)) => { - if next_ch.is_uppercase() { - state = advance_state!(state, next_bytes_parsed)?; - - part_buf.push('@'); - part_buf.push(next_ch); - - is_private_tag = true; - is_capitalized = true; - is_accessor_fn = false; - } else { - return Err(( - MadeProgress, - BadIdent::PrivateTagNotUppercase(state.line, state.column), - state, - )); - } - } - Err(_reason) => { - return Err(( - MadeProgress, - BadIdent::PrivateTagNotUppercase(state.line, state.column), - state, - )); - } + return Ok((bytes_parsed as u16, Ident::AccessorFunction(accessor))); } - } else { - return Err((NoProgress, BadIdent::Start(state.line, state.column), state)); + Err(fail) => return Err((1, fail)), + }, + '@' => match chomp_private_tag(buffer, row, col) { + Ok(tagname) => { + let bytes_parsed = tagname.len(); + + return Ok((bytes_parsed as u16, Ident::PrivateTag(tagname))); + } + Err(fail) => return Err((1, fail)), + }, + c if c.is_alphabetic() => { + // fall through + chomped += width; + first_is_uppercase = c.is_uppercase(); } - } - Err(_reason) => { - return Err((NoProgress, BadIdent::Start(state.line, state.column), state)); + _ => { + return Err((0, BadIdent::Start(row, col))); + } + }, + Err(_) => return Err((0, BadIdent::Start(row, col))), + } + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if ch.is_alphabetic() || ch.is_ascii_digit() { + chomped += width; + } else { + // we're done + break; } } - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((ch, bytes_parsed)) => { - // After the first character, only these are allowed: - // - // * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers - // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() - // * A dot ('.') - if ch.is_alphabetic() { - if part_buf.is_empty() { - // Capitalization is determined by the first character in the part. - is_capitalized = ch.is_uppercase(); - } - - part_buf.push(ch); - } else if ch.is_ascii_digit() { - // Parts may not start with numbers! - if part_buf.is_empty() { - return Err(( - MadeProgress, - BadIdent::PartStartsWithNumber(state.line, state.column), - state, - )); - } - - part_buf.push(ch); - } else if ch == '.' { - // There are two posssible errors here: - // - // 1. Having two consecutive dots is an error. - // 2. Having capitalized parts after noncapitalized (e.g. `foo.Bar`) is an error. - if part_buf.is_empty() { - return Err(( - MadeProgress, - BadIdent::DoubleDot(state.line, state.column), - state, - )); - } - - if is_capitalized && !noncapitalized_parts.is_empty() { - return Err(( - MadeProgress, - BadIdent::WeirdDotQualified(state.line, state.column), - state, - )); - } - - if is_capitalized { - capitalized_parts.push(part_buf.into_bump_str()); - } else { - noncapitalized_parts.push(part_buf.into_bump_str()); - } - - // Now that we've recorded the contents of the current buffer, reset it. - part_buf = String::new_in(arena); - } else if ch == '_' { - // we don't allow underscores in the middle of an identifier - // but still parse them (and generate a malformed identifier) - // to give good error messages for this case - state = advance_state!(state, bytes_parsed)?; - return Err(( - MadeProgress, - BadIdent::Underscore(state.line, state.column), - state, - )); - } else { - // This must be the end of the identifier. We're done! - - break; + if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) { + let module_name = if first_is_uppercase { + match chomp_module_chain(&buffer[chomped..]) { + Ok(width) => { + chomped += width as usize; + unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) } } - - state = advance_state!(state, bytes_parsed)?; - } - Err(_reason) => { - // - return Err(( - MadeProgress, - BadIdent::Start(state.line, state.column), - state, - )); - } - } - } - - if part_buf.is_empty() { - // We probably had a trailing dot, e.g. `Foo.bar.` - this is malformed! - // - // This condition might also occur if we encounter a malformed accessor like `.|` - // - // If we made it this far and don't have a next_char, then necessarily - // we have consumed a '.' char previously. - let fail = if noncapitalized_parts.is_empty() { - if capitalized_parts.is_empty() { - BadIdent::StrayDot(state.line, state.column) - } else { - BadIdent::WeirdDotQualified(state.line, state.column) + Err(MadeProgress) => todo!(), + Err(NoProgress) => unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }, } } else { - BadIdent::WeirdDotAccess(state.line, state.column) + "" }; - return Err((MadeProgress, fail, state)); - } + let mut parts = Vec::with_capacity_in(4, arena); - // Record the final parts. - if is_capitalized { - capitalized_parts.push(part_buf.into_bump_str()); - } else { - noncapitalized_parts.push(part_buf.into_bump_str()); - } - - let answer = if is_accessor_fn { - // Handle accessor functions first because they have the strictest requirements. - // Accessor functions may have exactly 1 noncapitalized part, and no capitalzed parts. - if capitalized_parts.is_empty() && noncapitalized_parts.len() == 1 && !is_private_tag { - let value = noncapitalized_parts.iter().next().unwrap(); - - Ident::AccessorFunction(value) - } else { - return Err(( - MadeProgress, - BadIdent::WeirdAccessor(state.line, state.column), - state, - )); + if !first_is_uppercase { + let first_part = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + parts.push(first_part); } - } else if noncapitalized_parts.is_empty() { - // We have capitalized parts only, so this must be a tag. - match capitalized_parts.first() { - Some(value) => { - if capitalized_parts.len() == 1 { - if is_private_tag { - Ident::PrivateTag(value) - } else { - Ident::GlobalTag(value) + + match chomp_access_chain(&buffer[chomped..], &mut parts) { + Ok(width) => { + chomped += width as usize; + + let ident = Ident::Access { + module_name, + parts: parts.into_bump_slice(), + }; + + Ok((chomped as u16, ident)) + } + Err(0) if !module_name.is_empty() => Err(( + chomped as u16, + BadIdent::QualifiedTag(row, chomped as u16 + col), + )), + Err(1) if parts.is_empty() => Err(( + chomped as u16 + 1, + BadIdent::WeirdDotQualified(row, chomped as u16 + col + 1), + )), + Err(width) => Err(( + chomped as u16 + width, + BadIdent::WeirdDotAccess(row, chomped as u16 + col + width), + )), + } + } else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) { + // we don't allow underscores in the middle of an identifier + // but still parse them (and generate a malformed identifier) + // to give good error messages for this case + Err(( + chomped as u16 + 1, + BadIdent::Underscore(row, col + chomped as u16 + 1), + )) + } else if first_is_uppercase { + // just one segment, starting with an uppercase letter; that's a global tag + let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + Ok((chomped as u16, Ident::GlobalTag(value))) + } else { + // just one segment, starting with a lowercase letter; that's a normal identifier + let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + let ident = Ident::Access { + module_name: "", + parts: arena.alloc([value]), + }; + Ok((chomped as u16, ident)) + } +} + +fn chomp_module_chain(buffer: &[u8]) -> Result { + let mut chomped = 0; + + while let Some(b'.') = buffer.get(chomped) { + match &buffer.get(chomped + 1..) { + Some(slice) => match chomp_uppercase_part(slice) { + Ok(name) => { + chomped += name.len() + 1; + } + Err(MadeProgress) => return Err(MadeProgress), + Err(NoProgress) => break, + }, + None => return Err(MadeProgress), + } + } + + if chomped == 0 { + Err(NoProgress) + } else { + Ok(chomped as u16) + } +} + +pub fn concrete_type<'a>() -> impl Parser<'a, (&'a str, &'a str), ()> { + move |_, state: State<'a>| match chomp_concrete_type(state.bytes) { + Err(progress) => Err((progress, (), state)), + Ok((module_name, type_name, width)) => { + match state.advance_without_indenting_ee(width, |_, _| ()) { + Ok(state) => Ok((MadeProgress, (module_name, type_name), state)), + Err(bad) => Err(bad), + } + } + } +} + +// parse a type name like `Result` or `Result.Result` +fn chomp_concrete_type(buffer: &[u8]) -> Result<(&str, &str, usize), Progress> { + let first = crate::ident::chomp_uppercase_part(buffer)?; + + if let Some(b'.') = buffer.get(first.len()) { + match crate::ident::chomp_module_chain(&buffer[first.len()..]) { + Err(_) => Err(MadeProgress), + Ok(rest) => { + let width = first.len() + rest as usize; + + // we must explicitly check here for a trailing `.` + if let Some(b'.') = buffer.get(width) { + return Err(MadeProgress); + } + + let slice = &buffer[..width]; + + match slice.iter().rev().position(|c| *c == b'.') { + None => Ok(("", first, first.len())), + Some(rev_index) => { + let index = slice.len() - rev_index; + let module_name = + unsafe { std::str::from_utf8_unchecked(&slice[..index - 1]) }; + let type_name = unsafe { std::str::from_utf8_unchecked(&slice[index..]) }; + + Ok((module_name, type_name, width)) } - } else { - // This is a qualified tag, which is not allowed! - return Err(( - MadeProgress, - BadIdent::QualifiedTag(state.line, state.column), - state, - )); } } - None => { - // We had neither capitalized nor noncapitalized parts, - // yet we made it this far. The only explanation is that this was - // a stray '.' drifting through the cosmos. - return Err(( - MadeProgress, - BadIdent::StrayDot(state.line, state.column), - state, - )); - } } - } else if is_private_tag { - // This is qualified field access with an '@' in front, which does not make sense! - return Err(( - MadeProgress, - BadIdent::PrivateTagFieldAccess(state.line, state.column), - state, - )); } else { - // We have multiple noncapitalized parts, so this must be field access. - Ident::Access { - module_name: join_module_parts(arena, capitalized_parts.into_bump_slice()), - parts: noncapitalized_parts.into_bump_slice(), - } - }; - - Ok((Progress::MadeProgress, (answer, None), state)) + Ok(("", first, first.len())) + } +} + +fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result { + let mut chomped = 0; + + while let Some(b'.') = buffer.get(chomped) { + match &buffer.get(chomped + 1..) { + Some(slice) => match chomp_lowercase_part(slice) { + Ok(name) => { + let value = unsafe { + std::str::from_utf8_unchecked( + &buffer[chomped + 1..chomped + 1 + name.len()], + ) + }; + parts.push(value); + + chomped += name.len() + 1; + } + Err(_) => return Err(chomped as u16 + 1), + }, + None => return Err(chomped as u16 + 1), + } + } + + if chomped == 0 { + Err(0) + } else { + Ok(chomped as u16) + } +} + +fn parse_ident_help_help<'a>( + arena: &'a Bump, + mut state: State<'a>, +) -> ParseResult<'a, Ident<'a>, BadIdent> { + match chomp_identifier_chain(arena, state.bytes, state.line, state.column) { + Ok((width, ident)) => { + state = advance_state!(state, width as usize)?; + Ok((MadeProgress, ident, state)) + } + Err((0, fail)) => Err((NoProgress, fail, state)), + Err((width, fail)) => { + state = advance_state!(state, width as usize)?; + Err((MadeProgress, fail, state)) + } + } } diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 2a48a885fb..39058ff686 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -1,314 +1,284 @@ -use crate::ast::{Attempting, CommentOrNewline, Def, Module}; -use crate::blankspace::{space0, space0_around, space0_before, space1}; -use crate::expr::def; +use crate::ast::{CommentOrNewline, Def, Module}; +use crate::blankspace::{space0_before_e, space0_e}; use crate::header::{ - package_entry, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry, - InterfaceHeader, ModuleName, PackageEntry, PackageName, PackageOrPath, PlatformHeader, To, - TypedIdent, + package_entry, package_name, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry, + InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, To, TypedIdent, }; use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; use crate::parser::Progress::{self, *}; use crate::parser::{ - self, ascii_char, ascii_string, backtrackable, end_of_file, loc, optional, peek_utf8_char, - peek_utf8_char_at, unexpected, unexpected_eof, Either, ParseResult, Parser, State, SyntaxError, + backtrackable, specialize, word1, Col, EEffects, EExposes, EHeader, EImports, EPackages, + EProvides, ERequires, ETypedIdent, Parser, Row, State, SyntaxError, }; use crate::string_literal; use crate::type_annotation; -use bumpalo::collections::{String, Vec}; -use bumpalo::Bump; +use bumpalo::collections::Vec; use roc_region::all::Located; -pub fn header<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> { - one_of!(interface_module(), app_module(), platform_module()) +pub fn parse_header<'a>( + arena: &'a bumpalo::Bump, + state: State<'a>, +) -> Result<(Module<'a>, State<'a>), EHeader<'a>> { + match header().parse(arena, state) { + Ok((_, module, state)) => Ok((module, state)), + Err((_, fail, _)) => Err(fail), + } } -#[inline(always)] -fn app_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> { - map!(app_header(), |header| { Module::App { header } }) -} +fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { + use crate::parser::keyword_e; -#[inline(always)] -fn platform_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> { - map!(platform_header(), |header| { Module::Platform { header } }) -} - -#[inline(always)] -fn interface_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> { - map!(interface_header(), |header| { - Module::Interface { header } - }) -} - -#[inline(always)] -pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, SyntaxError<'a>> { - parser::map( - and!( - skip_first!( - ascii_string("interface"), - and!(space1(1), loc!(module_name())) - ), - and!(exposes_values(), imports()) + one_of![ + map!( + skip_first!(keyword_e("app", EHeader::Start), app_header()), + |header| { Module::App { header } } ), - |( - (after_interface_keyword, name), - ( - ((before_exposes, after_exposes), exposes), - ((before_imports, after_imports), imports), - ), - )| { - InterfaceHeader { - name, - exposes, - imports, - after_interface_keyword, - before_exposes, - after_exposes, - before_imports, - after_imports, - } - }, - ) + map!( + skip_first!(keyword_e("platform", EHeader::Start), platform_header()), + |header| { Module::Platform { header } } + ), + map!( + skip_first!(keyword_e("interface", EHeader::Start), interface_header()), + |header| { Module::Interface { header } } + ) + ] } #[inline(always)] -pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, SyntaxError<'a>> { - // e.g. rtfeldman/blah - // - // Package names and accounts can be capitalized and can contain dashes. - // They cannot contain underscores or other special characters. - // They must be ASCII. +fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> { + |arena, state| { + let min_indent = 1; - map!( - and!( - parse_package_part, - skip_first!(ascii_char(b'/'), parse_package_part) - ), - |(account, pkg)| { PackageName { account, pkg } } - ) + let (_, after_interface_keyword, state) = + space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?; + let (_, name, state) = loc!(module_name_help(EHeader::ModuleName)).parse(arena, state)?; + + let (_, ((before_exposes, after_exposes), exposes), state) = + specialize(EHeader::Exposes, exposes_values()).parse(arena, state)?; + let (_, ((before_imports, after_imports), imports), state) = + specialize(EHeader::Imports, imports()).parse(arena, state)?; + + let header = InterfaceHeader { + name, + exposes, + imports, + after_interface_keyword, + before_exposes, + after_exposes, + before_imports, + after_imports, + }; + + Ok((MadeProgress, header, state)) + } } -pub fn parse_package_part<'a>( - arena: &'a Bump, - mut state: State<'a>, -) -> ParseResult<'a, &'a str, SyntaxError<'a>> { - let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) +fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> { + use encode_unicode::CharExt; - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((ch, bytes_parsed)) => { - if ch == '-' || ch.is_ascii_alphanumeric() { - part_buf.push(ch); + let mut chomped = 0; - state = state.advance_without_indenting(bytes_parsed)?; + if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if first_letter.is_uppercase() { + chomped += width; + } else { + return Err(Progress::NoProgress); + } + } + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + // After the first character, only these are allowed: + // + // * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers + // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() + // * A '.' separating module parts + if ch.is_alphabetic() || ch.is_ascii_digit() { + chomped += width; + } else if ch == '.' { + chomped += width; + + if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if first_letter.is_uppercase() { + chomped += width; + } else if first_letter == '{' { + // the .{ starting a `Foo.{ bar, baz }` importing clauses + chomped -= width; + break; } else { - let progress = Progress::progress_when(!part_buf.is_empty()); - return Ok((progress, part_buf.into_bump_str(), state)); + return Err(Progress::MadeProgress); } } - Err(reason) => { - let progress = Progress::progress_when(!part_buf.is_empty()); - return state.fail(arena, progress, reason); - } + } else { + // we're done + break; } } - Err(unexpected_eof(arena, state, 0)) + let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + + Ok(name) } #[inline(always)] -pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, SyntaxError<'a>> { - move |arena, mut state: State<'a>| { - match peek_utf8_char(&state) { - Ok((first_letter, bytes_parsed)) => { - if !first_letter.is_uppercase() { - return Err(unexpected(0, Attempting::Module, state)); - }; +fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { + |_, mut state: State<'a>| match chomp_module_name(state.bytes) { + Ok(name) => { + let width = name.len(); + state.column += width as u16; + state.bytes = &state.bytes[width..]; - let mut buf = String::with_capacity_in(4, arena); - - buf.push(first_letter); - - state = state.advance_without_indenting(bytes_parsed)?; - - while !state.bytes.is_empty() { - match peek_utf8_char(&state) { - Ok((ch, bytes_parsed)) => { - // After the first character, only these are allowed: - // - // * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers - // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() - // * A '.' separating module parts - if ch.is_alphabetic() || ch.is_ascii_digit() { - state = state.advance_without_indenting(bytes_parsed)?; - - buf.push(ch); - } else if ch == '.' { - match peek_utf8_char_at(&state, 1) { - Ok((next, next_bytes_parsed)) => { - if next.is_uppercase() { - // If we hit another uppercase letter, keep going! - buf.push('.'); - buf.push(next); - - state = state.advance_without_indenting( - bytes_parsed + next_bytes_parsed, - )?; - } else { - // We have finished parsing the module name. - // - // There may be an identifier after this '.', - // e.g. "baz" in `Foo.Bar.baz` - return Ok(( - MadeProgress, - ModuleName::new(buf.into_bump_str()), - state, - )); - } - } - Err(reason) => return state.fail(arena, MadeProgress, reason), - } - } else { - // This is the end of the module name. We're done! - break; - } - } - Err(reason) => return state.fail(arena, MadeProgress, reason), - } - } - - Ok((MadeProgress, ModuleName::new(buf.into_bump_str()), state)) - } - Err(reason) => state.fail(arena, MadeProgress, reason), + Ok((MadeProgress, ModuleName::new(name), state)) } + Err(progress) => Err((progress, (), state)), } } #[inline(always)] -pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, SyntaxError<'a>> { - map_with_arena!( - and!( - skip_first!( - ascii_string("app"), - and!( - space1(1), - loc!(crate::parser::specialize( - |e, r, c| SyntaxError::Expr(crate::parser::EExpr::Str(e, r, c)), - string_literal::parse() - )) - ) - ), - and!( - optional(packages()), - and!(optional(imports()), provides_to()) - ) - ), - |arena, ((after_app_keyword, name), (opt_pkgs, (opt_imports, provides)))| { - let (before_packages, after_packages, package_entries) = match opt_pkgs { - Some(pkgs) => { - let pkgs: Packages<'a> = pkgs; // rustc must be told the type here +fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> { + |arena, state| { + let min_indent = 1; - ( - pkgs.before_packages_keyword, - pkgs.after_packages_keyword, - pkgs.entries, - ) - } - None => (&[] as _, &[] as _, Vec::new_in(arena)), - }; + let (_, after_app_keyword, state) = + space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?; + let (_, name, state) = loc!(crate::parser::specialize( + EHeader::AppName, + string_literal::parse() + )) + .parse(arena, state)?; - // rustc must be told the type here - #[allow(clippy::type_complexity)] - let opt_imports: Option<( - (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Vec<'a, Located>>, - )> = opt_imports; + let (_, opt_pkgs, state) = + maybe!(specialize(EHeader::Packages, packages())).parse(arena, state)?; + let (_, opt_imports, state) = + maybe!(specialize(EHeader::Imports, imports())).parse(arena, state)?; + let (_, provides, state) = + specialize(EHeader::Provides, provides_to()).parse(arena, state)?; - let ((before_imports, after_imports), imports) = - opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena))); - let provides: ProvidesTo<'a> = provides; // rustc must be told the type here + let (before_packages, after_packages, package_entries) = match opt_pkgs { + Some(pkgs) => { + let pkgs: Packages<'a> = pkgs; // rustc must be told the type here - AppHeader { - name, - packages: package_entries, - imports, - provides: provides.entries, - to: provides.to, - after_app_keyword, - before_packages, - after_packages, - before_imports, - after_imports, - before_provides: provides.before_provides_keyword, - after_provides: provides.after_provides_keyword, - before_to: provides.before_to_keyword, - after_to: provides.after_to_keyword, - } - } - ) -} - -#[inline(always)] -pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, SyntaxError<'a>> { - parser::map( - and!( - skip_first!( - ascii_string("platform"), - and!(space1(1), loc!(package_name())) - ), - and!( - and!( - and!(requires(), and!(exposes_modules(), packages())), - and!(imports(), provides_without_to()) - ), - effects() - ) - ), - |( - (after_platform_keyword, name), - ( ( - ( - ((before_requires, after_requires), requires), - (((before_exposes, after_exposes), exposes), packages), - ), - ( - ((before_imports, after_imports), imports), - ((before_provides, after_provides), provides), - ), - ), - effects, - ), - )| { - PlatformHeader { - name, - requires, - exposes, - packages: packages.entries, - imports, - provides, - effects, - after_platform_keyword, - before_requires, - after_requires, - before_exposes, - after_exposes, - before_packages: packages.before_packages_keyword, - after_packages: packages.after_packages_keyword, - before_imports, - after_imports, - before_provides, - after_provides, + pkgs.before_packages_keyword, + pkgs.after_packages_keyword, + pkgs.entries, + ) } - }, - ) + None => (&[] as _, &[] as _, Vec::new_in(arena)), + }; + + // rustc must be told the type here + #[allow(clippy::type_complexity)] + let opt_imports: Option<( + (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), + Vec<'a, Located>>, + )> = opt_imports; + + let ((before_imports, after_imports), imports) = + opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena))); + let provides: ProvidesTo<'a> = provides; // rustc must be told the type here + + let header = AppHeader { + name, + packages: package_entries, + imports, + provides: provides.entries, + to: provides.to, + after_app_keyword, + before_packages, + after_packages, + before_imports, + after_imports, + before_provides: provides.before_provides_keyword, + after_provides: provides.after_provides_keyword, + before_to: provides.before_to_keyword, + after_to: provides.after_to_keyword, + }; + + Ok((MadeProgress, header, state)) + } +} + +#[inline(always)] +fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { + |arena, state| { + let min_indent = 1; + + let (_, after_platform_keyword, state) = + space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?; + let (_, name, state) = + loc!(specialize(EHeader::PlatformName, package_name())).parse(arena, state)?; + + let (_, ((before_requires, after_requires), requires), state) = + specialize(EHeader::Requires, requires()).parse(arena, state)?; + + let (_, ((before_exposes, after_exposes), exposes), state) = + specialize(EHeader::Exposes, exposes_modules()).parse(arena, state)?; + + let (_, packages, state) = specialize(EHeader::Packages, packages()).parse(arena, state)?; + + let (_, ((before_imports, after_imports), imports), state) = + specialize(EHeader::Imports, imports()).parse(arena, state)?; + + let (_, ((before_provides, after_provides), provides), state) = + specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?; + + let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?; + + let header = PlatformHeader { + name, + requires, + exposes, + packages: packages.entries, + imports, + provides, + effects, + after_platform_keyword, + before_requires, + after_requires, + before_exposes, + after_exposes, + before_packages: packages.before_packages_keyword, + after_packages: packages.after_packages_keyword, + before_imports, + after_imports, + before_provides, + after_provides, + }; + + Ok((MadeProgress, header, state)) + } +} + +fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { + |_arena, state: State<'a>| { + if state.has_reached_end() { + Ok((NoProgress, (), state)) + } else { + Err((NoProgress, SyntaxError::ConditionFailed, state)) + } + } } #[inline(always)] pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located>>, SyntaxError<'a>> { + use crate::parser::EExpr; // force that we pare until the end of the input - skip_second!(zero_or_more!(space0_around(loc(def(0)), 0)), end_of_file()) + let min_indent = 0; + skip_second!( + specialize( + |e, _, _| SyntaxError::Expr(e), + zero_or_more!(crate::blankspace::space0_around_ee( + loc!(crate::expr::def_help(min_indent)), + min_indent, + EExpr::Space, + EExpr::IndentStart, + EExpr::IndentEnd, + )) + ), + end_of_file() + ) } - +#[derive(Debug)] struct ProvidesTo<'a> { entries: Vec<'a, Located>>, to: Located>, @@ -319,48 +289,39 @@ struct ProvidesTo<'a> { after_to_keyword: &'a [CommentOrNewline<'a>], } +fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> { + one_of![ + specialize( + |_, r, c| EProvides::Identifier(r, c), + map!(lowercase_ident(), To::ExistingPackage) + ), + specialize(EProvides::Package, map!(package_or_path(), To::NewPackage)) + ] +} + #[inline(always)] -fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, SyntaxError<'a>> { +fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> { + let min_indent = 1; + map!( and!( + provides_without_to(), and!( - skip_second!(backtrackable(space1(1)), ascii_string("provides")), - space1(1) - ), - and!( - collection!( - ascii_char(b'['), - loc!(map!(unqualified_ident(), ExposesEntry::Exposed)), - ascii_char(b','), - ascii_char(b']'), - 1 + spaces_around_keyword( + min_indent, + "to", + EProvides::To, + EProvides::Space, + EProvides::IndentTo, + EProvides::IndentListStart ), - and!( - space1(1), - skip_first!( - ascii_string("to"), - and!( - space1(1), - loc!(either!(lowercase_ident(), package_or_path())) - ) - ) - ) + loc!(provides_to_package()) ) ), |( - (before_provides_keyword, after_provides_keyword), - (entries, (before_to_keyword, (after_to_keyword, loc_to))), + ((before_provides_keyword, after_provides_keyword), entries), + ((before_to_keyword, after_to_keyword), to), )| { - let loc_to: Located>> = loc_to; - let to_val = match loc_to.value { - Either::First(pkg) => To::ExistingPackage(pkg), - Either::Second(pkg) => To::NewPackage(pkg), - }; - let to = Located { - value: to_val, - region: loc_to.region, - }; - ProvidesTo { entries, to, @@ -380,20 +341,44 @@ fn provides_without_to<'a>() -> impl Parser< (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), Vec<'a, Located>>, ), - SyntaxError<'a>, + EProvides<'a>, > { + let min_indent = 1; and!( - and!(skip_second!(space1(1), ascii_string("provides")), space1(1)), - collection!( - ascii_char(b'['), - loc!(map!(unqualified_ident(), ExposesEntry::Exposed)), - ascii_char(b','), - ascii_char(b']'), - 1 + spaces_around_keyword( + min_indent, + "provides", + EProvides::Provides, + EProvides::Space, + EProvides::IndentProvides, + EProvides::IndentListStart + ), + collection_e!( + word1(b'[', EProvides::ListStart), + exposes_entry(EProvides::Identifier), + word1(b',', EProvides::ListEnd), + word1(b']', EProvides::ListEnd), + min_indent, + EProvides::Space, + EProvides::IndentListEnd ) ) } +fn exposes_entry<'a, F, E>( + to_expectation: F, +) -> impl Parser<'a, Located>, E> +where + F: Fn(crate::parser::Row, crate::parser::Col) -> E, + F: Copy, + E: 'a, +{ + loc!(map!( + specialize(|_, r, c| to_expectation(r, c), unqualified_ident()), + ExposesEntry::Exposed + )) +} + #[inline(always)] fn requires<'a>() -> impl Parser< 'a, @@ -401,16 +386,26 @@ fn requires<'a>() -> impl Parser< (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), Vec<'a, Located>>, ), - SyntaxError<'a>, + ERequires<'a>, > { + let min_indent = 1; and!( - and!(skip_second!(space1(1), ascii_string("requires")), space1(1)), - collection!( - ascii_char(b'{'), - loc!(typed_ident()), - ascii_char(b','), - ascii_char(b'}'), - 1 + spaces_around_keyword( + min_indent, + "requires", + ERequires::Requires, + ERequires::Space, + ERequires::IndentRequires, + ERequires::IndentListStart + ), + collection_e!( + word1(b'{', ERequires::ListStart), + specialize(ERequires::TypedIdent, loc!(typed_ident())), + word1(b',', ERequires::ListEnd), + word1(b'}', ERequires::ListEnd), + min_indent, + ERequires::Space, + ERequires::IndentListEnd ) ) } @@ -422,20 +417,51 @@ fn exposes_values<'a>() -> impl Parser< (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), Vec<'a, Located>>, ), - SyntaxError<'a>, + EExposes, > { + let min_indent = 1; + and!( - and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)), - collection!( - ascii_char(b'['), - loc!(map!(unqualified_ident(), ExposesEntry::Exposed)), - ascii_char(b','), - ascii_char(b']'), - 1 + spaces_around_keyword( + min_indent, + "exposes", + EExposes::Exposes, + EExposes::Space, + EExposes::IndentExposes, + EExposes::IndentListStart + ), + collection_e!( + word1(b'[', EExposes::ListStart), + exposes_entry(EExposes::Identifier), + word1(b',', EExposes::ListEnd), + word1(b']', EExposes::ListEnd), + min_indent, + EExposes::Space, + EExposes::IndentListEnd ) ) } +fn spaces_around_keyword<'a, E>( + min_indent: u16, + keyword: &'static str, + expectation: fn(Row, Col) -> E, + space_problem: fn(crate::parser::BadInputError, Row, Col) -> E, + indent_problem1: fn(Row, Col) -> E, + indent_problem2: fn(Row, Col) -> E, +) -> impl Parser<'a, (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), E> +where + E: 'a, +{ + and!( + skip_second!( + backtrackable(space0_e(min_indent, space_problem, indent_problem1)), + crate::parser::keyword_e(keyword, expectation) + ), + space0_e(min_indent, space_problem, indent_problem2) + ) +} + #[inline(always)] fn exposes_modules<'a>() -> impl Parser< 'a, @@ -443,20 +469,45 @@ fn exposes_modules<'a>() -> impl Parser< (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), Vec<'a, Located>>>, ), - SyntaxError<'a>, + EExposes, > { + let min_indent = 1; + and!( - and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)), - collection!( - ascii_char(b'['), - loc!(map!(module_name(), ExposesEntry::Exposed)), - ascii_char(b','), - ascii_char(b']'), - 1 + spaces_around_keyword( + min_indent, + "exposes", + EExposes::Exposes, + EExposes::Space, + EExposes::IndentExposes, + EExposes::IndentListStart + ), + collection_e!( + word1(b'[', EExposes::ListStart), + exposes_module(EExposes::Identifier), + word1(b',', EExposes::ListEnd), + word1(b']', EExposes::ListEnd), + min_indent, + EExposes::Space, + EExposes::IndentListEnd ) ) } +fn exposes_module<'a, F, E>( + to_expectation: F, +) -> impl Parser<'a, Located>>, E> +where + F: Fn(crate::parser::Row, crate::parser::Col) -> E, + F: Copy, + E: 'a, +{ + loc!(map!( + specialize(|_, r, c| to_expectation(r, c), module_name()), + ExposesEntry::Exposed + )) +} + #[derive(Debug)] struct Packages<'a> { entries: Vec<'a, Located>>, @@ -466,19 +517,27 @@ struct Packages<'a> { } #[inline(always)] -fn packages<'a>() -> impl Parser<'a, Packages<'a>, SyntaxError<'a>> { +fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> { + let min_indent = 1; + map!( and!( - and!( - skip_second!(backtrackable(space1(1)), ascii_string("packages")), - space1(1) + spaces_around_keyword( + min_indent, + "packages", + EPackages::Packages, + EPackages::Space, + EPackages::IndentPackages, + EPackages::IndentListStart ), - collection!( - ascii_char(b'{'), - loc!(package_entry()), - ascii_char(b','), - ascii_char(b'}'), - 1 + collection_e!( + word1(b'{', EPackages::ListStart), + specialize(EPackages::PackageEntry, loc!(package_entry())), + word1(b',', EPackages::ListEnd), + word1(b'}', EPackages::ListEnd), + min_indent, + EPackages::Space, + EPackages::IndentListEnd ) ), |((before_packages_keyword, after_packages_keyword), entries)| { @@ -498,42 +557,68 @@ fn imports<'a>() -> impl Parser< (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), Vec<'a, Located>>, ), - SyntaxError<'a>, + EImports, > { + let min_indent = 1; + and!( - and!( - skip_second!(backtrackable(space1(1)), ascii_string("imports")), - space1(1) + spaces_around_keyword( + min_indent, + "imports", + EImports::Imports, + EImports::Space, + EImports::IndentImports, + EImports::IndentListStart ), - collection!( - ascii_char(b'['), + collection_e!( + word1(b'[', EImports::ListStart), loc!(imports_entry()), - ascii_char(b','), - ascii_char(b']'), - 1 + word1(b',', EImports::ListEnd), + word1(b']', EImports::ListEnd), + min_indent, + EImports::Space, + EImports::IndentListEnd ) ) } #[inline(always)] -fn effects<'a>() -> impl Parser<'a, Effects<'a>, SyntaxError<'a>> { +fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> { move |arena, state| { - let (_, spaces_before_effects_keyword, state) = - skip_second!(space1(0), ascii_string("effects")).parse(arena, state)?; - let (_, spaces_after_effects_keyword, state) = space1(0).parse(arena, state)?; + let min_indent = 1; + + let (_, (spaces_before_effects_keyword, spaces_after_effects_keyword), state) = + spaces_around_keyword( + min_indent, + "effects", + EEffects::Effects, + EEffects::Space, + EEffects::IndentEffects, + EEffects::IndentListStart, + ) + .parse(arena, state)?; // e.g. `fx.` - let (_, type_shortname, state) = - skip_second!(lowercase_ident(), ascii_char(b'.')).parse(arena, state)?; + let (_, type_shortname, state) = skip_second!( + specialize(|_, r, c| EEffects::Shorthand(r, c), lowercase_ident()), + word1(b'.', EEffects::ShorthandDot) + ) + .parse(arena, state)?; - let (_, (type_name, spaces_after_type_name), state) = - and!(uppercase_ident(), space1(0)).parse(arena, state)?; - let (_, entries, state) = collection!( - ascii_char(b'{'), - loc!(typed_ident()), - ascii_char(b','), - ascii_char(b'}'), - 1 + // the type name, e.g. Effects + let (_, (type_name, spaces_after_type_name), state) = and!( + specialize(|_, r, c| EEffects::TypeName(r, c), uppercase_ident()), + space0_e(min_indent, EEffects::Space, EEffects::IndentListStart) + ) + .parse(arena, state)?; + let (_, entries, state) = collection_e!( + word1(b'{', EEffects::ListStart), + specialize(EEffects::TypedIdent, loc!(typed_ident())), + word1(b',', EEffects::ListEnd), + word1(b'}', EEffects::ListEnd), + min_indent, + EEffects::Space, + EEffects::IndentListEnd ) .parse(arena, state)?; @@ -553,63 +638,89 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, SyntaxError<'a>> { } #[inline(always)] -fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, SyntaxError<'a>> { - move |arena, state| { - // You must have a field name, e.g. "email" - let (_, ident, state) = loc!(lowercase_ident()).parse(arena, state)?; +fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> { + // e.g. + // + // printLine : Str -> Effect {} + let min_indent = 0; - let (_, spaces_before_colon, state) = space0(0).parse(arena, state)?; - - let (_, ann, state) = skip_first!( - ascii_char(b':'), - space0_before(type_annotation::located(0), 0) - ) - .parse(arena, state)?; - - // e.g. - // - // printLine : Str -> Effect {} - - Ok(( - MadeProgress, + map!( + and!( + and!( + loc!(specialize( + |_, r, c| ETypedIdent::Identifier(r, c), + lowercase_ident() + )), + space0_e(min_indent, ETypedIdent::Space, ETypedIdent::IndentHasType) + ), + skip_first!( + word1(b':', ETypedIdent::HasType), + space0_before_e( + specialize(ETypedIdent::Type, type_annotation::located_help(min_indent)), + min_indent, + ETypedIdent::Space, + ETypedIdent::IndentType, + ) + ) + ), + |((ident, spaces_before_colon), ann)| { TypedIdent::Entry { ident, spaces_before_colon, ann, - }, - state, - )) - } + } + } + ) +} + +fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> { + specialize(|_, r, c| EImports::Shorthand(r, c), lowercase_ident()) +} + +fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E> +where + F: Fn(crate::parser::Row, crate::parser::Col) -> E, + E: 'a, + F: 'a, +{ + specialize(move |_, r, c| to_expectation(r, c), module_name()) } #[inline(always)] -#[allow(clippy::type_complexity)] -fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, SyntaxError<'a>> { +fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> { + let min_indent = 1; + + type Temp<'a> = ( + (Option<&'a str>, ModuleName<'a>), + Option>>>, + ); + map_with_arena!( and!( and!( // e.g. `base.` - optional(skip_second!(lowercase_ident(), ascii_char(b'.'))), + maybe!(skip_second!( + shortname(), + word1(b'.', EImports::ShorthandDot) + )), // e.g. `Task` - module_name() + module_name_help(EImports::ModuleName) ), // e.g. `.{ Task, after}` - optional(skip_first!( - ascii_char(b'.'), - collection!( - ascii_char(b'{'), - loc!(map!(unqualified_ident(), ExposesEntry::Exposed)), - ascii_char(b','), - ascii_char(b'}'), - 1 + maybe!(skip_first!( + word1(b'.', EImports::ExposingDot), + collection_e!( + word1(b'{', EImports::SetStart), + exposes_entry(EImports::Identifier), + word1(b',', EImports::SetEnd), + word1(b'}', EImports::SetEnd), + min_indent, + EImports::Space, + EImports::IndentSetEnd ) )) ), - |arena, - ((opt_shortname, module_name), opt_values): ( - (Option<&'a str>, ModuleName<'a>), - Option>>> - )| { + |arena, ((opt_shortname, module_name), opt_values): Temp<'a>| { let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena)); match opt_shortname { diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index e9f3ed8a9c..1f6ee8609e 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -1,7 +1,5 @@ use crate::ast::Base; -use crate::parser::{parse_utf8, Number, ParseResult, Parser, Progress, State, SyntaxError}; -use std::char; -use std::str::from_utf8_unchecked; +use crate::parser::{Number, ParseResult, Parser, Progress, State}; pub enum NumLiteral<'a> { Float(&'a str), @@ -52,29 +50,21 @@ fn chomp_number_base<'a>( ) -> ParseResult<'a, NumLiteral<'a>, Number> { let (_is_float, chomped) = chomp_number(bytes); - match parse_utf8(&bytes[0..chomped]) { - Ok(string) => match state.advance_without_indenting(chomped + 2 + is_negative as usize) { - Ok(new) => { - // all is well - Ok(( - Progress::MadeProgress, - NumLiteral::NonBase10Int { - is_negative, - string, - base, - }, - new, - )) - } - Err((_, SyntaxError::LineTooLong(_), new)) => { - // the only error we care about in this context - Err((Progress::MadeProgress, Number::LineTooLong, new)) - } - Err(_) => unreachable!("we know advancing will succeed if there is space on the line"), - }, + let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) }; - Err(_) => unreachable!("no invalid utf8 could have been chomped"), - } + let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| { + Number::LineTooLong + })?; + + Ok(( + Progress::MadeProgress, + NumLiteral::NonBase10Int { + is_negative, + string, + base, + }, + new, + )) } fn chomp_number_dec<'a>( @@ -94,27 +84,21 @@ fn chomp_number_dec<'a>( return Err((Progress::NoProgress, Number::End, state)); } - let string = unsafe { from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) }; + let string = + unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) }; - match state.advance_without_indenting(chomped + is_negative as usize) { - Ok(new) => { - // all is well - Ok(( - Progress::MadeProgress, - if is_float { - NumLiteral::Float(string) - } else { - NumLiteral::Num(string) - }, - new, - )) - } - Err((_, SyntaxError::LineTooLong(_), new)) => { - // the only error we care about in this context - Err((Progress::MadeProgress, Number::LineTooLong, new)) - } - Err(_) => unreachable!("we know advancing will succeed if there is space on the line"), - } + let new = state + .advance_without_indenting_ee(chomped + is_negative as usize, |_, _| Number::LineTooLong)?; + + Ok(( + Progress::MadeProgress, + if is_float { + NumLiteral::Float(string) + } else { + NumLiteral::Num(string) + }, + new, + )) } fn chomp_number(mut bytes: &[u8]) -> (bool, usize) { diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 56c93a324b..b806305b07 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -1,15 +1,11 @@ -use crate::ast::Attempting; use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use encode_unicode::CharExt; use roc_region::all::{Located, Region}; use std::fmt; -use std::str::from_utf8; -use std::{char, u16}; use Progress::*; /// A position in a source file. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct State<'a> { /// The raw input bytes from the file. pub bytes: &'a [u8], @@ -22,18 +18,6 @@ pub struct State<'a> { /// Current indentation level, in columns /// (so no indent is col 1 - this saves an arithmetic operation.) pub indent_col: u16, - - // true at the beginning of each line, then false after encountering - // the first nonspace char on that line. - pub is_indenting: bool, - - pub context_stack: &'a ContextStack<'a>, - - /// The original length of the string, before any bytes were consumed. - /// This is used internally by the State::bytes_consumed() function. - /// - /// TODO make this private, in a way that doesn't break macros! - pub original_len: usize, } #[derive(Debug, PartialEq, Eq)] @@ -43,103 +27,24 @@ pub enum Either { } impl<'a> State<'a> { - pub fn new_in(arena: &'a Bump, bytes: &'a [u8], _attempting: Attempting) -> State<'a> { + pub fn new(bytes: &'a [u8]) -> State<'a> { State { bytes, line: 0, column: 0, indent_col: 0, - is_indenting: true, - context_stack: arena.alloc(ContextStack::Nil), - original_len: bytes.len(), } } - pub fn check_indent( - self, - _arena: &'a Bump, - min_indent: u16, - ) -> Result, Self)> { - if self.indent_col < min_indent { - Err((SyntaxError::OutdentedTooFar, self)) - } else { - Ok(self) - } - } - - pub fn check_indent_e( - self, - _arena: &'a Bump, - min_indent: u16, - to_error: TE, - row: Row, - col: Col, - ) -> Result - where - TE: Fn(Row, Col) -> E, - { - if self.indent_col < min_indent { - Err((to_error(row, col), self)) - } else { - Ok(self) - } - } - - /// Returns the total number of bytes consumed since the parser began parsing. - /// - /// So if the parser has consumed 8 bytes, this function will return 8. - pub fn bytes_consumed(&self) -> usize { - self.original_len - self.bytes.len() - } - /// Returns whether the parser has reached the end of the input pub fn has_reached_end(&self) -> bool { self.bytes.is_empty() } - /// Increments the line, then resets column, indent_col, and is_indenting. - /// Advances the input by 1, to consume the newline character. - pub fn newline(&self, arena: &'a Bump) -> Result, Self)> { - self.newline_e(arena, |_, _, _| SyntaxError::TooManyLines) - } - - pub fn newline_e( - &self, - arena: &'a Bump, - to_error: TE, - ) -> Result - where - TE: Fn(BadInputError, Row, Col) -> E, - { - match self.line.checked_add(1) { - Some(line) => Ok(State { - bytes: &self.bytes[1..], - line, - column: 0, - indent_col: 0, - is_indenting: true, - original_len: self.original_len, - context_stack: arena.alloc(self.context_stack.clone()), - }), - None => Err(( - Progress::NoProgress, - to_error(BadInputError::TooManyLines, self.line, self.column), - self.clone(), - )), - } - } - /// Use advance_spaces to advance with indenting. /// This assumes we are *not* advancing with spaces, or at least that /// any spaces on the line were preceded by non-spaces - which would mean /// they weren't eligible to indent anyway. - pub fn advance_without_indenting( - self, - quantity: usize, - ) -> Result, Self)> { - self.advance_without_indenting_ee(quantity, |line, _| SyntaxError::LineTooLong(line)) - } - pub fn advance_without_indenting_e( self, quantity: usize, @@ -167,65 +72,10 @@ impl<'a> State<'a> { bytes: &self.bytes[quantity..], column: column_usize as u16, // Once we hit a nonspace character, we are no longer indenting. - is_indenting: false, ..self }) } - _ => Err(line_too_long_e(self.clone(), to_error)), - } - } - - pub fn advance_spaces( - &self, - arena: &'a Bump, - spaces: usize, - ) -> Result, Self)> { - self.advance_spaces_e(arena, spaces, |line, _| SyntaxError::LineTooLong(line)) - } - - /// Advance the parser while also indenting as appropriate. - /// This assumes we are only advancing with spaces, since they can indent. - pub fn advance_spaces_e( - &self, - arena: &'a Bump, - spaces: usize, - to_error: TE, - ) -> Result - where - TE: Fn(Row, Col) -> E, - { - match (self.column as usize).checked_add(spaces) { - Some(column_usize) if column_usize <= u16::MAX as usize => { - // Spaces don't affect is_indenting; if we were previously indneting, - // we still are, and if we already finished indenting, we're still done. - let is_indenting = self.is_indenting; - - // If we're indenting, spaces indent us further. - let indent_col = if is_indenting { - // This doesn't need to be checked_add because it's always true that - // indent_col <= col, so if this could possibly overflow, we would - // already have errored out from the column calculation. - // - // Leaving debug assertions in case this invariant someday disappers. - debug_assert!(u16::MAX - self.indent_col >= spaces as u16); - debug_assert!(spaces <= u16::MAX as usize); - - self.indent_col + spaces as u16 - } else { - self.indent_col - }; - - Ok(State { - bytes: &self.bytes[spaces..], - line: self.line, - column: column_usize as u16, - indent_col, - is_indenting, - context_stack: arena.alloc(self.context_stack.clone()), - original_len: self.original_len, - }) - } - _ => Err(line_too_long_e(self.clone(), to_error)), + _ => Err((NoProgress, to_error(self.line, self.column), self)), } } @@ -260,16 +110,13 @@ impl<'a> fmt::Debug for State<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "State {{")?; - match from_utf8(self.bytes) { + match std::str::from_utf8(self.bytes) { Ok(string) => write!(f, "\n\tbytes: [utf8] {:?}", string)?, Err(_) => write!(f, "\n\tbytes: [invalid utf8] {:?}", self.bytes)?, } write!(f, "\n\t(line, col): ({}, {}),", self.line, self.column)?; write!(f, "\n\tindent_col: {}", self.indent_col)?; - write!(f, "\n\tis_indenting: {:?}", self.is_indenting)?; - write!(f, "\n\toriginal_len: {}", self.original_len)?; - write!(f, "\n\tcontext stack: {:?}", self.context_stack)?; write!(f, "\n}}") } } @@ -334,9 +181,147 @@ pub enum SyntaxError<'a> { Type(Type<'a>), Pattern(EPattern<'a>), Expr(EExpr<'a>), + Header(EHeader<'a>), Space(BadInputError), } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EHeader<'a> { + Provides(EProvides<'a>, Row, Col), + Exposes(EExposes, Row, Col), + Imports(EImports, Row, Col), + Requires(ERequires<'a>, Row, Col), + Packages(EPackages<'a>, Row, Col), + Effects(EEffects<'a>, Row, Col), + + Space(BadInputError, Row, Col), + Start(Row, Col), + ModuleName(Row, Col), + AppName(EString<'a>, Row, Col), + PlatformName(EPackageName, Row, Col), + IndentStart(Row, Col), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EProvides<'a> { + Provides(Row, Col), + To(Row, Col), + IndentProvides(Row, Col), + IndentTo(Row, Col), + IndentListStart(Row, Col), + IndentListEnd(Row, Col), + IndentPackage(Row, Col), + ListStart(Row, Col), + ListEnd(Row, Col), + Identifier(Row, Col), + Package(EPackageOrPath<'a>, Row, Col), + Space(BadInputError, Row, Col), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EExposes { + Exposes(Row, Col), + IndentExposes(Row, Col), + IndentListStart(Row, Col), + IndentListEnd(Row, Col), + ListStart(Row, Col), + ListEnd(Row, Col), + Identifier(Row, Col), + Space(BadInputError, Row, Col), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ERequires<'a> { + Requires(Row, Col), + IndentRequires(Row, Col), + IndentListStart(Row, Col), + IndentListEnd(Row, Col), + ListStart(Row, Col), + ListEnd(Row, Col), + TypedIdent(ETypedIdent<'a>, Row, Col), + Space(BadInputError, Row, Col), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ETypedIdent<'a> { + Space(BadInputError, Row, Col), + HasType(Row, Col), + IndentHasType(Row, Col), + Name(Row, Col), + Type(Type<'a>, Row, Col), + IndentType(Row, Col), + Identifier(Row, Col), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EPackages<'a> { + Space(BadInputError, Row, Col), + Packages(Row, Col), + IndentPackages(Row, Col), + ListStart(Row, Col), + ListEnd(Row, Col), + IndentListStart(Row, Col), + IndentListEnd(Row, Col), + PackageEntry(EPackageEntry<'a>, Row, Col), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EPackageName { + MissingSlash(Row, Col), + Account(Row, Col), + Pkg(Row, Col), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EPackageOrPath<'a> { + BadPath(EString<'a>, Row, Col), + BadPackage(EPackageName, Row, Col), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EPackageEntry<'a> { + BadPackageOrPath(EPackageOrPath<'a>, Row, Col), + Shorthand(Row, Col), + Colon(Row, Col), + IndentPackageOrPath(Row, Col), + Space(BadInputError, Row, Col), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EEffects<'a> { + Space(BadInputError, Row, Col), + Effects(Row, Col), + IndentEffects(Row, Col), + ListStart(Row, Col), + ListEnd(Row, Col), + IndentListStart(Row, Col), + IndentListEnd(Row, Col), + TypedIdent(ETypedIdent<'a>, Row, Col), + ShorthandDot(Row, Col), + Shorthand(Row, Col), + TypeName(Row, Col), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EImports { + Imports(Row, Col), + IndentImports(Row, Col), + IndentListStart(Row, Col), + IndentListEnd(Row, Col), + ListStart(Row, Col), + ListEnd(Row, Col), + Identifier(Row, Col), + ExposingDot(Row, Col), + ShorthandDot(Row, Col), + Shorthand(Row, Col), + ModuleName(Row, Col), + Space(BadInputError, Row, Col), + IndentSetStart(Row, Col), + IndentSetEnd(Row, Col), + SetStart(Row, Col), + SetEnd(Row, Col), +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BadInputError { HasTab, @@ -393,7 +378,7 @@ pub enum EExpr<'a> { UnaryNegate(Row, Col), BadOperator(&'a [u8], Row, Col), - Def(&'a SyntaxError<'a>, Row, Col), + DefMissingFinalExpr(Row, Col), Type(Type<'a>, Row, Col), Pattern(&'a EPattern<'a>, Row, Col), IndentDefBody(Row, Col), @@ -409,8 +394,6 @@ pub enum EExpr<'a> { BackpassComma(Row, Col), BackpassArrow(Row, Col), - Syntax(&'a SyntaxError<'a>, Row, Col), - When(When<'a>, Row, Col), If(If<'a>, Row, Col), @@ -443,17 +426,10 @@ pub enum EString<'a> { EndlessSingle(Row, Col), EndlessMulti(Row, Col), UnknownEscape(Row, Col), - Format(&'a SyntaxError<'a>, Row, Col), + Format(&'a EExpr<'a>, Row, Col), + FormatEnd(Row, Col), } -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum Escape { -// EscapeUnknown, -// BadUnicodeFormat(u16), -// BadUnicodeCode(u16), -// BadUnicodeLength(u16, i32, i32), -// } - #[derive(Debug, Clone, PartialEq, Eq)] pub enum ERecord<'a> { End(Row, Col), @@ -467,7 +443,7 @@ pub enum ERecord<'a> { Ampersand(Row, Col), // TODO remove - Syntax(&'a SyntaxError<'a>, Row, Col), + Expr(&'a EExpr<'a>, Row, Col), Space(BadInputError, Row, Col), @@ -513,7 +489,6 @@ pub enum List<'a> { End(Row, Col), Space(BadInputError, Row, Col), - Syntax(&'a SyntaxError<'a>, Row, Col), Expr(&'a EExpr<'a>, Row, Col), IndentOpen(Row, Col), @@ -534,7 +509,6 @@ pub enum When<'a> { Condition(&'a EExpr<'a>, Row, Col), Branch(&'a EExpr<'a>, Row, Col), - Syntax(&'a SyntaxError<'a>, Row, Col), IndentIs(Row, Col), IndentCondition(Row, Col), @@ -555,7 +529,6 @@ pub enum If<'a> { Condition(&'a EExpr<'a>, Row, Col), ThenBranch(&'a EExpr<'a>, Row, Col), ElseBranch(&'a EExpr<'a>, Row, Col), - Syntax(&'a SyntaxError<'a>, Row, Col), IndentCondition(Row, Col), IndentIf(Row, Col), @@ -590,9 +563,9 @@ pub enum PRecord<'a> { Field(Row, Col), Colon(Row, Col), Optional(Row, Col), + Pattern(&'a EPattern<'a>, Row, Col), - // TODO remove - Syntax(&'a SyntaxError<'a>, Row, Col), + Expr(&'a EExpr<'a>, Row, Col), Space(BadInputError, Row, Col), @@ -606,13 +579,9 @@ pub enum PRecord<'a> { pub enum PInParens<'a> { End(Row, Col), Open(Row, Col), - /// - // TODO remove - Syntax(&'a SyntaxError<'a>, Row, Col), + Pattern(&'a EPattern<'a>, Row, Col), - /// Space(BadInputError, Row, Col), - /// IndentOpen(Row, Col), IndentEnd(Row, Col), } @@ -623,7 +592,7 @@ pub enum Type<'a> { TTagUnion(TTagUnion<'a>, Row, Col), TInParens(TInParens<'a>, Row, Col), TApply(TApply, Row, Col), - TVariable(TVariable, Row, Col), + TBadTypeVariable(Row, Col), TWildcard(Row, Col), /// TStart(Row, Col), @@ -693,47 +662,6 @@ pub enum TApply { StartIsNumber(Row, Col), } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TVariable { - /// - StartNotLowercase(Row, Col), - End(Row, Col), - Space(BadInputError, Row, Col), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ContextStack<'a> { - Cons(ContextItem, &'a ContextStack<'a>), - Nil, -} - -impl<'a> ContextStack<'a> { - pub fn uncons(&'a self) -> Option<(ContextItem, &'a Self)> { - match self { - ContextStack::Cons(item, rest) => Some((*item, rest)), - ContextStack::Nil => None, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ContextItem { - pub line: u32, - pub column: u16, - pub context: Attempting, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DeadEnd<'a, T> { - pub line: u32, - pub column: u16, - pub problem: T, - pub context_stack: ContextStack<'a>, -} - -/// use std vec to escape the arena's lifetime bound -/// since this is only used when there is in fact an error -/// I think this is fine #[derive(Debug)] pub struct ParseProblem<'a, T> { pub line: u32, @@ -743,10 +671,6 @@ pub struct ParseProblem<'a, T> { pub bytes: &'a [u8], } -pub fn fail<'a, T>() -> impl Parser<'a, T, SyntaxError<'a>> { - move |_arena, state: State<'a>| Err((NoProgress, SyntaxError::ConditionFailed, state)) -} - pub trait Parser<'a, Output, Error> { fn parse(&self, _: &'a Bump, _: State<'a>) -> ParseResult<'a, Output, Error>; } @@ -774,83 +698,6 @@ where } } -pub fn not_followed_by<'a, P, ByParser, By, Val>( - parser: P, - by: ByParser, -) -> impl Parser<'a, Val, SyntaxError<'a>> -where - ByParser: Parser<'a, By, SyntaxError<'a>>, - P: Parser<'a, Val, SyntaxError<'a>>, -{ - move |arena, state: State<'a>| { - let original_state = state.clone(); - - parser - .parse(arena, state) - .and_then(|(progress, answer, state)| { - let after_parse = state.clone(); - - match by.parse(arena, state) { - Ok((_, _, _state)) => { - Err((NoProgress, SyntaxError::ConditionFailed, original_state)) - } - Err(_) => Ok((progress, answer, after_parse)), - } - }) - } -} - -pub fn not<'a, P, Val>(parser: P) -> impl Parser<'a, (), SyntaxError<'a>> -where - P: Parser<'a, Val, SyntaxError<'a>>, -{ - move |arena, state: State<'a>| { - let original_state = state.clone(); - - match parser.parse(arena, state) { - Ok((_, _, _)) => Err((NoProgress, SyntaxError::ConditionFailed, original_state)), - Err((_, _, _)) => Ok((NoProgress, (), original_state)), - } - } -} - -pub fn not_e<'a, P, TE, E, X, Val>(parser: P, to_error: TE) -> impl Parser<'a, (), E> -where - TE: Fn(Row, Col) -> E, - P: Parser<'a, Val, X>, - E: 'a, -{ - move |arena, state: State<'a>| { - let original_state = state.clone(); - - match parser.parse(arena, state) { - Ok((_, _, _)) => Err(( - NoProgress, - to_error(original_state.line, original_state.column), - original_state, - )), - Err((_, _, _)) => Ok((NoProgress, (), original_state)), - } - } -} - -pub fn lookahead<'a, Peek, P, PeekVal, Val, Error>( - peek: Peek, - parser: P, -) -> impl Parser<'a, Val, Error> -where - Error: 'a, - Peek: Parser<'a, PeekVal, Error>, - P: Parser<'a, Val, Error>, -{ - move |arena, state: State<'a>| { - let original_state = state.clone(); - - peek.parse(arena, state) - .and_then(|_| parser.parse(arena, original_state)) - } -} - pub fn and_then<'a, P1, P2, F, Before, After, Error>( parser: P1, transform: F, @@ -905,280 +752,32 @@ where } } -pub fn unexpected_eof<'a>( - _arena: &'a Bump, - state: State<'a>, - chars_consumed: usize, -) -> (Progress, SyntaxError<'a>, State<'a>) { - checked_unexpected(state, chars_consumed, SyntaxError::Eof) -} - -pub fn unexpected( - chars_consumed: usize, - _attempting: Attempting, - state: State, -) -> (Progress, SyntaxError, State) { - // NOTE state is the last argument because chars_consumed often depends on the state's fields - // having state be the final argument prevents borrowing issues - checked_unexpected(state, chars_consumed, |region| { - SyntaxError::Unexpected(region) - }) -} - -/// Check for line overflow, then compute a new Region based on chars_consumed -/// and provide it as a way to construct a Problem. -/// If maximum line length was exceeded, return a Problem indicating as much. -#[inline(always)] -fn checked_unexpected<'a, F>( - state: State<'a>, - chars_consumed: usize, - problem_from_region: F, -) -> (Progress, SyntaxError<'a>, State<'a>) -where - F: FnOnce(Region) -> SyntaxError<'a>, -{ - match (state.column as usize).checked_add(chars_consumed) { - // Crucially, this is < u16::MAX and not <= u16::MAX. This means if - // column ever gets set to u16::MAX, we will automatically bail out - // with LineTooLong - which is exactly what we want! Once a line has - // been discovered to be too long, we don't want to parse anything else - // until that's fixed. - Some(end_col) if end_col < u16::MAX as usize => { - let region = Region { - start_col: state.column, - end_col: end_col as u16, - start_line: state.line, - end_line: state.line, - }; - - (Progress::NoProgress, problem_from_region(region), state) - } - _ => { - let (_progress, fail, state) = line_too_long(state); - (Progress::NoProgress, fail, state) - } - } -} - -fn line_too_long_e(state: State, to_error: TE) -> (Progress, E, State) -where - TE: Fn(Row, Col) -> E, -{ - let problem = to_error(state.line, state.column); - // Set column to MAX and advance the parser to end of input. - // This way, all future parsers will fail on EOF, and then - // unexpected_eof will take them back here - thus propagating - // the initial LineTooLong error all the way to the end, even if - // (for example) the LineTooLong initially occurs in the middle of - // a one_of chain, which would otherwise prevent it from propagating. - let column = u16::MAX; - let bytes = state.bytes.get(0..state.bytes.len()).unwrap(); - let state = State { - bytes, - line: state.line, - column, - ..state - }; - - // TODO do we make progress in this case? - // isn't this error fatal? - (Progress::NoProgress, problem, state) -} - -fn line_too_long(state: State) -> (Progress, SyntaxError, State) { - line_too_long_e(state, |line, _| SyntaxError::LineTooLong(line)) -} - -/// A single ASCII char that isn't a newline. -/// (For newlines, use newline_char(), which handles line numbers) -pub fn ascii_char<'a>(expected: u8) -> impl Parser<'a, (), SyntaxError<'a>> { - // Make sure this really is not a newline! - debug_assert_ne!(expected, b'\n'); - - move |arena, state: State<'a>| match state.bytes.first() { - Some(&actual) if expected == actual => Ok(( - Progress::MadeProgress, - (), - state.advance_without_indenting(1)?, - )), - Some(_) => Err(unexpected(0, Attempting::Keyword, state)), - _ => Err(unexpected_eof(arena, state, 0)), - } -} - -/// A single '\n' character. -/// Use this instead of ascii_char('\n') because it properly handles -/// incrementing the line number. -pub fn newline_char<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { - move |arena, state: State<'a>| match state.bytes.first() { - Some(b'\n') => Ok((Progress::MadeProgress, (), state.newline(arena)?)), - Some(_) => Err(unexpected(0, Attempting::Keyword, state)), - _ => Err(unexpected_eof(arena, state, 0)), - } -} - -/// One or more ASCII hex digits. (Useful when parsing unicode escape codes, -/// which must consist entirely of ASCII hex digits.) -pub fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> { - move |arena, state: State<'a>| { - let mut buf = bumpalo::collections::String::new_in(arena); - - for &byte in state.bytes.iter() { - if (byte as char).is_ascii_hexdigit() { - buf.push(byte as char); - } else if buf.is_empty() { - // We didn't find any hex digits! - return Err(unexpected(0, Attempting::Keyword, state)); - } else { - let state = state.advance_without_indenting(buf.len())?; - - return Ok((Progress::MadeProgress, buf.into_bump_str(), state)); - } - } - - Err(unexpected_eof(arena, state, 0)) - } -} - -/// A single UTF-8-encoded char. This will both parse *and* validate that the -/// char is valid UTF-8, but it will *not* advance the state. -pub fn peek_utf8_char<'a>(state: &State) -> Result<(char, usize), SyntaxError<'a>> { - if !state.bytes.is_empty() { - match char::from_utf8_slice_start(state.bytes) { - Ok((ch, len_utf8)) => Ok((ch, len_utf8)), - Err(_) => Err(SyntaxError::BadUtf8), - } - } else { - Err(SyntaxError::Eof( - Region::zero(), /* TODO get a better region */ - )) - } -} - -/// A single UTF-8-encoded char. This will both parse *and* validate that the -/// char is valid UTF-8, but it will *not* advance the state. -pub fn peek_utf8_char_e( - state: &State, - end_of_file: EOF, - to_error: TE, -) -> Result<(char, usize), E> -where - TE: Fn(BadInputError, Row, Col) -> E, - EOF: Fn(Row, Col) -> E, -{ - if !state.bytes.is_empty() { - match char::from_utf8_slice_start(state.bytes) { - Ok((ch, len_utf8)) => Ok((ch, len_utf8)), - Err(_) => Err(to_error(BadInputError::BadUtf8, state.line, state.column)), - } - } else { - Err(end_of_file(state.line, state.column)) - } -} - -/// A single UTF-8-encoded char, with an offset. This will both parse *and* -/// validate that the char is valid UTF-8, but it will *not* advance the state. -pub fn peek_utf8_char_at<'a>( - state: &State, - offset: usize, -) -> Result<(char, usize), SyntaxError<'a>> { - if state.bytes.len() > offset { - let bytes = &state.bytes[offset..]; - - match char::from_utf8_slice_start(bytes) { - Ok((ch, len_utf8)) => Ok((ch, len_utf8)), - Err(_) => Err(SyntaxError::BadUtf8), - } - } else { - Err(SyntaxError::Eof( - Region::zero(), /* TODO get a better region */ - )) - } -} - -pub fn keyword<'a>( - keyword: &'static str, - _min_indent: u16, -) -> impl Parser<'a, (), SyntaxError<'a>> { - move |arena, state: State<'a>| { - let initial_state = state.clone(); - // first parse the keyword characters - let (_, _, after_keyword_state) = ascii_string(keyword).parse(arena, state)?; - - // then we must have at least one space character - // TODO this is potentially wasteful if there are a lot of spaces - match peek_utf8_char(&after_keyword_state) { - Ok((next, _width)) if next == ' ' || next == '#' || next == '\n' => { - // give back the state after parsing the keyword, but before the whitespace - // that way we can attach the whitespace to whatever follows - Ok((MadeProgress, (), after_keyword_state)) - } - _ => { - // this is not a keyword, maybe it's `whence` or `iffy` - // anyway, make no progress and return the initial state - // so we can try something else - Err((NoProgress, SyntaxError::ConditionFailed, initial_state)) - } - } - } -} - pub fn keyword_e<'a, ToError, E>(keyword: &'static str, if_error: ToError) -> impl Parser<'a, (), E> where ToError: Fn(Row, Col) -> E, E: 'a, { - move |arena, state: State<'a>| { - let initial_state = state.clone(); - // first parse the keyword characters - let (_, _, after_keyword_state) = ascii_string(keyword) - .parse(arena, state) - .map_err(|(_, _, state)| (NoProgress, if_error(state.line, state.column), state))?; + move |_, mut state: State<'a>| { + let width = keyword.len(); - // then we must have at least one space character - // TODO this is potentially wasteful if there are a lot of spaces - match peek_utf8_char(&after_keyword_state) { - Ok((next, _width)) if next == ' ' || next == '#' || next == '\n' => { - // give back the state after parsing the keyword, but before the whitespace - // that way we can attach the whitespace to whatever follows - Ok((MadeProgress, (), after_keyword_state)) - } - _ => { - // this is not a keyword, maybe it's `whence` or `iffy` - // anyway, make no progress and return the initial state - // so we can try something else - Err(( - NoProgress, - if_error(initial_state.line, initial_state.column), - initial_state, - )) - } + if !state.bytes.starts_with(keyword.as_bytes()) { + return Err((NoProgress, if_error(state.line, state.column), state)); } - } -} -/// A hardcoded string with no newlines, consisting only of ASCII characters -pub fn ascii_string<'a>(keyword: &'static str) -> impl Parser<'a, (), SyntaxError<'a>> { - // Verify that this really is exclusively ASCII characters. - // The `unsafe` block in this function relies upon this assumption! - // - // Also, this can't have newlines because we don't attempt to advance - // the row in the state, only the column. - debug_assert!(keyword.chars().all(|ch| ch.len_utf8() == 1 && ch != '\n')); - - move |_arena, state: State<'a>| { - let len = keyword.len(); - - // TODO do this comparison in one SIMD instruction (on supported systems) - if state.bytes.starts_with(keyword.as_bytes()) { - Ok(( - Progress::MadeProgress, - (), - state.advance_without_indenting(len)?, - )) - } else { - let (_, fail, state) = unexpected(len, Attempting::Keyword, state); - Err((NoProgress, fail, state)) + // the next character should not be an identifier character + // to prevent treating `whence` or `iffy` as keywords + match state.bytes.get(width) { + Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => { + state.column += width as u16; + state.bytes = &state.bytes[width..]; + Ok((MadeProgress, (), state)) + } + None => { + state.column += width as u16; + state.bytes = &state.bytes[width..]; + Ok((MadeProgress, (), state)) + } + Some(_) => Err((NoProgress, if_error(state.line, state.column), state)), } } } @@ -1450,21 +1049,6 @@ pub fn fail_when_progress( } } -pub fn satisfies<'a, P, A, F>(parser: P, predicate: F) -> impl Parser<'a, A, SyntaxError<'a>> -where - P: Parser<'a, A, SyntaxError<'a>>, - F: Fn(&A) -> bool, -{ - move |arena: &'a Bump, state: State<'a>| match parser.parse(arena, state.clone()) { - Ok((progress, output, next_state)) if predicate(&output) => { - Ok((progress, output, next_state)) - } - Ok((progress, _, _)) | Err((progress, _, _)) => { - Err((progress, SyntaxError::ConditionFailed, state)) - } - } -} - pub fn optional<'a, P, T, E>(parser: P) -> impl Parser<'a, Option, E> where P: Parser<'a, T, E>, @@ -1473,7 +1057,7 @@ where move |arena: &'a Bump, state: State<'a>| { // We have to clone this because if the optional parser fails, // we need to revert back to the original state. - let original_state = state.clone(); + let original_state = state; match parser.parse(arena, state) { Ok((progress, out1, state)) => Ok((progress, Some(out1), state)), @@ -1589,6 +1173,46 @@ macro_rules! collection { }; } +#[macro_export] +macro_rules! collection_e { + ($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $space_problem:expr, $indent_problem:expr) => { + skip_first!( + $opening_brace, + skip_first!( + // We specifically allow space characters inside here, so that + // `[ ]` can be successfully parsed as an empty list, and then + // changed by the formatter back into `[]`. + // + // We don't allow newlines or comments in the middle of empty + // roc_collections because those are normally stored in an Expr, + // and there's no Expr in which to store them in an empty collection! + // + // We could change the AST to add extra storage specifically to + // support empty literals containing newlines or comments, but this + // does not seem worth even the tiniest regression in compiler performance. + zero_or_more!($crate::parser::word1(b' ', |row, col| $space_problem( + crate::parser::BadInputError::LineTooLong, + row, + col + ))), + skip_second!( + $crate::parser::sep_by0( + $delimiter, + $crate::blankspace::space0_around_ee( + $elem, + $min_indent, + $space_problem, + $indent_problem, + $indent_problem + ) + ), + $closing_brace + ) + ) + ) + }; +} + /// Parse zero or more elements between two braces (e.g. square braces). /// Elements can be optionally surrounded by spaces, and are separated by a /// delimiter (e.g comma-separated) with optionally a trailing delimiter. @@ -1719,6 +1343,19 @@ macro_rules! one_of { }; } +#[macro_export] +macro_rules! maybe { + ($p1:expr) => { + move |arena: &'a bumpalo::Bump, state: $crate::parser::State<'a>| match $p1 + .parse(arena, state) + { + Ok((progress, value, state)) => Ok((progress, Some(value), state)), + Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), + Err((NoProgress, _, state)) => Ok((NoProgress, None, state)), + } + }; +} + #[macro_export] macro_rules! one_of_with_error { ($toerror:expr; $p1:expr) => { @@ -1835,30 +1472,6 @@ macro_rules! word1_check_indent { }; } -#[allow(dead_code)] -fn in_context<'a, AddContext, P1, P2, Start, A, X, Y>( - add_context: AddContext, - parser_start: P1, - parser_rest: P2, -) -> impl Parser<'a, A, Y> -where - AddContext: Fn(X, Row, Col) -> Y, - P1: Parser<'a, Start, Y>, - P2: Parser<'a, A, X>, - Y: 'a, -{ - move |arena, state| { - let (_, _, state) = parser_start.parse(arena, state)?; - - match parser_rest.parse(arena, state) { - Ok((progress, value, state)) => Ok((progress, value, state)), - Err((progress, fail, state)) => { - Err((progress, add_context(fail, state.line, state.column), state)) - } - } - } -} - #[macro_export] macro_rules! map { ($parser:expr, $transform:expr) => { @@ -1941,42 +1554,6 @@ macro_rules! zero_or_more { #[macro_export] macro_rules! one_or_more { - ($parser:expr) => { - move |arena, state: State<'a>| { - use bumpalo::collections::Vec; - - match $parser.parse(arena, state) { - Ok((_, first_output, next_state)) => { - let mut state = next_state; - let mut buf = Vec::with_capacity_in(1, arena); - - buf.push(first_output); - - loop { - match $parser.parse(arena, state) { - Ok((_, next_output, next_state)) => { - state = next_state; - buf.push(next_output); - } - Err((progress, fail, old_state)) => { - return $crate::parser::fail_when_progress( - progress, fail, buf, old_state, - ) - } - } - } - } - Err((progress, _, new_state)) => { - debug_assert_eq!(progress, NoProgress, "{:?}", &new_state); - Err($crate::parser::unexpected_eof(arena, new_state, 0)) - } - } - } - }; -} - -#[macro_export] -macro_rules! one_or_more_e { ($parser:expr, $to_error:expr) => { move |arena, state: State<'a>| { use bumpalo::collections::Vec; @@ -2020,39 +1597,6 @@ macro_rules! debug { }; } -#[macro_export] -macro_rules! attempt { - ($attempting:expr, $parser:expr) => { - move |arena: &'a Bump, mut state: $crate::parser::State<'a>| { - let item = $crate::parser::ContextItem { - context: $attempting, - line: state.line, - column: state.column, - }; - - state.context_stack = arena.alloc($crate::parser::ContextStack::Cons( - item, - state.context_stack, - )); - - $parser - .parse(arena, state) - .map(|(progress, answer, mut state)| { - // If the parser suceeded, go back to what we were originally attempting. - // (If it failed, that's exactly where we care what we were attempting!) - match state.context_stack.uncons() { - Some((_item, rest)) => { - state.context_stack = rest; - } - None => unreachable!("context stack contains at least one element"), - } - - (progress, answer, state) - }) - } - }; -} - #[macro_export] macro_rules! either { ($p1:expr, $p2:expr) => { @@ -2136,40 +1680,13 @@ where map_with_arena!(parser, transform) } -/// For some reason, some usages won't compile unless they use this instead of the macro version -#[inline(always)] -pub fn attempt<'a, P, Val, Error>(attempting: Attempting, parser: P) -> impl Parser<'a, Val, Error> -where - P: Parser<'a, Val, Error>, - Error: 'a, -{ - attempt!(attempting, parser) -} - -pub fn parse_utf8<'a>(bytes: &[u8]) -> Result<&str, SyntaxError<'a>> { - match from_utf8(bytes) { - Ok(string) => Ok(string), - Err(_) => Err(SyntaxError::BadUtf8), - } -} - -pub fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { - |_arena: &'a Bump, state: State<'a>| { - if state.has_reached_end() { - Ok((NoProgress, (), state)) - } else { - Err((NoProgress, SyntaxError::ConditionFailed, state)) - } - } -} - pub fn backtrackable<'a, P, Val, Error>(parser: P) -> impl Parser<'a, Val, Error> where P: Parser<'a, Val, Error>, Error: 'a, { move |arena: &'a Bump, state: State<'a>| { - let old_state = state.clone(); + let old_state = state; match parser.parse(arena, state) { Ok((_, a, s1)) => Ok((NoProgress, a, s1)), diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 5e073623d8..50883f5352 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -1,10 +1,10 @@ use crate::ast::Pattern; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; -use crate::ident::{ident, lowercase_ident, Ident}; +use crate::ident::{lowercase_ident, parse_ident_help, Ident}; use crate::parser::Progress::{self, *}; use crate::parser::{ backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord, - ParseResult, Parser, State, SyntaxError, + ParseResult, Parser, State, }; use bumpalo::collections::string::String; use bumpalo::collections::Vec; @@ -51,13 +51,6 @@ fn parse_closure_param<'a>( .parse(arena, state) } -pub fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located>, SyntaxError<'a>> { - specialize( - |e, _, _| SyntaxError::Pattern(e), - loc_pattern_help(min_indent), - ) -} - pub fn loc_pattern_help<'a>( min_indent: u16, ) -> impl Parser<'a, Located>, EPattern<'a>> { @@ -130,7 +123,7 @@ fn loc_pattern_in_parens_help<'a>( between!( word1(b'(', PInParens::Open), space0_around_ee( - move |arena, state| specialize_ref(PInParens::Syntax, loc_pattern(min_indent)) + move |arena, state| specialize_ref(PInParens::Pattern, loc_pattern_help(min_indent)) .parse(arena, state), min_indent, PInParens::Space, @@ -176,10 +169,11 @@ fn loc_ident_pattern_help<'a>( can_have_arguments: bool, ) -> impl Parser<'a, Located>, EPattern<'a>> { move |arena: &'a Bump, state: State<'a>| { - let original_state = state.clone(); + let original_state = state; let (_, loc_ident, state) = - specialize(|_, r, c| EPattern::Start(r, c), loc!(ident())).parse(arena, state)?; + specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident_help)) + .parse(arena, state)?; match loc_ident.value { Ident::GlobalTag(tag) => { @@ -296,10 +290,6 @@ fn loc_ident_pattern_help<'a>( } } -pub fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> { - specialize(|e, _, _| SyntaxError::Pattern(e), underscore_pattern_help()) -} - fn underscore_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> { move |arena: &'a Bump, state: State<'a>| { let (_, _, next_state) = word1(b'_', EPattern::Underscore).parse(arena, state)?; @@ -324,13 +314,6 @@ fn lowercase_ident_pattern<'a>( specialize(move |_, _, _| EPattern::End(row, col), lowercase_ident()).parse(arena, state) } -pub fn record_pattern<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> { - specialize( - |e, r, c| SyntaxError::Pattern(EPattern::Record(e, r, c)), - record_pattern_help(min_indent), - ) -} - #[inline(always)] fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> { move |arena, state| { @@ -385,7 +368,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located { - let val_parser = specialize_ref(PRecord::Syntax, loc_pattern(min_indent)); + let val_parser = specialize_ref(PRecord::Pattern, loc_pattern_help(min_indent)); let (_, loc_val, state) = space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon) .parse(arena, state)?; @@ -413,7 +396,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located { let val_parser = - specialize_ref(PRecord::Syntax, loc!(crate::expr::expr(min_indent))); + specialize_ref(PRecord::Expr, loc!(crate::expr::expr_help(min_indent))); let (_, loc_val, state) = space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon) diff --git a/compiler/parse/src/string_literal.rs b/compiler/parse/src/string_literal.rs index 6c5d3e1564..740ef52f29 100644 --- a/compiler/parse/src/string_literal.rs +++ b/compiler/parse/src/string_literal.rs @@ -1,10 +1,7 @@ use crate::ast::{EscapedChar, StrLiteral, StrSegment}; use crate::expr; use crate::parser::Progress::*; -use crate::parser::{ - allocated, ascii_char, loc, parse_utf8, specialize_ref, word1, BadInputError, EString, Parser, - State, -}; +use crate::parser::{allocated, loc, specialize_ref, word1, BadInputError, EString, Parser, State}; use bumpalo::collections::vec::Vec; use bumpalo::Bump; @@ -102,7 +99,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // to exclude that char we just parsed. let string_bytes = &state.bytes[0..(segment_parsed_bytes - 1)]; - match parse_utf8(string_bytes) { + match std::str::from_utf8(string_bytes) { Ok(string) => { state = advance_state!(state, string.len())?; @@ -233,9 +230,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // Parse an arbitrary expression, then give a // canonicalization error if that expression variant // is not allowed inside a string interpolation. - let (_progress, loc_expr, new_state) = specialize_ref( - EString::Format, - skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')')), + let (_progress, loc_expr, new_state) = skip_second!( + specialize_ref(EString::Format, loc(allocated(expr::expr_help(0)))), + word1(b')', EString::FormatEnd) ) .parse(arena, state)?; diff --git a/compiler/parse/src/test_helpers.rs b/compiler/parse/src/test_helpers.rs index e0ca22e765..a1b34105b8 100644 --- a/compiler/parse/src/test_helpers.rs +++ b/compiler/parse/src/test_helpers.rs @@ -1,13 +1,10 @@ -use crate::ast::{self, Attempting}; -use crate::blankspace::space0_before; -use crate::expr::expr; -use crate::module::{header, module_defs}; -use crate::parser::{loc, Parser, State, SyntaxError}; +use crate::ast; +use crate::module::module_defs; +use crate::parser::{Parser, State, SyntaxError}; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_region::all::Located; -#[allow(dead_code)] pub fn parse_expr_with<'a>( arena: &'a Bump, input: &'a str, @@ -15,24 +12,12 @@ pub fn parse_expr_with<'a>( parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) } -pub fn parse_header_with<'a>( - arena: &'a Bump, - input: &'a str, -) -> Result, SyntaxError<'a>> { - let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module); - let answer = header().parse(arena, state); - - answer - .map(|(_, loc_expr, _)| loc_expr) - .map_err(|(_, fail, _)| fail) -} - #[allow(dead_code)] pub fn parse_defs_with<'a>( arena: &'a Bump, input: &'a str, ) -> Result>>, SyntaxError<'a>> { - let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module); + let state = State::new(input.trim().as_bytes()); let answer = module_defs().parse(arena, state); answer .map(|(_, loc_expr, _)| loc_expr) @@ -44,11 +29,10 @@ pub fn parse_loc_with<'a>( arena: &'a Bump, input: &'a str, ) -> Result>, SyntaxError<'a>> { - let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module); - let parser = space0_before(loc(expr(0)), 0); - let answer = parser.parse(&arena, state); + let state = State::new(input.trim().as_bytes()); - answer - .map(|(_, loc_expr, _)| loc_expr) - .map_err(|(_, fail, _)| fail) + match crate::expr::test_parse_expr(0, arena, state) { + Ok(loc_expr) => Ok(loc_expr), + Err(fail) => Err(SyntaxError::Expr(fail)), + } } diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 5f7b6fb5f1..b540775c0e 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -1,24 +1,16 @@ use crate::ast::{AssignedField, Tag, TypeAnnotation}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; -use crate::ident::join_module_parts; use crate::keyword; use crate::parser::{ - allocated, backtrackable, not_e, optional, peek_utf8_char_e, specialize, specialize_ref, word1, - word2, ParseResult, Parser, + allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, ParseResult, + Parser, Progress::{self, *}, - State, SyntaxError, TApply, TInParens, TRecord, TTagUnion, TVariable, Type, + State, TApply, TInParens, TRecord, TTagUnion, Type, }; -use bumpalo::collections::string::String; use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::{Located, Region}; -pub fn located<'a>( - min_indent: u16, -) -> impl Parser<'a, Located>, SyntaxError<'a>> { - specialize(|x, _, _| SyntaxError::Type(x), expression(min_indent)) -} - pub fn located_help<'a>(min_indent: u16) -> impl Parser<'a, Located>, Type<'a>> { expression(min_indent) } @@ -62,7 +54,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located>, Typ loc!(specialize(Type::TRecord, record_type(min_indent))), loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))), loc!(applied_type(min_indent)), - loc!(specialize(Type::TVariable, parse_type_variable)) + loc!(parse_type_variable) ), // Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a one_of![ @@ -117,21 +109,13 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located>)| { @@ -192,102 +176,9 @@ where F: Fn(Row, Col) -> E, E: 'a, { - use encode_unicode::CharExt; - - move |arena, mut state: State<'a>| { - let mut buf; - - match char::from_utf8_slice_start(state.bytes) { - Ok((first_letter, bytes_parsed)) => match first_letter { - '@' => { - debug_assert_eq!(bytes_parsed, 1); - - // parsing a private tag name - match char::from_utf8_slice_start(&state.bytes[1..]) { - Ok((second_letter, bytes_parsed_2)) if second_letter.is_uppercase() => { - let total_parsed = bytes_parsed + bytes_parsed_2; - - buf = String::with_capacity_in(total_parsed, arena); - - buf.push('@'); - buf.push(second_letter); - - state = state.advance_without_indenting(total_parsed).map_err( - |(progress, _, state)| { - (progress, to_problem(state.line, state.column), state) - }, - )?; - } - _ => { - // important for error messages - state = state.advance_without_indenting(bytes_parsed).map_err( - |(progress, _, state)| { - (progress, to_problem(state.line, state.column), state) - }, - )?; - - let row = state.line; - let col = state.column; - return state.fail(arena, MadeProgress, to_problem(row, col)); - } - } - } - - _ if first_letter.is_uppercase() => { - buf = String::with_capacity_in(1, arena); - - buf.push(first_letter); - - state = state.advance_without_indenting(bytes_parsed).map_err( - |(progress, _, state)| { - (progress, to_problem(state.line, state.column), state) - }, - )?; - } - - _ => { - let row = state.line; - let col = state.column; - return state.fail(arena, NoProgress, to_problem(row, col)); - } - }, - Err(_) => { - let row = state.line; - let col = state.column; - return state.fail(arena, NoProgress, to_problem(row, col)); - } - }; - - while !state.bytes.is_empty() { - match char::from_utf8_slice_start(state.bytes) { - Ok((ch, bytes_parsed)) => { - // After the first character, only these are allowed: - // - // * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers - // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() - // * A ':' indicating the end of the field - if ch.is_alphabetic() || ch.is_ascii_digit() { - buf.push(ch); - - state = state.advance_without_indenting(bytes_parsed).map_err( - |(progress, _, state)| { - (progress, to_problem(state.line, state.column), state) - }, - )?; - } else { - // This is the end of the field. We're done! - break; - } - } - Err(_) => { - let row = state.line; - let col = state.column; - return state.fail(arena, MadeProgress, to_problem(row, col)); - } - }; - } - - Ok((MadeProgress, buf.into_bump_str(), state)) + move |arena, state: State<'a>| match crate::ident::tag_name().parse(arena, state) { + Ok(good) => Ok(good), + Err((progress, _, state)) => Err((progress, to_problem(state.line, state.column), state)), } } @@ -517,156 +408,52 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located fn parse_concrete_type<'a>( arena: &'a Bump, - mut state: State<'a>, + state: State<'a>, ) -> ParseResult<'a, TypeAnnotation<'a>, TApply> { - let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) - let mut parts: Vec<&'a str> = Vec::new_in(arena); + let initial_bytes = state.bytes; - // Qualified types must start with a capitalized letter. - match peek_utf8_char_e(&state, TApply::StartNotUppercase, TApply::Space) { - Ok((first_letter, bytes_parsed)) => { - if first_letter.is_alphabetic() && first_letter.is_uppercase() { - part_buf.push(first_letter); - } else { - let problem = TApply::StartNotUppercase(state.line, state.column + 1); - return Err((NoProgress, problem, state)); - } + match crate::ident::concrete_type().parse(arena, state) { + Ok((_, (module_name, type_name), state)) => { + let answer = TypeAnnotation::Apply(module_name, type_name, &[]); - state = state.advance_without_indenting_e(bytes_parsed, TApply::Space)?; + Ok((MadeProgress, answer, state)) } - Err(reason) => return Err((NoProgress, reason, state)), - } + Err((NoProgress, _, state)) => { + Err((NoProgress, TApply::End(state.line, state.column), state)) + } + Err((MadeProgress, _, mut state)) => { + // we made some progress, but ultimately failed. + // that means a malformed type name + let chomped = crate::ident::chomp_malformed(state.bytes); + let delta = initial_bytes.len() - state.bytes.len(); + let parsed_str = + unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) }; - while !state.bytes.is_empty() { - match peek_utf8_char_e(&state, TApply::End, TApply::Space) { - Ok((ch, bytes_parsed)) => { - // After the first character, only these are allowed: - // - // * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers - // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() - // * A dot ('.') - if ch.is_alphabetic() { - if part_buf.is_empty() && !ch.is_uppercase() { - // Each part must begin with a capital letter. - return Err(( - MadeProgress, - TApply::StartNotUppercase(state.line, state.column), - state, - )); - } + state = state.advance_without_indenting_ee(chomped, |r, c| { + TApply::Space(crate::parser::BadInputError::LineTooLong, r, c) + })?; - part_buf.push(ch); - } else if ch.is_ascii_digit() { - // Parts may not start with numbers! - if part_buf.is_empty() { - return Err(( - MadeProgress, - TApply::StartIsNumber(state.line, state.column), - state, - )); - } + dbg!(&state); - part_buf.push(ch); - } else if ch == '.' { - // Having two consecutive dots is an error. - if part_buf.is_empty() { - return Err(( - MadeProgress, - TApply::DoubleDot(state.line, state.column), - state, - )); - } - - parts.push(part_buf.into_bump_str()); - - // Now that we've recorded the contents of the current buffer, reset it. - part_buf = String::new_in(arena); - } else { - // This must be the end of the type. We're done! - break; - } - - state = state.advance_without_indenting_e(bytes_parsed, TApply::Space)?; - } - Err(reason) => { - return Err((MadeProgress, reason, state)); - } + Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state)) } } - - if part_buf.is_empty() { - // We probably had a trailing dot, e.g. `Foo.bar.` - this is malformed! - // - // This condition might also occur if we encounter a malformed accessor like `.|` - // - // If we made it this far and don't have a next_char, then necessarily - // we have consumed a '.' char previously. - return Err(( - MadeProgress, - TApply::TrailingDot(state.line, state.column), - state, - )); - } - - let answer = TypeAnnotation::Apply( - join_module_parts(arena, parts.into_bump_slice()), - part_buf.into_bump_str(), - &[], - ); - - Ok((MadeProgress, answer, state)) } fn parse_type_variable<'a>( arena: &'a Bump, - mut state: State<'a>, -) -> ParseResult<'a, TypeAnnotation<'a>, TVariable> { - let mut buf = String::new_in(arena); + state: State<'a>, +) -> ParseResult<'a, TypeAnnotation<'a>, Type<'a>> { + match crate::ident::lowercase_ident().parse(arena, state) { + Ok((_, name, state)) => { + let answer = TypeAnnotation::BoundVariable(name); - let start_bytes_len = state.bytes.len(); - - match peek_utf8_char_e(&state, TVariable::StartNotLowercase, TVariable::Space) { - Ok((first_letter, bytes_parsed)) => { - // Type variables must start with a lowercase letter. - if first_letter.is_alphabetic() && first_letter.is_lowercase() { - buf.push(first_letter); - } else { - return Err(( - NoProgress, - TVariable::StartNotLowercase(state.line, state.column), - state, - )); - } - - state = state.advance_without_indenting_e(bytes_parsed, TVariable::Space)?; + Ok((MadeProgress, answer, state)) } - Err(reason) => return Err((NoProgress, reason, state)), + Err((progress, _, state)) => Err(( + progress, + Type::TBadTypeVariable(state.line, state.column), + state, + )), } - - while !state.bytes.is_empty() { - match peek_utf8_char_e(&state, TVariable::End, TVariable::Space) { - Ok((ch, bytes_parsed)) => { - // After the first character, only these are allowed: - // - // * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers - // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() - if ch.is_alphabetic() || ch.is_ascii_digit() { - buf.push(ch); - } else { - // This must be the end of the type. We're done! - break; - } - - state = state.advance_without_indenting_e(bytes_parsed, TVariable::Space)?; - } - Err(reason) => { - return state.fail(arena, MadeProgress, reason); - } - } - } - - let answer = TypeAnnotation::BoundVariable(buf.into_bump_str()); - - let progress = Progress::from_lengths(start_bytes_len, state.bytes.len()); - Ok((progress, answer, state)) } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index f48d03df13..a152464ce0 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -23,14 +23,12 @@ mod test_parse { use roc_parse::ast::Pattern::{self, *}; use roc_parse::ast::StrLiteral::{self, *}; use roc_parse::ast::StrSegment::*; - use roc_parse::ast::{ - self, Attempting, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch, - }; + use roc_parse::ast::{self, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch}; use roc_parse::header::{ AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, PackageName, PackageOrPath, PlatformHeader, To, }; - use roc_parse::module::{app_header, interface_header, module_defs, platform_header}; + use roc_parse::module::module_defs; use roc_parse::parser::{Parser, State, SyntaxError}; use roc_parse::test_helpers::parse_expr_with; use roc_region::all::{Located, Region}; @@ -43,10 +41,9 @@ mod test_parse { assert_eq!(Ok(expected_expr), actual); } - fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError, _attempting: Attempting) { + fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError) { let arena = Bump::new(); let actual = parse_expr_with(&arena, input); - // let expected_fail = Fail { reason, attempting }; assert!(actual.is_err()); } @@ -291,7 +288,7 @@ mod test_parse { #[test] fn empty_source_file() { - assert_parsing_fails("", SyntaxError::Eof(Region::zero()), Attempting::Module); + assert_parsing_fails("", SyntaxError::Eof(Region::zero())); } #[test] @@ -308,11 +305,7 @@ mod test_parse { // Make sure it's longer than our maximum line length assert_eq!(too_long_str.len(), max_line_length + 1); - assert_parsing_fails( - &too_long_str, - SyntaxError::LineTooLong(0), - Attempting::Module, - ); + assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(0)); } // INT LITERALS @@ -1012,7 +1005,7 @@ mod test_parse { use roc_parse::ident::BadIdent; let arena = Bump::new(); - let expected = Expr::MalformedIdent("@One.Two.Whee", BadIdent::QualifiedTag(0, 13)); + let expected = Expr::MalformedIdent("@One.Two.Whee", BadIdent::BadPrivateTag(0, 4)); let actual = parse_expr_with(&arena, "@One.Two.Whee"); assert_eq!(Ok(expected), actual); @@ -2416,7 +2409,7 @@ mod test_parse { let imports = Vec::new_in(&arena); let provides = Vec::new_in(&arena); let module_name = StrLiteral::PlainLine("test-app"); - let expected = AppHeader { + let header = AppHeader { name: Located::new(0, 0, 4, 14, module_name), packages, imports, @@ -2433,17 +2426,15 @@ mod test_parse { after_to: &[], }; + let expected = roc_parse::ast::Module::App { header }; + let src = indoc!( r#" app "test-app" packages {} imports [] provides [] to blah "# ); - let actual = app_header() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert_eq!(Ok(expected), actual); } @@ -2457,7 +2448,7 @@ mod test_parse { let imports = Vec::new_in(&arena); let provides = Vec::new_in(&arena); let module_name = StrLiteral::PlainLine("test-app"); - let expected = AppHeader { + let header = AppHeader { name: Located::new(0, 0, 4, 14, module_name), packages, imports, @@ -2474,17 +2465,16 @@ mod test_parse { after_to: &[], }; + let expected = roc_parse::ast::Module::App { header }; + let src = indoc!( r#" app "test-app" provides [] to "./blah" "# ); - let actual = app_header() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + + let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert_eq!(Ok(expected), actual); } @@ -2509,7 +2499,8 @@ mod test_parse { let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort")); let provides = bumpalo::vec![in &arena; provide_entry]; let module_name = StrLiteral::PlainLine("quicksort"); - let expected = AppHeader { + + let header = AppHeader { name: Located::new(0, 0, 4, 15, module_name), packages, imports, @@ -2526,6 +2517,8 @@ mod test_parse { after_to: &[], }; + let expected = roc_parse::ast::Module::App { header }; + let src = indoc!( r#" app "quicksort" @@ -2535,12 +2528,8 @@ mod test_parse { "# ); - let actual = app_header() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert_eq!(Ok(expected), actual); } @@ -2560,7 +2549,7 @@ mod test_parse { spaces_after_effects_keyword: &[], spaces_after_type_name: &[], }; - let expected = PlatformHeader { + let header = PlatformHeader { name: Located::new(0, 0, 9, 23, pkg_name), requires: Vec::new_in(&arena), exposes: Vec::new_in(&arena), @@ -2581,13 +2570,11 @@ mod test_parse { after_provides: &[], }; + let expected = roc_parse::ast::Module::Platform { header }; + let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}"; - let actual = platform_header() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert_eq!(Ok(expected), actual); } @@ -2621,7 +2608,7 @@ mod test_parse { spaces_after_effects_keyword: &[], spaces_after_type_name: &[], }; - let expected = PlatformHeader { + let header = PlatformHeader { name: Located::new(0, 0, 9, 19, pkg_name), requires: Vec::new_in(&arena), exposes: Vec::new_in(&arena), @@ -2642,6 +2629,8 @@ mod test_parse { after_provides: &[], }; + let expected = roc_parse::ast::Module::Platform { header }; + let src = indoc!( r#" platform foo/barbaz @@ -2653,12 +2642,8 @@ mod test_parse { effects fx.Effect {} "# ); - let actual = platform_header() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert_eq!(Ok(expected), actual); } @@ -2669,7 +2654,7 @@ mod test_parse { let exposes = Vec::new_in(&arena); let imports = Vec::new_in(&arena); let module_name = ModuleName::new("Foo"); - let expected = InterfaceHeader { + let header = InterfaceHeader { name: Located::new(0, 0, 10, 13, module_name), exposes, imports, @@ -2680,17 +2665,16 @@ mod test_parse { before_imports: &[], after_imports: &[], }; + + let expected = roc_parse::ast::Module::Interface { header }; + let src = indoc!( r#" interface Foo exposes [] imports [] "# ); - let actual = interface_header() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert_eq!(Ok(expected), actual); } @@ -2701,7 +2685,7 @@ mod test_parse { let exposes = Vec::new_in(&arena); let imports = Vec::new_in(&arena); let module_name = ModuleName::new("Foo.Bar.Baz"); - let expected = InterfaceHeader { + let header = InterfaceHeader { name: Located::new(0, 0, 10, 21, module_name), exposes, imports, @@ -2712,17 +2696,16 @@ mod test_parse { before_imports: &[], after_imports: &[], }; + + let expected = roc_parse::ast::Module::Interface { header }; + let src = indoc!( r#" interface Foo.Bar.Baz exposes [] imports [] "# ); - let actual = interface_header() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert_eq!(Ok(expected), actual); } @@ -2748,10 +2731,7 @@ mod test_parse { "# ); let actual = module_defs() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) + .parse(&arena, State::new(src.as_bytes())) .map(|tuple| tuple.1); // It should occur twice in the debug output - once for the pattern, @@ -2810,10 +2790,7 @@ mod test_parse { ); let actual = module_defs() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) + .parse(&arena, State::new(src.as_bytes())) .map(|tuple| tuple.1); assert_eq!(Ok(expected), actual); @@ -2833,11 +2810,8 @@ mod test_parse { ); let actual = module_defs() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + .parse(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert!(actual.is_ok()); } @@ -2858,18 +2832,15 @@ mod test_parse { ); let actual = module_defs() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); + .parse(&arena, State::new(src.as_bytes())) + .map(|tuple| tuple.0); assert!(actual.is_ok()); } #[test] fn outdenting_newline_after_else() { - let arena = Bump::new(); + let arena = &Bump::new(); // highlights a problem with the else branch demanding a newline after its expression let src = indoc!( @@ -2881,16 +2852,19 @@ mod test_parse { "# ); - let actual = module_defs() - .parse( - &arena, - State::new_in(&arena, src.as_bytes(), Attempting::Module), - ) - .map(|tuple| tuple.1); - - dbg!(&actual); - - assert!(actual.is_ok()); + let state = State::new(src.as_bytes()); + let parser = module_defs(); + let parsed = parser.parse(arena, state); + match parsed { + Ok((_, _, state)) => { + dbg!(state); + return; + } + Err((_, _fail, _state)) => { + dbg!(_fail, _state); + assert!(false); + } + } } #[test] diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index 85fc4da8d1..9ccd46c4c0 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -1,4 +1,5 @@ use roc_collections::all::MutSet; +use roc_parse::parser::{Col, Row}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_region::all::Region; @@ -357,23 +358,7 @@ fn to_bad_ident_expr_report<'b>( let region = Region::from_row_col(row, col); alloc.stack(vec![ - alloc.reflow(r"I trying to parse a record field accessor here:"), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow("Something like "), - alloc.parser_suggestion(".name"), - alloc.reflow(" or "), - alloc.parser_suggestion(".height"), - alloc.reflow(" that accesses a value from a record."), - ]), - ]) - } - - PartStartsWithNumber(row, col) => { - let region = Region::from_row_col(row, col); - - alloc.stack(vec![ - alloc.reflow("I trying to parse a record field access here:"), + alloc.reflow(r"I trying to parse a record field access here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("So I expect to see a lowercase letter next, like "), @@ -430,34 +415,73 @@ fn to_bad_ident_expr_report<'b>( ]), ]) } - PrivateTagNotUppercase(row, col) => { - let region = Region::from_row_col(row, col); + Underscore(row, col) => { + let region = + Region::from_rows_cols(surroundings.start_line, surroundings.start_col, row, col); alloc.stack(vec![ - alloc.reflow("I am trying to parse a private tag here:"), + alloc.reflow("Underscores are not allowed in identifier names:"), alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow(r"But after the "), - alloc.keyword("@"), - alloc.reflow(r" symbol I found a lowercase letter. "), - alloc.reflow(r"All tag names (global and private)"), - alloc.reflow(r" must start with an uppercase letter, like "), - alloc.parser_suggestion("@UUID"), - alloc.reflow(" or "), - alloc.parser_suggestion("@Secrets"), - alloc.reflow("."), - ]), + alloc.concat(vec![alloc.reflow( + r"I recommend using camelCase, it is the standard in the Roc ecosystem.", + )]), ]) } - PrivateTagFieldAccess(_row, _col) => alloc.stack(vec![ - alloc.reflow("I am very confused by this field access:"), - alloc.region(surroundings), - alloc.concat(vec![ - alloc.reflow(r"It looks like a record field access on a private tag.") - ]), - ]), - _ => todo!(), + BadPrivateTag(row, col) => { + use BadIdentNext::*; + match what_is_next(alloc.src_lines, row, col) { + LowercaseAccess(width) => { + let region = Region::from_rows_cols(row, col, row, col + width); + alloc.stack(vec![ + alloc.reflow("I am very confused by this field access:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow(r"It looks like a record field access on a private tag.") + ]), + ]) + } + UppercaseAccess(width) => { + let region = Region::from_rows_cols(row, col, row, col + width); + alloc.stack(vec![ + alloc.reflow("I am very confused by this expression:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow( + r"Looks like a private tag is treated like a module name. ", + ), + alloc.reflow(r"Maybe you wanted a qualified name, like "), + alloc.parser_suggestion("Json.Decode.string"), + alloc.text("?"), + ]), + ]) + } + Other(Some(c)) if c.is_lowercase() => { + let region = Region::from_rows_cols( + surroundings.start_line, + surroundings.start_col + 1, + row, + col + 1, + ); + alloc.stack(vec![ + alloc.reflow("I am trying to parse a private tag here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow(r"But after the "), + alloc.keyword("@"), + alloc.reflow(r" symbol I found a lowercase letter. "), + alloc.reflow(r"All tag names (global and private)"), + alloc.reflow(r" must start with an uppercase letter, like "), + alloc.parser_suggestion("@UUID"), + alloc.reflow(" or "), + alloc.parser_suggestion("@Secrets"), + alloc.reflow("."), + ]), + ]) + } + other => todo!("{:?}", other), + } + } } } @@ -486,22 +510,6 @@ fn to_bad_ident_pattern_report<'b>( ]) } - PartStartsWithNumber(row, col) => { - let region = Region::from_row_col(row, col); - - alloc.stack(vec![ - alloc.reflow("I trying to parse a record field access here:"), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow("So I expect to see a lowercase letter next, like "), - alloc.parser_suggestion(".name"), - alloc.reflow(" or "), - alloc.parser_suggestion(".height"), - alloc.reflow("."), - ]), - ]) - } - WeirdAccessor(_row, _col) => alloc.stack(vec![ alloc.reflow("I am very confused by this field access"), alloc.region(surroundings), @@ -547,33 +555,6 @@ fn to_bad_ident_pattern_report<'b>( ]), ]) } - PrivateTagNotUppercase(row, col) => { - let region = Region::from_row_col(row, col); - - alloc.stack(vec![ - alloc.reflow("I am trying to parse a private tag here:"), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow(r"But after the "), - alloc.keyword("@"), - alloc.reflow(r" symbol I found a lowercase letter. "), - alloc.reflow(r"All tag names (global and private)"), - alloc.reflow(r" must start with an uppercase letter, like "), - alloc.parser_suggestion("@UUID"), - alloc.reflow(" or "), - alloc.parser_suggestion("@Secrets"), - alloc.reflow("."), - ]), - ]) - } - - PrivateTagFieldAccess(_row, _col) => alloc.stack(vec![ - alloc.reflow("I am very confused by this field access:"), - alloc.region(surroundings), - alloc.concat(vec![ - alloc.reflow(r"It looks like a record field access on a private tag.") - ]), - ]), Underscore(row, col) => { let region = Region::from_row_col(row, col - 1); @@ -591,6 +572,69 @@ fn to_bad_ident_pattern_report<'b>( } } +#[derive(Debug)] +enum BadIdentNext<'a> { + LowercaseAccess(u16), + UppercaseAccess(u16), + NumberAccess(u16), + Keyword(&'a str), + DanglingDot, + Other(Option), +} + +fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> BadIdentNext<'a> { + let row_index = row as usize; + let col_index = col as usize; + match source_lines.get(row_index) { + None => BadIdentNext::Other(None), + Some(line) => { + let chars = &line[col_index..]; + let mut it = chars.chars(); + + match roc_parse::keyword::KEYWORDS + .iter() + .find(|keyword| crate::error::parse::starts_with_keyword(chars, keyword)) + { + Some(keyword) => BadIdentNext::Keyword(keyword), + None => match it.next() { + None => BadIdentNext::Other(None), + Some('.') => match it.next() { + Some(c) if c.is_lowercase() => { + BadIdentNext::LowercaseAccess(2 + till_whitespace(it) as u16) + } + Some(c) if c.is_uppercase() => { + BadIdentNext::UppercaseAccess(2 + till_whitespace(it) as u16) + } + Some(c) if c.is_ascii_digit() => { + BadIdentNext::NumberAccess(2 + till_whitespace(it) as u16) + } + _ => BadIdentNext::DanglingDot, + }, + Some(c) => BadIdentNext::Other(Some(c)), + }, + } + } + } +} + +fn till_whitespace(it: I) -> usize +where + I: Iterator, +{ + let mut chomped = 0; + + for c in it { + if c.is_ascii_whitespace() || c == '#' { + break; + } else { + chomped += 1; + continue; + } + } + + chomped +} + fn pretty_runtime_error<'b>( alloc: &'b RocDocAllocator<'b>, runtime_error: RuntimeError, diff --git a/compiler/reporting/src/error/parse.rs b/compiler/reporting/src/error/parse.rs index c9a412aac7..1a46b03b87 100644 --- a/compiler/reporting/src/error/parse.rs +++ b/compiler/reporting/src/error/parse.rs @@ -153,6 +153,7 @@ fn to_syntax_report<'a>( 0, 0, ), + Header(header) => to_header_report(alloc, filename, &header, 0, 0), _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -171,6 +172,8 @@ enum Node { IfElseBranch, ListElement, InsideParens, + RecordConditionalDefault, + StringFormat, } fn to_expr_report<'a>( @@ -197,14 +200,12 @@ fn to_expr_report<'a>( to_expr_in_parens_report(alloc, filename, context, &expr, *row, *col) } EExpr::Type(tipe, row, col) => to_type_report(alloc, filename, &tipe, *row, *col), - EExpr::Def(syntax, row, col) => to_syntax_report(alloc, filename, syntax, *row, *col), - EExpr::ElmStyleFunction(region, row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = *region; let doc = alloc.stack(vec![ - alloc.reflow(r"I am in the middle of parsing a definition, but I got stuck here:"), + alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), @@ -344,6 +345,8 @@ fn to_expr_report<'a>( ]), ), Node::ListElement => (r, c, alloc.text("a list")), + Node::RecordConditionalDefault => (r, c, alloc.text("record field default")), + Node::StringFormat => (r, c, alloc.text("a string format")), Node::InsideParens => (r, c, alloc.text("some parentheses")), }, Context::InDef(r, c) => (r, c, alloc.text("a definition")), @@ -375,12 +378,39 @@ fn to_expr_report<'a>( } } + EExpr::DefMissingFinalExpr(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); + let region = Region::from_row_col(*row, *col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("This definition is missing a final expression."), + alloc.reflow(" A nested definition must be followed by"), + alloc.reflow(" either another definition, or an expression"), + ]), + alloc.vcat(vec![ + alloc.text("x = 4").indent(4), + alloc.text("y = 2").indent(4), + alloc.text("").indent(4), + alloc.text("x + y").indent(4), + ]), + ]); + + Report { + filename, + doc, + title: "MISSING FINAL EXPRESSION".to_string(), + } + } + EExpr::Colon(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); let doc = alloc.stack(vec![ - alloc.reflow(r"I am in the middle of parsing a definition, but I got stuck here:"), + alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), @@ -419,7 +449,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc - .reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"), + .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I was expecting a "), @@ -440,7 +470,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc - .reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"), + .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I was expecting a "), @@ -464,7 +494,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc - .reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"), + .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I was expecting a "), @@ -485,7 +515,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc - .reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"), + .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I was expecting a "), @@ -509,7 +539,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc - .reflow(r"I am in the middle of parsing a function argument list, but I got stuck at this comma:"), + .reflow(r"I am partway through parsing a function argument list, but I got stuck at this comma:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), @@ -529,7 +559,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc - .reflow(r"I am in the middle of parsing a function argument list, but I got stuck here:"), + .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), @@ -636,7 +666,7 @@ fn to_unfinished_lambda_report<'a>( fn to_str_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, - _context: Context, + context: Context, parse_problem: &roc_parse::parser::EString<'a>, start_row: Row, start_col: Col, @@ -645,7 +675,14 @@ fn to_str_report<'a>( match *parse_problem { EString::Open(_row, _col) => unreachable!("another branch would be taken"), - EString::Format(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col), + EString::Format(expr, row, col) => to_expr_report( + alloc, + filename, + Context::InNode(Node::StringFormat, start_row, start_col, Box::new(context)), + expr, + row, + col, + ), EString::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), EString::UnknownEscape(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); @@ -712,6 +749,26 @@ fn to_str_report<'a>( title: "WEIRD CODE POINT".to_string(), } } + EString::FormatEnd(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I cannot find the end of this format expression:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow(r"You could change it to something like "), + alloc.parser_suggestion("\"The count is \\(count\\)\""), + alloc.reflow("."), + ]), + ]); + + Report { + filename, + doc, + title: "ENDLESS FORMAT".to_string(), + } + } EString::EndlessSingle(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -839,7 +896,6 @@ fn to_list_report<'a>( use roc_parse::parser::List; match *parse_problem { - List::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col), List::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), List::Expr(expr, row, col) => to_expr_report( @@ -948,7 +1004,6 @@ fn to_if_report<'a>( use roc_parse::parser::If; match *parse_problem { - If::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col), If::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), If::Condition(expr, row, col) => to_expr_report( @@ -1119,7 +1174,6 @@ fn to_when_report<'a>( } } - When::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col), When::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), When::Branch(expr, row, col) => to_expr_report( @@ -1512,7 +1566,20 @@ fn to_precord_report<'a>( PRecord::Pattern(pattern, row, col) => { to_pattern_report(alloc, filename, pattern, row, col) } - PRecord::Syntax(syntax, row, col) => to_syntax_report(alloc, filename, syntax, row, col), + + PRecord::Expr(expr, row, col) => to_expr_report( + alloc, + filename, + Context::InNode( + Node::RecordConditionalDefault, + start_row, + start_col, + Box::new(Context::InDef(row, col)), + ), + expr, + row, + col, + ), PRecord::IndentOpen(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); @@ -2469,6 +2536,479 @@ fn to_tapply_report<'a>( } } +fn to_header_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + parse_problem: &roc_parse::parser::EHeader<'a>, + start_row: Row, + start_col: Col, +) -> Report<'a> { + use roc_parse::parser::EHeader; + + match parse_problem { + EHeader::Provides(provides, row, col) => { + to_provides_report(alloc, filename, &provides, *row, *col) + } + + EHeader::Exposes(exposes, row, col) => { + to_exposes_report(alloc, filename, &exposes, *row, *col) + } + + EHeader::Imports(imports, row, col) => { + to_imports_report(alloc, filename, &imports, *row, *col) + } + + EHeader::Requires(requires, row, col) => { + to_requires_report(alloc, filename, &requires, *row, *col) + } + + EHeader::Packages(packages, row, col) => { + to_packages_report(alloc, filename, &packages, *row, *col) + } + + EHeader::Effects(effects, row, col) => { + to_effects_report(alloc, filename, &effects, *row, *col) + } + + EHeader::IndentStart(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); + let region = Region::from_row_col(*row, *col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![alloc.reflow("I may be confused by indentation.")]), + ]); + + Report { + filename, + doc, + title: "INCOMPLETE HEADER".to_string(), + } + } + + EHeader::Start(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); + let region = Region::from_row_col(*row, *col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am expecting a header, but got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting a module keyword next, one of "), + alloc.keyword("interface"), + alloc.reflow(", "), + alloc.keyword("app"), + alloc.reflow(" or "), + alloc.keyword("platform"), + alloc.reflow("."), + ]), + ]); + + Report { + filename, + doc, + title: "MISSING HEADER".to_string(), + } + } + + EHeader::ModuleName(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); + let region = Region::from_row_col(*row, *col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting a module name next, like "), + alloc.parser_suggestion("BigNum"), + alloc.reflow(" or "), + alloc.parser_suggestion("Main"), + alloc.reflow(". Module names must start with an uppercase letter."), + ]), + ]); + + Report { + filename, + doc, + title: "WEIRD MODULE NAME".to_string(), + } + } + + EHeader::AppName(_, row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); + let region = Region::from_row_col(*row, *col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting an application name next, like "), + alloc.parser_suggestion("app \"main\""), + alloc.reflow(" or "), + alloc.parser_suggestion("app \"editor\""), + alloc.reflow(". App names are surrounded by quotation marks."), + ]), + ]); + + Report { + filename, + doc, + title: "WEIRD APP NAME".to_string(), + } + } + + EHeader::PlatformName(_, row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); + let region = Region::from_row_col(*row, *col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting a platform name next, like "), + alloc.parser_suggestion("roc/core"), + alloc.reflow("."), + ]), + ]); + + Report { + filename, + doc, + title: "WEIRD MODULE NAME".to_string(), + } + } + + EHeader::Space(error, row, col) => to_space_report(alloc, filename, &error, *row, *col), + } +} + +fn to_provides_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + parse_problem: &roc_parse::parser::EProvides, + start_row: Row, + start_col: Col, +) -> Report<'a> { + use roc_parse::parser::EProvides; + + match *parse_problem { + EProvides::Identifier(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc + .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![alloc.reflow( + "I was expecting a type name, value name or function name next, like ", + )]), + alloc + .parser_suggestion("provides [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD PROVIDES".to_string(), + } + } + + EProvides::Provides(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("provides"), + alloc.reflow(" keyword next, like "), + ]), + alloc + .parser_suggestion("provides [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD PROVIDES".to_string(), + } + } + + EProvides::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + + _ => todo!("unhandled parse error {:?}", parse_problem), + } +} + +fn to_exposes_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + parse_problem: &roc_parse::parser::EExposes, + start_row: Row, + start_col: Col, +) -> Report<'a> { + use roc_parse::parser::EExposes; + + match *parse_problem { + EExposes::Identifier(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a exposes list, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![alloc.reflow( + "I was expecting a type name, value name or function name next, like ", + )]), + alloc + .parser_suggestion("exposes [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD EXPOSES".to_string(), + } + } + + EExposes::Exposes(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("exposes"), + alloc.reflow(" keyword next, like "), + ]), + alloc + .parser_suggestion("exposes [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD EXPOSES".to_string(), + } + } + + EExposes::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + + _ => todo!("unhandled parse error {:?}", parse_problem), + } +} + +fn to_imports_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + parse_problem: &roc_parse::parser::EImports, + start_row: Row, + start_col: Col, +) -> Report<'a> { + use roc_parse::parser::EImports; + + match *parse_problem { + EImports::Identifier(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![alloc.reflow( + "I was expecting a type name, value name or function name next, like ", + )]), + alloc + .parser_suggestion("imports [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD EXPOSES".to_string(), + } + } + + EImports::Imports(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("imports"), + alloc.reflow(" keyword next, like "), + ]), + alloc + .parser_suggestion("imports [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD IMPORTS".to_string(), + } + } + + EImports::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + + EImports::ModuleName(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting a module name next, like "), + alloc.parser_suggestion("BigNum"), + alloc.reflow(" or "), + alloc.parser_suggestion("Main"), + alloc.reflow(". Module names must start with an uppercase letter."), + ]), + ]); + + Report { + filename, + doc, + title: "WEIRD MODULE NAME".to_string(), + } + } + + _ => todo!("unhandled parse error {:?}", parse_problem), + } +} + +fn to_requires_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + parse_problem: &roc_parse::parser::ERequires<'a>, + start_row: Row, + start_col: Col, +) -> Report<'a> { + use roc_parse::parser::ERequires; + + match *parse_problem { + ERequires::Requires(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("requires"), + alloc.reflow(" keyword next, like "), + ]), + alloc + .parser_suggestion("requires { main : Task I64 Str }") + .indent(4), + ]); + + Report { + filename, + doc, + title: "MISSING REQUIRES".to_string(), + } + } + + ERequires::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + + _ => todo!("unhandled parse error {:?}", parse_problem), + } +} + +fn to_packages_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + parse_problem: &roc_parse::parser::EPackages, + start_row: Row, + start_col: Col, +) -> Report<'a> { + use roc_parse::parser::EPackages; + + match *parse_problem { + EPackages::Packages(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("packages"), + alloc.reflow(" keyword next, like "), + ]), + alloc.parser_suggestion("packages {}").indent(4), + ]); + + Report { + filename, + doc, + title: "MISSING PACKAGES".to_string(), + } + } + + EPackages::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + + _ => todo!("unhandled parse error {:?}", parse_problem), + } +} + +fn to_effects_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + parse_problem: &roc_parse::parser::EEffects, + start_row: Row, + start_col: Col, +) -> Report<'a> { + use roc_parse::parser::EEffects; + + match *parse_problem { + EEffects::Effects(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("effects"), + alloc.reflow(" keyword next, like "), + ]), + alloc.parser_suggestion("effects {}").indent(4), + ]); + + Report { + filename, + doc, + title: "MISSING PACKAGES".to_string(), + } + } + + EEffects::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + + _ => todo!("unhandled parse error {:?}", parse_problem), + } +} + fn to_space_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, @@ -2538,7 +3078,7 @@ fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a> } } -fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool { +pub fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool { if let Some(stripped) = rest_of_line.strip_prefix(keyword) { match stripped.chars().next() { None => true, diff --git a/compiler/reporting/tests/helpers/mod.rs b/compiler/reporting/tests/helpers/mod.rs index 7bc49f0421..3cade3ded1 100644 --- a/compiler/reporting/tests/helpers/mod.rs +++ b/compiler/reporting/tests/helpers/mod.rs @@ -11,9 +11,6 @@ use roc_collections::all::{ImMap, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; -use roc_parse::ast::{self, Attempting}; -use roc_parse::blankspace::space0_before; -use roc_parse::parser::{loc, Parser, State, SyntaxError}; use roc_problem::can::Problem; use roc_region::all::Located; use roc_solve::solve; @@ -100,28 +97,9 @@ pub struct CanExprOut { pub constraint: Constraint, } -#[allow(dead_code)] -pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, SyntaxError<'a>> { - parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) -} - -#[allow(dead_code)] -pub fn parse_loc_with<'a>( - arena: &'a Bump, - input: &'a str, -) -> Result>, SyntaxError<'a>> { - let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module); - let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); - let answer = parser.parse(&arena, state); - - answer - .map(|(_, loc_expr, _)| loc_expr) - .map_err(|(_, fail, _)| fail) -} - #[derive(Debug)] pub struct ParseErrOut<'a> { - pub fail: SyntaxError<'a>, + pub fail: roc_parse::parser::SyntaxError<'a>, pub home: ModuleId, pub interns: Interns, } @@ -132,7 +110,7 @@ pub fn can_expr_with<'a>( home: ModuleId, expr_str: &'a str, ) -> Result> { - let loc_expr = match parse_loc_with(&arena, expr_str) { + let loc_expr = match roc_parse::test_helpers::parse_loc_with(&arena, expr_str) { Ok(e) => e, Err(fail) => { let interns = Interns::default(); diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index f4bd0aca8b..2a0f18dd5e 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -169,6 +169,37 @@ mod test_reporting { } } + fn list_header_reports(arena: &Bump, src: &str, buf: &mut String, callback: F) + where + F: FnOnce(RocDocBuilder<'_>, &mut String), + { + use ven_pretty::DocAllocator; + + use roc_parse::parser::State; + + let state = State::new(src.as_bytes()); + + let filename = filename_from_string(r"\code\proj\Main.roc"); + let src_lines: Vec<&str> = src.split('\n').collect(); + + match roc_parse::module::parse_header(arena, state) { + Err(fail) => { + let interns = Interns::default(); + let home = crate::helpers::test_home(); + + let alloc = RocDocAllocator::new(&src_lines, home, &interns); + + use roc_parse::parser::SyntaxError; + let problem = + SyntaxError::Header(fail).into_parse_problem(filename.clone(), src.as_bytes()); + let doc = parse_problem(&alloc, filename, 0, problem); + + callback(doc.pretty(&alloc).append(alloc.line()), buf) + } + Ok(_) => todo!(), + } + } + fn report_problem_as(src: &str, expected_rendering: &str) { let mut buf: String = String::new(); let arena = Bump::new(); @@ -193,6 +224,30 @@ mod test_reporting { assert_eq!(buf, expected_rendering); } + fn report_header_problem_as(src: &str, expected_rendering: &str) { + let mut buf: String = String::new(); + let arena = Bump::new(); + + let callback = |doc: RocDocBuilder<'_>, buf: &mut String| { + doc.1 + .render_raw(70, &mut roc_reporting::report::CiWrite::new(buf)) + .expect("list_reports") + }; + + list_header_reports(&arena, src, &mut buf, callback); + + // convenient to copy-paste the generated message + if true { + if buf != expected_rendering { + for line in buf.split("\n") { + println!(" {}", line); + } + } + } + + assert_eq!(buf, expected_rendering); + } + fn color_report_problem_as(src: &str, expected_rendering: &str) { let mut buf: String = String::new(); let arena = Bump::new(); @@ -3160,7 +3215,7 @@ mod test_reporting { r#" ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── - I am in the middle of parsing a definition, but I got stuck here: + I am partway through parsing a definition, but I got stuck here: 1│ f x y = x ^^^ @@ -4069,13 +4124,13 @@ mod test_reporting { indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - I trying to parse a record field accessor here: - + + I trying to parse a record field access here: + 1│ foo.bar. ^ - - Something like .name or .height that accesses a value from a record. + + So I expect to see a lowercase letter next, like .name or .height. "# ), ) @@ -4092,15 +4147,14 @@ mod test_reporting { indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - I am trying to parse a qualified name here: - + + I am very confused by this expression: + 1│ @Foo.Bar - ^ - - This looks like a qualified tag name to me, but tags cannot be - qualified! Maybe you wanted a qualified name, something like - Json.Decode.string? + ^^^^ + + Looks like a private tag is treated like a module name. Maybe you + wanted a qualified name, like Json.Decode.string? "# ), ) @@ -4508,21 +4562,21 @@ mod test_reporting { indoc!( r#" f : Foo..Bar + + f "# ), - indoc!( - r#" - ── DOUBLE DOT ────────────────────────────────────────────────────────────────── - - I encountered two dots in a row: - - 1│ f : Foo..Bar - ^ - - Try removing one of them. - "# - ), + indoc!(r#""#), ) + + // ── DOUBLE DOT ────────────────────────────────────────────────────────────────── + // + // I encountered two dots in a row: + // + // 1│ f : Foo..Bar + // ^ + // + // Try removing one of them. } #[test] @@ -4531,22 +4585,22 @@ mod test_reporting { indoc!( r#" f : Foo.Bar. + + f "# ), - indoc!( - r#" - ── TRAILING DOT ──────────────────────────────────────────────────────────────── - - I encountered a dot with nothing after it: - - 1│ f : Foo.Bar. - ^ - - Dots are used to refer to a type in a qualified way, like - Num.I64 or List.List a. Try adding a type name next. - "# - ), + indoc!(r#""#), ) + + // ── TRAILING DOT ──────────────────────────────────────────────────────────────── + // + // I encountered a dot with nothing after it: + // + // 1│ f : Foo.Bar. + // ^ + // + // Dots are used to refer to a type in a qualified way, like + // Num.I64 or List.List a. Try adding a type name next. } #[test] @@ -4582,26 +4636,40 @@ mod test_reporting { indoc!( r#" f : Foo.1 + + f "# ), - indoc!( - r#" - ── WEIRD QUALIFIED NAME ──────────────────────────────────────────────────────── - - I encountered a number at the start of a qualified name segment: - - 1│ f : Foo.1 - ^ - - All parts of a qualified type name must start with an uppercase - letter, like Num.I64 or List.List a. - "# - ), + indoc!(r#""#), ) + + // ── WEIRD QUALIFIED NAME ──────────────────────────────────────────────────────── + // + // I encountered a number at the start of a qualified name segment: + // + // 1│ f : Foo.1 + // ^ + // + // All parts of a qualified type name must start with an uppercase + // letter, like Num.I64 or List.List a. } #[test] fn type_apply_start_with_lowercase() { + report_problem_as( + indoc!( + r#" + f : Foo.foo + + f + "# + ), + indoc!(r#""#), + ) + } + + #[test] + fn def_missing_final_expression() { report_problem_as( indoc!( r#" @@ -4610,16 +4678,20 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD QUALIFIED NAME ──────────────────────────────────────────────────────── - - I encountered a lowercase letter at the start of a qualified name - segment: - + ── MISSING FINAL EXPRESSION ──────────────────────────────────────────────────── + + I am partway through parsing a definition, but I got stuck here: + 1│ f : Foo.foo - ^ - - All parts of a qualified type name must start with an uppercase - letter, like Num.I64 or List.List a. + ^ + + This definition is missing a final expression. A nested definition + must be followed by either another definition, or an expression + + x = 4 + y = 2 + + x + y "# ), ) @@ -4983,13 +5055,13 @@ mod test_reporting { indoc!( r#" ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── - - I am in the middle of parsing a function argument list, but I got - stuck at this comma: - + + I am partway through parsing a function argument list, but I got stuck + at this comma: + 1│ \a,,b -> 1 ^ - + I was expecting an argument pattern before this, so try adding an argument before the comma and see if that helps? "# @@ -5008,13 +5080,13 @@ mod test_reporting { indoc!( r#" ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── - - I am in the middle of parsing a function argument list, but I got - stuck at this comma: - + + I am partway through parsing a function argument list, but I got stuck + at this comma: + 1│ \,b -> 1 ^ - + I was expecting an argument pattern before this, so try adding an argument before the comma and see if that helps? "# @@ -5416,13 +5488,13 @@ mod test_reporting { indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - I trying to parse a record field accessor here: - + + I trying to parse a record field access here: + 1│ Num.add . 23 ^ - - Something like .name or .height that accesses a value from a record. + + So I expect to see a lowercase letter next, like .name or .height. "# ), ) @@ -5468,7 +5540,7 @@ mod test_reporting { I am very confused by this field access: 1│ @UUID.bar - ^^^^^^^^^ + ^^^^ It looks like a record field access on a private tag. "# @@ -5702,4 +5774,113 @@ mod test_reporting { ), ) } + + #[test] + fn provides_to_identifier() { + report_header_problem_as( + indoc!( + r#" + app "test-base64" + packages { base: "platform" } + imports [base.Task, Base64 ] + provides [ main, @Foo ] to base + "# + ), + indoc!( + r#" + ── WEIRD PROVIDES ────────────────────────────────────────────────────────────── + + I am partway through parsing a provides list, but I got stuck here: + + 3│ imports [base.Task, Base64 ] + 4│ provides [ main, @Foo ] to base + ^ + + I was expecting a type name, value name or function name next, like + + provides [ Animal, default, tame ] + "# + ), + ) + } + + #[test] + fn exposes_identifier() { + report_header_problem_as( + indoc!( + r#" + interface Foobar + exposes [ main, @Foo ] + imports [base.Task, Base64 ] + "# + ), + indoc!( + r#" + ── WEIRD EXPOSES ─────────────────────────────────────────────────────────────── + + I am partway through parsing a exposes list, but I got stuck here: + + 1│ interface Foobar + 2│ exposes [ main, @Foo ] + ^ + + I was expecting a type name, value name or function name next, like + + exposes [ Animal, default, tame ] + "# + ), + ) + } + + #[test] + fn invalid_module_name() { + report_header_problem_as( + indoc!( + r#" + interface foobar + exposes [ main, @Foo ] + imports [base.Task, Base64 ] + "# + ), + indoc!( + r#" + ── WEIRD MODULE NAME ─────────────────────────────────────────────────────────── + + I am partway through parsing a header, but got stuck here: + + 1│ interface foobar + ^ + + I am expecting a module name next, like BigNum or Main. Module names + must start with an uppercase letter. + "# + ), + ) + } + + #[test] + fn invalid_app_name() { + report_header_problem_as( + indoc!( + r#" + app foobar + exposes [ main, @Foo ] + imports [base.Task, Base64 ] + "# + ), + indoc!( + r#" + ── WEIRD APP NAME ────────────────────────────────────────────────────────────── + + I am partway through parsing a header, but got stuck here: + + 1│ app foobar + ^ + + I am expecting an application name next, like app "main" or + app "editor". App names are surrounded by quotation marks. + "# + ), + ) + } } diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index dbf974988b..047dcfae54 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -568,6 +568,36 @@ fn list_map_closure() { ); } +#[test] +fn list_map3_group() { + assert_evals_to!( + indoc!( + r#" + List.map3 [1,2,3] [3,2,1] [2,1,3] (\a, b, c -> Group a b c) + "# + ), + RocList::from_slice(&[(1, 3, 2), (2, 2, 1), (3, 1, 3)]), + RocList<(i64, i64, i64)> + ); +} + +#[test] +fn list_map3_different_length() { + assert_evals_to!( + indoc!( + r#" + List.map3 + ["a", "b", "d"] + ["b", "x"] + ["c"] + (\a, b, c -> Str.concat a (Str.concat b c)) + "# + ), + RocList::from_slice(&[RocStr::from_slice("abc".as_bytes()),]), + RocList + ); +} + #[test] fn list_map2_pair() { assert_evals_to!( diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 4892ca4931..7009339979 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -19,29 +19,29 @@ roc_fmt = { path = "../compiler/fmt" } roc_reporting = { path = "../compiler/reporting" } # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it ven_graph = { path = "../vendor/pathfinding" } -im = "14" # im and im-rc should always have the same version! -im-rc = "14" # im and im-rc should always have the same version! +im = "15" # im and im-rc should always have the same version! +im-rc = "15" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } inlinable_string = "0.1" arraystring = "0.3.0" libc = "0.2" page_size = "0.4" -winit = "0.22" -wgpu = "0.6" +winit = "0.24" +wgpu = "0.7" glyph_brush = "0.7" log = "0.4" zerocopy = "0.3" -env_logger = "0.7" +env_logger = "0.8" futures = "0.3" -wgpu_glyph = "0.10" -cgmath = "0.17.0" +wgpu_glyph = "0.11" +cgmath = "0.18.0" snafu = { version = "0.6", features = ["backtraces"] } colored = "2" pest = "2.1" pest_derive = "2.1" ropey = "1.2.0" copypasta = "0.7.1" -indoc = "0.3.3" +indoc = "1.0" palette = "0.5" confy = { git = 'https://github.com/rust-cli/confy', features = [ "yaml_conf" @@ -53,10 +53,10 @@ version = "1.4" features = ["derive"] [dev-dependencies] -pretty_assertions = "0.5.1" +pretty_assertions = "0.6" maplit = "1.0.1" -quickcheck = "0.8" -quickcheck_macros = "0.8" +quickcheck = "1.0" +quickcheck_macros = "1.0" criterion = "0.3" rand = "0.8.2" diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index 2e03310ced..a6b84a21ce 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -66,6 +66,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * When refactoring; - Cutting and pasting code to a new file should automatically add imports to the new file and delete them from the old file. - Ability to link e.g. variable name in comments to actual variable name. Comment is automatically updated when variable name is changed. + - When updating dependencies with breaking changes; show similar diffs from github projects that have succesfully updated that dependency. - AST backed renaming, changing variable/function/type name should change it all over the codebase. * Automatically create all "arms" when pattern matching after entering `when var is` based on the type. - All `when ... is` should be updated if the type is changed, e.g. adding Indigo to the Color type should add an arm everywhere where `when color is` is used. diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 9a2592d436..8b27cf8eee 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -88,9 +88,9 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { adapter .request_device( &wgpu::DeviceDescriptor { + label: None, features: wgpu::Features::empty(), limits: wgpu::Limits::default(), - shader_validation: false, }, None, ) @@ -108,12 +108,12 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let mut size = window.inner_size(); let swap_chain_descr = wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: render_format, width: size.width, height: size.height, - //Immediate may cause tearing, change present_mode if this becomes a problem - present_mode: wgpu::PresentMode::Immediate, + // TODO go back to Immediate + present_mode: wgpu::PresentMode::Fifo, }; let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr); @@ -182,12 +182,12 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { swap_chain = gpu_device.create_swap_chain( &surface, &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format: render_format, width: size.width, height: size.height, - //Immediate may cause tearing, change present_mode if this becomes a problem - present_mode: wgpu::PresentMode::Immediate, + // TODO go back to Immediate + present_mode: wgpu::PresentMode::Fifo, }, ); @@ -376,7 +376,10 @@ fn draw_all_rects( render_pass.set_pipeline(&rect_resources.pipeline); render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); - render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..)); + render_pass.set_index_buffer( + rect_buffers.index_buffer.slice(..), + wgpu::IndexFormat::Uint32, + ); render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1); } else { // need to begin render pass to clear screen @@ -403,6 +406,7 @@ fn begin_render_pass<'a>( }, }], depth_stencil_attachment: None, + label: None, }) } diff --git a/editor/src/graphics/lowlevel/ortho.rs b/editor/src/graphics/lowlevel/ortho.rs index 115fd24a11..c3bcab80bb 100644 --- a/editor/src/graphics/lowlevel/ortho.rs +++ b/editor/src/graphics/lowlevel/ortho.rs @@ -91,8 +91,9 @@ pub fn init_ortho( entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer { - dynamic: false, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, min_binding_size: None, }, count: None, @@ -104,7 +105,7 @@ pub fn init_ortho( layout: &ortho_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::Buffer(ortho_buffer.slice(..)), + resource: ortho_buffer.as_entire_binding(), }], label: Some("Ortho bind group"), }); diff --git a/editor/src/graphics/lowlevel/pipelines.rs b/editor/src/graphics/lowlevel/pipelines.rs index 98b692fe30..9c5084b207 100644 --- a/editor/src/graphics/lowlevel/pipelines.rs +++ b/editor/src/graphics/lowlevel/pipelines.rs @@ -21,9 +21,8 @@ pub fn make_rect_pipeline( &gpu_device, &pipeline_layout, swap_chain_descr.format, - &[Vertex::DESC], - wgpu::include_spirv!("../shaders/rect.vert.spv"), - wgpu::include_spirv!("../shaders/rect.frag.spv"), + &wgpu::include_spirv!("../shaders/rect.vert.spv"), + &wgpu::include_spirv!("../shaders/rect.frag.spv"), ); RectResources { pipeline, ortho } @@ -33,9 +32,8 @@ pub fn create_render_pipeline( device: &wgpu::Device, layout: &wgpu::PipelineLayout, color_format: wgpu::TextureFormat, - vertex_descs: &[wgpu::VertexBufferDescriptor], - vs_src: wgpu::ShaderModuleSource, - fs_src: wgpu::ShaderModuleSource, + vs_src: &wgpu::ShaderModuleDescriptor, + fs_src: &wgpu::ShaderModuleDescriptor, ) -> wgpu::RenderPipeline { let vs_module = device.create_shader_module(vs_src); let fs_module = device.create_shader_module(fs_src); @@ -43,29 +41,27 @@ pub fn create_render_pipeline( device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Render pipeline"), layout: Some(&layout), - vertex_stage: wgpu::ProgrammableStageDescriptor { + vertex: wgpu::VertexState { module: &vs_module, entry_point: "main", + buffers: &[Vertex::DESC], }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + fragment: Some(wgpu::FragmentState { module: &fs_module, entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: color_format, + color_blend: wgpu::BlendState::REPLACE, + alpha_blend: wgpu::BlendState::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }], }), - rasterization_state: None, - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format: color_format, - color_blend: wgpu::BlendDescriptor::REPLACE, - alpha_blend: wgpu::BlendDescriptor::REPLACE, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, - vertex_state: wgpu::VertexStateDescriptor { - index_format: wgpu::IndexFormat::Uint32, - vertex_buffers: vertex_descs, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, }, }) } diff --git a/editor/src/graphics/lowlevel/vertex.rs b/editor/src/graphics/lowlevel/vertex.rs index 3b5274ce3f..1b6dd40c56 100644 --- a/editor/src/graphics/lowlevel/vertex.rs +++ b/editor/src/graphics/lowlevel/vertex.rs @@ -13,18 +13,18 @@ unsafe impl bytemuck::Zeroable for Vertex {} impl Vertex { pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; - pub const DESC: wgpu::VertexBufferDescriptor<'static> = wgpu::VertexBufferDescriptor { - stride: Self::SIZE, + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, step_mode: wgpu::InputStepMode::Vertex, attributes: &[ // position - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float2, }, // color - wgpu::VertexAttributeDescriptor { + wgpu::VertexAttribute { offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float4, diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs index 061fc21495..5f7d60062d 100644 --- a/editor/src/lang/expr.rs +++ b/editor/src/lang/expr.rs @@ -16,9 +16,8 @@ use roc_module::ident::ModuleName; use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_parse::ast; use roc_parse::ast::StrLiteral; -use roc_parse::ast::{self, Attempting}; -use roc_parse::blankspace::space0_before; use roc_parse::expr::expr; use roc_parse::parser::{loc, Parser, State, SyntaxError}; use roc_problem::can::{Problem, RuntimeError}; @@ -235,14 +234,10 @@ pub fn str_to_expr2<'a>( scope: &mut Scope, region: Region, ) -> Result<(Expr2, self::Output), SyntaxError<'a>> { - let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module); - let parser = space0_before(loc(expr(0)), 0); - let parse_res = parser.parse(&arena, state); - - parse_res - .map(|(_, loc_expr, _)| arena.alloc(loc_expr.value)) - .map(|loc_expr_val_ref| to_expr2(env, scope, loc_expr_val_ref, region)) - .map_err(|(_, fail, _)| fail) + match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { + Ok(loc_expr) => Ok(to_expr2(env, scope, arena.alloc(loc_expr.value), region)), + Err(fail) => Err(fail), + } } pub fn to_expr2<'a>( diff --git a/editor/src/lang/roc_file.rs b/editor/src/lang/roc_file.rs index 676c74048b..98f1a25210 100644 --- a/editor/src/lang/roc_file.rs +++ b/editor/src/lang/roc_file.rs @@ -2,7 +2,7 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_fmt::def::fmt_def; use roc_fmt::module::fmt_module; -use roc_parse::ast::{Attempting, Def, Module}; +use roc_parse::ast::{Def, Module}; use roc_parse::module::module_defs; use roc_parse::parser; use roc_parse::parser::{Parser, SyntaxError}; @@ -36,11 +36,11 @@ impl<'a> File<'a> { let allocation = arena.alloc(bytes); - let module_parse_state = parser::State::new_in(arena, allocation, Attempting::Module); - let parsed_module = roc_parse::module::header().parse(&arena, module_parse_state); + let module_parse_state = parser::State::new(allocation); + let parsed_module = roc_parse::module::parse_header(&arena, module_parse_state); match parsed_module { - Ok((_, module, state)) => { + Ok((module, state)) => { let parsed_defs = module_defs().parse(&arena, state); match parsed_defs { @@ -52,7 +52,7 @@ impl<'a> File<'a> { Err((_, error, _)) => Err(ReadError::ParseDefs(error)), } } - Err((_, error, _)) => Err(ReadError::ParseHeader(error)), + Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))), } }