diff options
51 files changed, 1563 insertions, 905 deletions
@@ -14,11 +14,19 @@ version = "0.2.0" dependencies = [ "abnf 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "annotate-snippets" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -147,6 +155,7 @@ dependencies = [ "serde_cbor 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version-sync 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -158,6 +167,7 @@ dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "version-sync 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -174,6 +184,11 @@ dependencies = [ ] [[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "dtoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -384,6 +399,16 @@ dependencies = [ [[package]] name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -636,6 +661,11 @@ dependencies = [ [[package]] name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -769,6 +799,14 @@ dependencies = [ [[package]] name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -776,6 +814,24 @@ dependencies = [ ] [[package]] +name = "pulldown-cmark" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "quote" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -826,6 +882,22 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "regex" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -931,6 +1003,11 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "semver-parser" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "serde" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -964,7 +1041,11 @@ version = "0.4.0" dependencies = [ "dhall 0.4.0", "dhall_proc_macros 0.4.0", + "doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version-sync 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1016,6 +1097,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -1038,6 +1129,14 @@ dependencies = [ ] [[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1086,6 +1185,14 @@ dependencies = [ ] [[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "tower-service" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1136,11 +1243,26 @@ dependencies = [ [[package]] name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "url" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -1155,6 +1277,21 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "version-sync" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "semver-parser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "version_check" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1310,6 +1447,7 @@ dependencies = [ [metadata] "checksum abnf 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47feb9fbcef700639ef28e04ca2a87eab8161a01a075ee227b15c90143805462" +"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" "checksum annotate-snippets 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aba2d96b8c8b5e656ad7ffb0d09f57772f10a1db74c8d23fca0ec695b38a4047" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" @@ -1329,6 +1467,7 @@ dependencies = [ "checksum ctor 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "47c5e5ac752e18207b12e16b10631ae5f7f68f8805f335f9b817ead83d9ffce1" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" "checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28" @@ -1355,6 +1494,7 @@ dependencies = [ "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum hyper 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e7b15203263d1faa615f9337d79c1d37959439dc46c2b4faab33286fadc2a1c5" "checksum hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" @@ -1385,6 +1525,7 @@ dependencies = [ "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" "checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" "checksum pest_consume 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3f016ccda869edc16802ba5feb6c4467038132067fbf914117f851ec6ac5e3a1" @@ -1401,13 +1542,18 @@ dependencies = [ "checksum pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f60c0d9f6fc88ecdd245d90c1920ff76a430ab34303fc778d33b1d0a4c3bf6d3" "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" "checksum proc-macro-hack 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)" = "f918f2b601f93baa836c1c2945faef682ba5b6d4828ecb45eeb7cc3c71b811b4" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +"checksum pulldown-cmark 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d1b74cc784b038a9921fd1a48310cc2e238101aa8ae0b94201e2d85121dd68b5" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" +"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" @@ -1418,6 +1564,7 @@ dependencies = [ "checksum security-framework-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "06fd2f23e31ef68dd2328cc383bd493142e46107a3a0e24f7d734e3f3b80fe4c" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum semver-parser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b46e1121e8180c12ff69a742aabc4f310542b6ccb69f1691689ac17fdf8618aa" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_cbor 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45cd6d95391b16cd57e88b68be41d504183b7faae22030c0cc3b3f73dd57b2fd" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" @@ -1427,12 +1574,15 @@ dependencies = [ "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" "checksum tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828" "checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" +"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" "checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" "checksum typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" @@ -1441,9 +1591,12 @@ dependencies = [ "checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +"checksum version-sync 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "844f3d3a2467f15cb999f5af7775f6e108ac546d4f42365832ed4c755404f806" "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" @@ -58,7 +58,7 @@ use std::collections::BTreeMap; let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }"; // Deserialize it to a Rust type. -let deserialized_map: BTreeMap<String, usize> = serde_dhall::from_str(data)?; +let deserialized_map: BTreeMap<String, usize> = serde_dhall::from_str(data).parse().unwrap(); let mut expected_map = BTreeMap::new(); expected_map.insert("x".to_string(), 1); @@ -167,6 +167,10 @@ same name as the corresponding test. ## Changelog +#### [???] + +- Breaking change: reworked most of the `serde_dhall` api + #### [0.4.0] - `dhall` now uses the stable Rust toolchain ! diff --git a/abnf_to_pest/Cargo.toml b/abnf_to_pest/Cargo.toml index 780010e..1567512 100644 --- a/abnf_to_pest/Cargo.toml +++ b/abnf_to_pest/Cargo.toml @@ -14,5 +14,5 @@ doctest = false [dependencies] abnf = "0.6.0" indexmap = "1.0.2" -itertools = "0.8.0" +itertools = "0.9.0" pretty = "0.5.2" diff --git a/dhall/Cargo.toml b/dhall/Cargo.toml index 0ffe78b..bb60d9e 100644 --- a/dhall/Cargo.toml +++ b/dhall/Cargo.toml @@ -22,10 +22,11 @@ reqwest = { version = "0.10", features = ["blocking"] } serde = "1.0" serde_cbor = "0.9.0" smallvec = "1.0.0" -url = "2.1.1" +url = "2.1" [dev-dependencies] pretty_assertions = "0.6.1" +version-sync = "0.8" [build-dependencies] walkdir = "2" diff --git a/dhall/README.md b/dhall/README.md index 82b3e6a..25f11f7 100644 --- a/dhall/README.md +++ b/dhall/README.md @@ -1,10 +1,11 @@ # `dhall` Implementation of the Dhall configuration language. -This is an internal crate used for [`serde_dhall`], you probably want to use -that instead. -The API is very unstable and does not respect semver; -use at your own risk. +WARNING: This is an internal crate used for [`serde_dhall`], you probably want +to use that instead. + +WARNING: The API is very unstable and does not respect semver; use at your own +risk. [`serde_dhall`]: https://docs.rs/serde_dhall diff --git a/dhall/src/error/builder.rs b/dhall/src/error/builder.rs index c0bacb5..3ee65fb 100644 --- a/dhall/src/error/builder.rs +++ b/dhall/src/error/builder.rs @@ -6,7 +6,7 @@ use annotate_snippets::{ use crate::syntax::{ParsedSpan, Span}; #[derive(Debug, Clone, Default)] -pub(crate) struct ErrorBuilder { +pub struct ErrorBuilder { title: FreeAnnotation, annotations: Vec<SpannedAnnotation>, footer: Vec<FreeAnnotation>, diff --git a/dhall/src/error/mod.rs b/dhall/src/error/mod.rs index e28b98b..ef4d41f 100644 --- a/dhall/src/error/mod.rs +++ b/dhall/src/error/mod.rs @@ -4,7 +4,7 @@ use crate::semantics::resolve::{ImportLocation, ImportStack}; use crate::syntax::{Import, ParseError}; mod builder; -pub(crate) use builder::*; +pub use builder::*; pub type Result<T> = std::result::Result<T, Error>; @@ -15,7 +15,7 @@ pub struct Error { #[derive(Debug)] #[non_exhaustive] -pub(crate) enum ErrorKind { +pub enum ErrorKind { IO(IOError), Parse(ParseError), Decode(DecodeError), @@ -25,7 +25,7 @@ pub(crate) enum ErrorKind { } #[derive(Debug)] -pub(crate) enum ImportError { +pub enum ImportError { Missing, MissingEnvVar, SanityCheck, @@ -53,21 +53,21 @@ pub struct TypeError { /// The specific type error #[derive(Debug)] -pub(crate) enum TypeMessage { +pub enum TypeMessage { Custom(String), } impl Error { - pub(crate) fn new(kind: ErrorKind) -> Self { + pub fn new(kind: ErrorKind) -> Self { Error { kind } } - pub(crate) fn kind(&self) -> &ErrorKind { + pub fn kind(&self) -> &ErrorKind { &self.kind } } impl TypeError { - pub(crate) fn new(message: TypeMessage) -> Self { + pub fn new(message: TypeMessage) -> Self { TypeError { message } } } diff --git a/dhall/src/lib.rs b/dhall/src/lib.rs index 24e4377..392c344 100644 --- a/dhall/src/lib.rs +++ b/dhall/src/lib.rs @@ -1,7 +1,10 @@ #![doc(html_root_url = "https://docs.rs/dhall/0.4.0")] #![allow( + clippy::implicit_hasher, clippy::module_inception, clippy::needless_lifetimes, + clippy::new_ret_no_self, + clippy::new_without_default, clippy::useless_format )] @@ -15,23 +18,15 @@ use std::fmt::Display; use std::path::Path; use url::Url; -use crate::error::{EncodeError, Error, TypeError}; +use crate::error::{Error, TypeError}; use crate::semantics::parse; use crate::semantics::resolve; use crate::semantics::resolve::ImportLocation; -use crate::semantics::{ - typecheck, typecheck_with, Hir, Nir, NirKind, Tir, Type, -}; -use crate::syntax::binary; -use crate::syntax::{Builtin, Expr}; - -pub type ParsedExpr = Expr; -pub type DecodedExpr = Expr; -pub type ResolvedExpr = Expr; -pub type NormalizedExpr = Expr; +use crate::semantics::{typecheck, typecheck_with, Hir, Nir, Tir, Type}; +use crate::syntax::Expr; #[derive(Debug, Clone)] -pub struct Parsed(ParsedExpr, ImportLocation); +pub struct Parsed(Expr, ImportLocation); /// An expression where all imports have been resolved /// @@ -48,15 +43,15 @@ pub struct Typed { /// A normalized expression. /// -/// Invariant: the contained expression must be in normal form, +/// This is actually a lie, because the expression will only get normalized on demand. #[derive(Debug, Clone)] pub struct Normalized(Nir); /// Controls conversion from `Nir` to `Expr` -#[derive(Copy, Clone)] -pub(crate) struct ToExprOptions { +#[derive(Copy, Clone, Default)] +pub struct ToExprOptions { /// Whether to convert all variables to `_` - pub(crate) alpha: bool, + pub alpha: bool, } impl Parsed { @@ -72,6 +67,7 @@ impl Parsed { pub fn parse_binary_file(f: &Path) -> Result<Parsed, Error> { parse::parse_binary_file(f) } + #[allow(dead_code)] pub fn parse_binary(data: &[u8]) -> Result<Parsed, Error> { parse::parse_binary(data) } @@ -80,15 +76,11 @@ impl Parsed { resolve::resolve(self) } pub fn skip_resolve(self) -> Result<Resolved, Error> { - Ok(Resolved(resolve::skip_resolve(&self.0)?)) - } - - pub fn encode(&self) -> Result<Vec<u8>, EncodeError> { - binary::encode(&self.0) + resolve::skip_resolve(self) } /// Converts a value back to the corresponding AST expression. - pub fn to_expr(&self) -> ParsedExpr { + pub fn to_expr(&self) -> Expr { self.0.clone() } } @@ -97,11 +89,11 @@ impl Resolved { pub fn typecheck(&self) -> Result<Typed, TypeError> { Ok(Typed::from_tir(typecheck(&self.0)?)) } - pub fn typecheck_with(self, ty: &Normalized) -> Result<Typed, TypeError> { - Ok(Typed::from_tir(typecheck_with(&self.0, ty.to_hir())?)) + pub fn typecheck_with(self, ty: &Hir) -> Result<Typed, TypeError> { + Ok(Typed::from_tir(typecheck_with(&self.0, ty)?)) } /// Converts a value back to the corresponding AST expression. - pub fn to_expr(&self) -> ResolvedExpr { + pub fn to_expr(&self) -> Expr { self.0.to_expr_noopts() } } @@ -115,79 +107,38 @@ impl Typed { } /// Reduce an expression to its normal form, performing beta reduction pub fn normalize(&self) -> Normalized { - Normalized(self.hir.rec_eval_closed_expr()) + Normalized(self.hir.eval_closed_expr()) } /// Converts a value back to the corresponding AST expression. - fn to_expr(&self) -> ResolvedExpr { + fn to_expr(&self) -> Expr { self.hir.to_expr(ToExprOptions { alpha: false }) } - pub(crate) fn ty(&self) -> &Type { + pub fn ty(&self) -> &Type { &self.ty } - pub(crate) fn get_type(&self) -> Result<Normalized, TypeError> { + pub fn get_type(&self) -> Result<Normalized, TypeError> { Ok(Normalized(self.ty.clone().into_nir())) } } impl Normalized { - pub fn encode(&self) -> Result<Vec<u8>, EncodeError> { - binary::encode(&self.to_expr()) - } - /// Converts a value back to the corresponding AST expression. - pub fn to_expr(&self) -> NormalizedExpr { - self.0.to_expr(ToExprOptions { alpha: false }) + pub fn to_expr(&self) -> Expr { + self.0.to_expr(ToExprOptions::default()) } /// Converts a value back to the corresponding Hir expression. - pub(crate) fn to_hir(&self) -> Hir { + pub fn to_hir(&self) -> Hir { self.0.to_hir_noenv() } + pub fn as_nir(&self) -> &Nir { + &self.0 + } /// Converts a value back to the corresponding AST expression, alpha-normalizing in the process. - pub(crate) fn to_expr_alpha(&self) -> NormalizedExpr { + pub fn to_expr_alpha(&self) -> Expr { self.0.to_expr(ToExprOptions { alpha: true }) } - pub(crate) fn to_nir(&self) -> Nir { - self.0.clone() - } - pub(crate) fn into_nir(self) -> Nir { - self.0 - } - - pub(crate) fn from_kind(v: NirKind) -> Self { - Normalized(Nir::from_kind(v)) - } - pub(crate) fn from_nir(th: Nir) -> Self { - Normalized(th) - } - - pub fn make_builtin_type(b: Builtin) -> Self { - Normalized::from_nir(Nir::from_builtin(b)) - } - pub fn make_optional_type(t: Normalized) -> Self { - Normalized::from_nir( - Nir::from_builtin(Builtin::Optional).app(t.to_nir()), - ) - } - pub fn make_list_type(t: Normalized) -> Self { - Normalized::from_nir(Nir::from_builtin(Builtin::List).app(t.to_nir())) - } - pub fn make_record_type( - kts: impl Iterator<Item = (String, Normalized)>, - ) -> Self { - Normalized::from_kind(NirKind::RecordType( - kts.map(|(k, t)| (k.into(), t.into_nir())).collect(), - )) - } - pub fn make_union_type( - kts: impl Iterator<Item = (String, Option<Normalized>)>, - ) -> Self { - Normalized::from_kind(NirKind::UnionType( - kts.map(|(k, t)| (k.into(), t.map(|t| t.into_nir()))) - .collect(), - )) - } } macro_rules! derive_traits_for_wrapper_struct { @@ -213,23 +164,12 @@ macro_rules! derive_traits_for_wrapper_struct { derive_traits_for_wrapper_struct!(Parsed); -impl std::hash::Hash for Normalized { - fn hash<H>(&self, state: &mut H) - where - H: std::hash::Hasher, - { - if let Ok(vec) = self.encode() { - vec.hash(state) - } - } -} - -impl From<Parsed> for NormalizedExpr { +impl From<Parsed> for Expr { fn from(other: Parsed) -> Self { other.to_expr() } } -impl From<Normalized> for NormalizedExpr { +impl From<Normalized> for Expr { fn from(other: Normalized) -> Self { other.to_expr() } diff --git a/dhall/src/semantics/builtins.rs b/dhall/src/semantics/builtins.rs index 803630b..6007a92 100644 --- a/dhall/src/semantics/builtins.rs +++ b/dhall/src/semantics/builtins.rs @@ -1,48 +1,35 @@ use crate::semantics::{ - skip_resolve, typecheck, Hir, HirKind, Nir, NirKind, NzEnv, VarEnv, + skip_resolve_expr, typecheck, Hir, HirKind, Nir, NirKind, NzEnv, VarEnv, }; use crate::syntax::map::DupTreeMap; use crate::syntax::Const::Type; use crate::syntax::{ BinOp, Builtin, Const, Expr, ExprKind, InterpolatedText, - InterpolatedTextContents, Label, LitKind, NaiveDouble, Span, UnspannedExpr, + InterpolatedTextContents, Label, NaiveDouble, NumKind, Span, UnspannedExpr, V, }; -use crate::Normalized; use std::collections::HashMap; use std::convert::TryInto; /// A partially applied builtin. /// Invariant: the evaluation of the given args must not be able to progress further #[derive(Debug, Clone)] -pub(crate) struct BuiltinClosure<Nir> { - pub env: NzEnv, - pub b: Builtin, +pub struct BuiltinClosure { + env: NzEnv, + b: Builtin, /// Arguments applied to the closure so far. - pub args: Vec<Nir>, + args: Vec<Nir>, } -impl BuiltinClosure<Nir> { - pub fn new(b: Builtin, env: NzEnv) -> Self { - BuiltinClosure { - env, - b, - args: Vec::new(), - } +impl BuiltinClosure { + pub fn new(b: Builtin, env: NzEnv) -> NirKind { + apply_builtin(b, Vec::new(), env) } - pub fn apply(&self, a: Nir) -> NirKind { use std::iter::once; let args = self.args.iter().cloned().chain(once(a)).collect(); apply_builtin(self.b, args, self.env.clone()) } - /// This doesn't break the invariant because we already checked that the appropriate arguments - /// did not normalize to something that allows evaluation to proceed. - pub fn normalize(&self) { - for x in self.args.iter() { - x.normalize(); - } - } pub fn to_hirkind(&self, venv: VarEnv) -> HirKind { HirKind::Expr(self.args.iter().fold( ExprKind::Builtin(self.b), @@ -56,7 +43,7 @@ impl BuiltinClosure<Nir> { } } -pub(crate) fn rc(x: UnspannedExpr) -> Expr { +pub fn rc(x: UnspannedExpr) -> Expr { Expr::new(x, Span::Artificial) } @@ -116,7 +103,7 @@ macro_rules! make_type { }; } -pub(crate) fn type_of_builtin(b: Builtin) -> Hir { +pub fn type_of_builtin(b: Builtin) -> Hir { use Builtin::*; let expr = match b { Bool | Natural | Integer | Double | Text => make_type!(Type), @@ -202,7 +189,7 @@ pub(crate) fn type_of_builtin(b: Builtin) -> Hir { forall (A: Type) -> Optional A ), }; - skip_resolve(&expr).unwrap() + skip_resolve_expr(&expr).unwrap() } // Ad-hoc macro to help construct closures @@ -241,7 +228,7 @@ macro_rules! make_closure { rc(ExprKind::BinOp( BinOp::NaturalPlus, make_closure!($($v)*), - rc(ExprKind::Lit(LitKind::Natural(1))) + rc(ExprKind::Num(NumKind::Natural(1))) )) }; ([ $($head:tt)* ] # $($tail:tt)*) => {{ @@ -257,8 +244,8 @@ macro_rules! make_closure { #[allow(clippy::cognitive_complexity)] fn apply_builtin(b: Builtin, args: Vec<Nir>, env: NzEnv) -> NirKind { - use LitKind::{Bool, Double, Integer, Natural}; use NirKind::*; + use NumKind::{Bool, Double, Integer, Natural}; // Small helper enum enum Ret { @@ -267,46 +254,54 @@ fn apply_builtin(b: Builtin, args: Vec<Nir>, env: NzEnv) -> NirKind { DoneAsIs, } let make_closure = |e| { - typecheck(&skip_resolve(&e).unwrap()) + typecheck(&skip_resolve_expr(&e).unwrap()) .unwrap() .eval(env.clone()) }; let ret = match (b, args.as_slice()) { + (Builtin::Bool, []) + | (Builtin::Natural, []) + | (Builtin::Integer, []) + | (Builtin::Double, []) + | (Builtin::Text, []) => Ret::NirKind(BuiltinType(b)), + (Builtin::Optional, [t]) => Ret::NirKind(OptionalType(t.clone())), + (Builtin::List, [t]) => Ret::NirKind(ListType(t.clone())), + (Builtin::OptionalNone, [t]) => { Ret::NirKind(EmptyOptionalLit(t.clone())) } (Builtin::NaturalIsZero, [n]) => match &*n.kind() { - Lit(Natural(n)) => Ret::NirKind(Lit(Bool(*n == 0))), + Num(Natural(n)) => Ret::NirKind(Num(Bool(*n == 0))), _ => Ret::DoneAsIs, }, (Builtin::NaturalEven, [n]) => match &*n.kind() { - Lit(Natural(n)) => Ret::NirKind(Lit(Bool(*n % 2 == 0))), + Num(Natural(n)) => Ret::NirKind(Num(Bool(*n % 2 == 0))), _ => Ret::DoneAsIs, }, (Builtin::NaturalOdd, [n]) => match &*n.kind() { - Lit(Natural(n)) => Ret::NirKind(Lit(Bool(*n % 2 != 0))), + Num(Natural(n)) => Ret::NirKind(Num(Bool(*n % 2 != 0))), _ => Ret::DoneAsIs, }, (Builtin::NaturalToInteger, [n]) => match &*n.kind() { - Lit(Natural(n)) => Ret::NirKind(Lit(Integer(*n as isize))), + Num(Natural(n)) => Ret::NirKind(Num(Integer(*n as isize))), _ => Ret::DoneAsIs, }, (Builtin::NaturalShow, [n]) => match &*n.kind() { - Lit(Natural(n)) => Ret::Nir(Nir::from_text(n)), + Num(Natural(n)) => Ret::Nir(Nir::from_text(n)), _ => Ret::DoneAsIs, }, (Builtin::NaturalSubtract, [a, b]) => match (&*a.kind(), &*b.kind()) { - (Lit(Natural(a)), Lit(Natural(b))) => { - Ret::NirKind(Lit(Natural(if b > a { b - a } else { 0 }))) + (Num(Natural(a)), Num(Natural(b))) => { + Ret::NirKind(Num(Natural(if b > a { b - a } else { 0 }))) } - (Lit(Natural(0)), _) => Ret::Nir(b.clone()), - (_, Lit(Natural(0))) => Ret::NirKind(Lit(Natural(0))), - _ if a == b => Ret::NirKind(Lit(Natural(0))), + (Num(Natural(0)), _) => Ret::Nir(b.clone()), + (_, Num(Natural(0))) => Ret::NirKind(Num(Natural(0))), + _ if a == b => Ret::NirKind(Num(Natural(0))), _ => Ret::DoneAsIs, }, (Builtin::IntegerShow, [n]) => match &*n.kind() { - Lit(Integer(n)) => { + Num(Integer(n)) => { let s = if *n < 0 { n.to_string() } else { @@ -317,30 +312,30 @@ fn apply_builtin(b: Builtin, args: Vec<Nir>, env: NzEnv) -> NirKind { _ => Ret::DoneAsIs, }, (Builtin::IntegerToDouble, [n]) => match &*n.kind() { - Lit(Integer(n)) => { - Ret::NirKind(Lit(Double(NaiveDouble::from(*n as f64)))) + Num(Integer(n)) => { + Ret::NirKind(Num(Double(NaiveDouble::from(*n as f64)))) } _ => Ret::DoneAsIs, }, (Builtin::IntegerNegate, [n]) => match &*n.kind() { - Lit(Integer(n)) => Ret::NirKind(Lit(Integer(-n))), + Num(Integer(n)) => Ret::NirKind(Num(Integer(-n))), _ => Ret::DoneAsIs, }, (Builtin::IntegerClamp, [n]) => match &*n.kind() { - Lit(Integer(n)) => { - Ret::NirKind(Lit(Natural((*n).try_into().unwrap_or(0)))) + Num(Integer(n)) => { + Ret::NirKind(Num(Natural((*n).try_into().unwrap_or(0)))) } _ => Ret::DoneAsIs, }, (Builtin::DoubleShow, [n]) => match &*n.kind() { - Lit(Double(n)) => Ret::Nir(Nir::from_text(n)), + Num(Double(n)) => Ret::Nir(Nir::from_text(n)), _ => Ret::DoneAsIs, }, (Builtin::TextShow, [v]) => match &*v.kind() { TextLit(tlit) => { if let Some(s) = tlit.as_text() { // Printing InterpolatedText takes care of all the escaping - let txt: InterpolatedText<Normalized> = + let txt: InterpolatedText<Expr> = std::iter::once(InterpolatedTextContents::Text(s)) .collect(); Ret::Nir(Nir::from_text(txt)) @@ -351,8 +346,8 @@ fn apply_builtin(b: Builtin, args: Vec<Nir>, env: NzEnv) -> NirKind { _ => Ret::DoneAsIs, }, (Builtin::ListLength, [_, l]) => match &*l.kind() { - EmptyListLit(_) => Ret::NirKind(Lit(Natural(0))), - NEListLit(xs) => Ret::NirKind(Lit(Natural(xs.len()))), + EmptyListLit(_) => Ret::NirKind(Num(Natural(0))), + NEListLit(xs) => Ret::NirKind(Num(Natural(xs.len()))), _ => Ret::DoneAsIs, }, (Builtin::ListHead, [_, l]) => match &*l.kind() { @@ -398,7 +393,7 @@ fn apply_builtin(b: Builtin, args: Vec<Nir>, env: NzEnv) -> NirKind { let mut kvs = HashMap::new(); kvs.insert( "index".into(), - Nir::from_kind(Lit(Natural(i))), + Nir::from_kind(Num(Natural(i))), ); kvs.insert("value".into(), e.clone()); Nir::from_kind(RecordLit(kvs)) @@ -466,14 +461,14 @@ fn apply_builtin(b: Builtin, args: Vec<Nir>, env: NzEnv) -> NirKind { λ(x : Natural) -> 1 + var(x) ))) - .app(Lit(Natural(0)).into_nir()), + .app(Num(Natural(0)).into_nir()), ), (Builtin::NaturalFold, [n, t, succ, zero]) => match &*n.kind() { - Lit(Natural(0)) => Ret::Nir(zero.clone()), - Lit(Natural(n)) => { + Num(Natural(0)) => Ret::Nir(zero.clone()), + Num(Natural(n)) => { let fold = Nir::from_builtin(Builtin::NaturalFold) - .app(Lit(Natural(n - 1)).into_nir()) + .app(Num(Natural(n - 1)).into_nir()) .app(t.clone()) .app(succ.clone()) .app(zero.clone()); @@ -490,9 +485,9 @@ fn apply_builtin(b: Builtin, args: Vec<Nir>, env: NzEnv) -> NirKind { } } -impl<Nir: std::cmp::PartialEq> std::cmp::PartialEq for BuiltinClosure<Nir> { +impl std::cmp::PartialEq for BuiltinClosure { fn eq(&self, other: &Self) -> bool { self.b == other.b && self.args == other.args } } -impl<Nir: std::cmp::Eq> std::cmp::Eq for BuiltinClosure<Nir> {} +impl std::cmp::Eq for BuiltinClosure {} diff --git a/dhall/src/semantics/mod.rs b/dhall/src/semantics/mod.rs index 87033c9..468d8b1 100644 --- a/dhall/src/semantics/mod.rs +++ b/dhall/src/semantics/mod.rs @@ -3,7 +3,7 @@ pub mod nze; pub mod parse; pub mod resolve; pub mod tck; -pub(crate) use self::builtins::*; -pub(crate) use self::nze::*; -pub(crate) use self::resolve::*; -pub(crate) use self::tck::*; +pub use self::builtins::*; +pub use self::nze::*; +pub use self::resolve::*; +pub use self::tck::*; diff --git a/dhall/src/semantics/nze/env.rs b/dhall/src/semantics/nze/env.rs index ef2bee6..ec99dbe 100644 --- a/dhall/src/semantics/nze/env.rs +++ b/dhall/src/semantics/nze/env.rs @@ -1,7 +1,7 @@ use crate::semantics::{AlphaVar, Nir, NirKind}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum NzVar { +pub enum NzVar { /// Reverse-debruijn index: counts number of binders from the bottom of the stack. Bound(usize), /// Fake fresh variable generated for expression equality checking. @@ -17,11 +17,11 @@ enum EnvItem<Type> { } #[derive(Debug, Clone)] -pub(crate) struct ValEnv<Type> { +pub struct ValEnv<Type> { items: Vec<EnvItem<Type>>, } -pub(crate) type NzEnv = ValEnv<()>; +pub type NzEnv = ValEnv<()>; impl NzVar { pub fn new(idx: usize) -> Self { diff --git a/dhall/src/semantics/nze/mod.rs b/dhall/src/semantics/nze/mod.rs index 2648339..23022e0 100644 --- a/dhall/src/semantics/nze/mod.rs +++ b/dhall/src/semantics/nze/mod.rs @@ -3,7 +3,7 @@ pub mod lazy; pub mod nir; pub mod normalize; pub mod var; -pub(crate) use env::*; -pub(crate) use nir::*; -pub(crate) use normalize::*; -pub(crate) use var::*; +pub use env::*; +pub use nir::*; +pub use normalize::*; +pub use var::*; diff --git a/dhall/src/semantics/nze/nir.rs b/dhall/src/semantics/nze/nir.rs index 32ef590..e0d227e 100644 --- a/dhall/src/semantics/nze/nir.rs +++ b/dhall/src/semantics/nze/nir.rs @@ -7,10 +7,10 @@ use crate::semantics::{ BuiltinClosure, Hir, HirKind, NzEnv, NzVar, TyEnv, Type, Universe, VarEnv, }; use crate::syntax::{ - BinOp, Builtin, Const, ExprKind, InterpolatedTextContents, Label, LitKind, - Span, + BinOp, Builtin, Const, Expr, ExprKind, InterpolatedTextContents, Label, + NumKind, Span, }; -use crate::{NormalizedExpr, ToExprOptions}; +use crate::ToExprOptions; /// Stores a possibly unevaluated value. Gets (partially) normalized on-demand, sharing computation /// automatically. Uses a Rc<RefCell> to share computation. @@ -19,7 +19,7 @@ use crate::{NormalizedExpr, ToExprOptions}; /// normalize as needed. /// Stands for "Normalized intermediate representation" #[derive(Clone)] -pub(crate) struct Nir(Rc<NirInternal>); +pub struct Nir(Rc<NirInternal>); #[derive(Debug)] struct NirInternal { @@ -28,7 +28,7 @@ struct NirInternal { /// An unevaluated subexpression #[derive(Debug, Clone)] -pub(crate) enum Thunk { +pub enum Thunk { /// A completely unnormalized expression. Thunk { env: NzEnv, body: Hir }, /// A partially normalized expression that may need to go through `normalize_one_layer`. @@ -37,7 +37,7 @@ pub(crate) enum Thunk { /// An unevaluated subexpression that takes an argument. #[derive(Debug, Clone)] -pub(crate) enum Closure { +pub enum Closure { /// Normal closure Closure { env: NzEnv, body: Hir }, /// Closure that ignores the argument passed @@ -48,7 +48,7 @@ pub(crate) enum Closure { // Invariant: this must not contain interpolations that are themselves TextLits, and contiguous // text values must be merged. #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct TextLit(Vec<InterpolatedTextContents<Nir>>); +pub struct TextLit(Vec<InterpolatedTextContents<Nir>>); /// This represents a value in Weak Head Normal Form (WHNF). This means that the value is /// normalized up to the first constructor, but subexpressions may not be fully normalized. @@ -58,7 +58,7 @@ pub(crate) struct TextLit(Vec<InterpolatedTextContents<Nir>>); /// In particular, this means that once we get a `NirKind`, it can be considered immutable, and /// we only need to recursively normalize its sub-`Nir`s to get to the NF. #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum NirKind { +pub enum NirKind { /// Closures LamClosure { binder: Binder, @@ -70,13 +70,17 @@ pub(crate) enum NirKind { annot: Nir, closure: Closure, }, - AppliedBuiltin(BuiltinClosure<Nir>), + AppliedBuiltin(BuiltinClosure), Var(NzVar), Const(Const), - Lit(LitKind), + // Must be a number type, Bool or Text + BuiltinType(Builtin), + Num(NumKind), + OptionalType(Nir), EmptyOptionalLit(Nir), NEOptionalLit(Nir), + ListType(Nir), // EmptyListLit(t) means `[] : List t`, not `[] : t` EmptyListLit(Nir), NEListLit(Vec<Nir>), @@ -93,34 +97,34 @@ pub(crate) enum NirKind { impl Nir { /// Construct a Nir from a completely unnormalized expression. - pub(crate) fn new_thunk(env: NzEnv, hir: Hir) -> Nir { + pub fn new_thunk(env: NzEnv, hir: Hir) -> Nir { NirInternal::from_thunk(Thunk::new(env, hir)).into_nir() } /// Construct a Nir from a partially normalized expression that's not in WHNF. - pub(crate) fn from_partial_expr(e: ExprKind<Nir>) -> Nir { + pub fn from_partial_expr(e: ExprKind<Nir>) -> Nir { // TODO: env let env = NzEnv::new(); NirInternal::from_thunk(Thunk::from_partial_expr(env, e)).into_nir() } /// Make a Nir from a NirKind - pub(crate) fn from_kind(v: NirKind) -> Nir { + pub fn from_kind(v: NirKind) -> Nir { NirInternal::from_whnf(v).into_nir() } - pub(crate) fn from_const(c: Const) -> Self { + pub fn from_const(c: Const) -> Self { let v = NirKind::Const(c); NirInternal::from_whnf(v).into_nir() } - pub(crate) fn from_builtin(b: Builtin) -> Self { + pub fn from_builtin(b: Builtin) -> Self { Self::from_builtin_env(b, &NzEnv::new()) } - pub(crate) fn from_builtin_env(b: Builtin, env: &NzEnv) -> Self { + pub fn from_builtin_env(b: Builtin, env: &NzEnv) -> Self { Nir::from_kind(NirKind::from_builtin_env(b, env.clone())) } - pub(crate) fn from_text(txt: impl ToString) -> Self { + pub fn from_text(txt: impl ToString) -> Self { Nir::from_kind(NirKind::TextLit(TextLit::from_text(txt.to_string()))) } - pub(crate) fn as_const(&self) -> Option<Const> { + pub fn as_const(&self) -> Option<Const> { match &*self.kind() { NirKind::Const(c) => Some(*c), _ => None, @@ -128,26 +132,22 @@ impl Nir { } /// This is what you want if you want to pattern-match on the value. - pub(crate) fn kind(&self) -> &NirKind { + pub fn kind(&self) -> &NirKind { self.0.kind() } - pub(crate) fn to_type(&self, u: impl Into<Universe>) -> Type { + pub fn to_type(&self, u: impl Into<Universe>) -> Type { Type::new(self.clone(), u.into()) } /// Converts a value back to the corresponding AST expression. - pub(crate) fn to_expr(&self, opts: ToExprOptions) -> NormalizedExpr { + pub fn to_expr(&self, opts: ToExprOptions) -> Expr { self.to_hir_noenv().to_expr(opts) } - pub(crate) fn to_expr_tyenv(&self, tyenv: &TyEnv) -> NormalizedExpr { + pub fn to_expr_tyenv(&self, tyenv: &TyEnv) -> Expr { self.to_hir(tyenv.as_varenv()).to_expr_tyenv(tyenv) } - pub(crate) fn normalize(&self) { - self.0.normalize() - } - - pub(crate) fn app(&self, v: Nir) -> Nir { + pub fn app(&self, v: Nir) -> Nir { Nir::from_kind(apply_any(self.clone(), v)) } @@ -188,12 +188,21 @@ impl Nir { closure.to_hir(venv), ), NirKind::Const(c) => ExprKind::Const(*c), - NirKind::Lit(l) => ExprKind::Lit(l.clone()), + NirKind::BuiltinType(b) => ExprKind::Builtin(*b), + NirKind::Num(l) => ExprKind::Num(l.clone()), + NirKind::OptionalType(t) => ExprKind::App( + Nir::from_builtin(Builtin::Optional).to_hir(venv), + t.to_hir(venv), + ), NirKind::EmptyOptionalLit(n) => ExprKind::App( Nir::from_builtin(Builtin::OptionalNone).to_hir(venv), n.to_hir(venv), ), NirKind::NEOptionalLit(n) => ExprKind::SomeLit(n.to_hir(venv)), + NirKind::ListType(t) => ExprKind::App( + Nir::from_builtin(Builtin::List).to_hir(venv), + t.to_hir(venv), + ), NirKind::EmptyListLit(n) => ExprKind::EmptyListLit(Hir::new( HirKind::Expr(ExprKind::App( Nir::from_builtin(Builtin::List).to_hir(venv), @@ -274,75 +283,18 @@ impl NirInternal { fn kind(&self) -> &NirKind { &self.kind } - fn normalize(&self) { - self.kind().normalize(); - } } impl NirKind { - pub(crate) fn into_nir(self) -> Nir { + pub fn into_nir(self) -> Nir { Nir::from_kind(self) } - pub(crate) fn normalize(&self) { - match self { - NirKind::Var(..) | NirKind::Const(_) | NirKind::Lit(_) => {} - - NirKind::EmptyOptionalLit(tth) | NirKind::EmptyListLit(tth) => { - tth.normalize(); - } - - NirKind::NEOptionalLit(th) => { - th.normalize(); - } - NirKind::LamClosure { annot, closure, .. } - | NirKind::PiClosure { annot, closure, .. } => { - annot.normalize(); - closure.normalize(); - } - NirKind::AppliedBuiltin(closure) => closure.normalize(), - NirKind::NEListLit(elts) => { - for x in elts.iter() { - x.normalize(); - } - } - NirKind::RecordLit(kvs) => { - for x in kvs.values() { - x.normalize(); - } - } - NirKind::RecordType(kvs) => { - for x in kvs.values() { - x.normalize(); - } - } - NirKind::UnionType(kts) | NirKind::UnionConstructor(_, kts) => { - for x in kts.values().flatten() { - x.normalize(); - } - } - NirKind::UnionLit(_, v, kts) => { - v.normalize(); - for x in kts.values().flatten() { - x.normalize(); - } - } - NirKind::TextLit(tlit) => tlit.normalize(), - NirKind::Equivalence(x, y) => { - x.normalize(); - y.normalize(); - } - NirKind::PartialExpr(e) => { - e.map_ref(Nir::normalize); - } - } - } - - pub(crate) fn from_builtin(b: Builtin) -> NirKind { + pub fn from_builtin(b: Builtin) -> NirKind { NirKind::from_builtin_env(b, NzEnv::new()) } - pub(crate) fn from_builtin_env(b: Builtin, env: NzEnv) -> NirKind { - NirKind::AppliedBuiltin(BuiltinClosure::new(b, env)) + pub fn from_builtin_env(b: Builtin, env: NzEnv) -> NirKind { + BuiltinClosure::new(b, env) } } @@ -390,9 +342,6 @@ impl Closure { } } - // TODO: somehow normalize the body. Might require to pass an env. - pub fn normalize(&self) {} - /// Convert this closure to a Hir expression pub fn to_hir(&self, venv: VarEnv) -> Hir { self.apply_var(NzVar::new(venv.size())) @@ -456,13 +405,6 @@ impl TextLit { pub fn iter(&self) -> impl Iterator<Item = &InterpolatedTextContents<Nir>> { self.0.iter() } - /// Normalize the contained values. This does not break the invariant because we have already - /// ensured that no contained values normalize to a TextLit. - pub fn normalize(&self) { - for x in self.0.iter() { - x.map_ref(Nir::normalize); - } - } } impl lazy::Eval<NirKind> for Thunk { diff --git a/dhall/src/semantics/nze/normalize.rs b/dhall/src/semantics/nze/normalize.rs index 79d55e8..570e106 100644 --- a/dhall/src/semantics/nze/normalize.rs +++ b/dhall/src/semantics/nze/normalize.rs @@ -2,14 +2,10 @@ use itertools::Itertools; use std::collections::HashMap; use crate::semantics::NzEnv; -use crate::semantics::{ - Binder, BuiltinClosure, Closure, Hir, HirKind, Nir, NirKind, TextLit, -}; -use crate::syntax::{ - BinOp, Builtin, ExprKind, InterpolatedTextContents, LitKind, -}; +use crate::semantics::{Binder, Closure, Hir, HirKind, Nir, NirKind, TextLit}; +use crate::syntax::{BinOp, ExprKind, InterpolatedTextContents, NumKind}; -pub(crate) fn apply_any(f: Nir, a: Nir) -> NirKind { +pub fn apply_any(f: Nir, a: Nir) -> NirKind { match f.kind() { NirKind::LamClosure { closure, .. } => closure.apply(a).kind().clone(), NirKind::AppliedBuiltin(closure) => closure.apply(a), @@ -20,7 +16,7 @@ pub(crate) fn apply_any(f: Nir, a: Nir) -> NirKind { } } -pub(crate) fn squash_textlit( +pub fn squash_textlit( elts: impl Iterator<Item = InterpolatedTextContents<Nir>>, ) -> Vec<InterpolatedTextContents<Nir>> { use std::mem::replace; @@ -58,7 +54,7 @@ pub(crate) fn squash_textlit( ret } -pub(crate) fn merge_maps<K, V, F>( +pub fn merge_maps<K, V, F>( map1: &HashMap<K, V>, map2: &HashMap<K, V>, mut f: F, @@ -99,40 +95,40 @@ fn apply_binop<'a>(o: BinOp, x: &'a Nir, y: &'a Nir) -> Option<Ret<'a>> { NaturalTimes, RecursiveRecordMerge, RecursiveRecordTypeMerge, RightBiasedRecordMerge, TextAppend, }; - use LitKind::{Bool, Natural}; - use NirKind::{EmptyListLit, Lit, NEListLit, RecordLit, RecordType}; + use NirKind::{EmptyListLit, NEListLit, Num, RecordLit, RecordType}; + use NumKind::{Bool, Natural}; Some(match (o, x.kind(), y.kind()) { - (BoolAnd, Lit(Bool(true)), _) => Ret::NirRef(y), - (BoolAnd, _, Lit(Bool(true))) => Ret::NirRef(x), - (BoolAnd, Lit(Bool(false)), _) => Ret::NirKind(Lit(Bool(false))), - (BoolAnd, _, Lit(Bool(false))) => Ret::NirKind(Lit(Bool(false))), + (BoolAnd, Num(Bool(true)), _) => Ret::NirRef(y), + (BoolAnd, _, Num(Bool(true))) => Ret::NirRef(x), + (BoolAnd, Num(Bool(false)), _) => Ret::NirKind(Num(Bool(false))), + (BoolAnd, _, Num(Bool(false))) => Ret::NirKind(Num(Bool(false))), (BoolAnd, _, _) if x == y => Ret::NirRef(x), - (BoolOr, Lit(Bool(true)), _) => Ret::NirKind(Lit(Bool(true))), - (BoolOr, _, Lit(Bool(true))) => Ret::NirKind(Lit(Bool(true))), - (BoolOr, Lit(Bool(false)), _) => Ret::NirRef(y), - (BoolOr, _, Lit(Bool(false))) => Ret::NirRef(x), + (BoolOr, Num(Bool(true)), _) => Ret::NirKind(Num(Bool(true))), + (BoolOr, _, Num(Bool(true))) => Ret::NirKind(Num(Bool(true))), + (BoolOr, Num(Bool(false)), _) => Ret::NirRef(y), + (BoolOr, _, Num(Bool(false))) => Ret::NirRef(x), (BoolOr, _, _) if x == y => Ret::NirRef(x), - (BoolEQ, Lit(Bool(true)), _) => Ret::NirRef(y), - (BoolEQ, _, Lit(Bool(true))) => Ret::NirRef(x), - (BoolEQ, Lit(Bool(x)), Lit(Bool(y))) => Ret::NirKind(Lit(Bool(x == y))), - (BoolEQ, _, _) if x == y => Ret::NirKind(Lit(Bool(true))), - (BoolNE, Lit(Bool(false)), _) => Ret::NirRef(y), - (BoolNE, _, Lit(Bool(false))) => Ret::NirRef(x), - (BoolNE, Lit(Bool(x)), Lit(Bool(y))) => Ret::NirKind(Lit(Bool(x != y))), - (BoolNE, _, _) if x == y => Ret::NirKind(Lit(Bool(false))), + (BoolEQ, Num(Bool(true)), _) => Ret::NirRef(y), + (BoolEQ, _, Num(Bool(true))) => Ret::NirRef(x), + (BoolEQ, Num(Bool(x)), Num(Bool(y))) => Ret::NirKind(Num(Bool(x == y))), + (BoolEQ, _, _) if x == y => Ret::NirKind(Num(Bool(true))), + (BoolNE, Num(Bool(false)), _) => Ret::NirRef(y), + (BoolNE, _, Num(Bool(false))) => Ret::NirRef(x), + (BoolNE, Num(Bool(x)), Num(Bool(y))) => Ret::NirKind(Num(Bool(x != y))), + (BoolNE, _, _) if x == y => Ret::NirKind(Num(Bool(false))), - (NaturalPlus, Lit(Natural(0)), _) => Ret::NirRef(y), - (NaturalPlus, _, Lit(Natural(0))) => Ret::NirRef(x), - (NaturalPlus, Lit(Natural(x)), Lit(Natural(y))) => { - Ret::NirKind(Lit(Natural(x + y))) + (NaturalPlus, Num(Natural(0)), _) => Ret::NirRef(y), + (NaturalPlus, _, Num(Natural(0))) => Ret::NirRef(x), + (NaturalPlus, Num(Natural(x)), Num(Natural(y))) => { + Ret::NirKind(Num(Natural(x + y))) } - (NaturalTimes, Lit(Natural(0)), _) => Ret::NirKind(Lit(Natural(0))), - (NaturalTimes, _, Lit(Natural(0))) => Ret::NirKind(Lit(Natural(0))), - (NaturalTimes, Lit(Natural(1)), _) => Ret::NirRef(y), - (NaturalTimes, _, Lit(Natural(1))) => Ret::NirRef(x), - (NaturalTimes, Lit(Natural(x)), Lit(Natural(y))) => { - Ret::NirKind(Lit(Natural(x * y))) + (NaturalTimes, Num(Natural(0)), _) => Ret::NirKind(Num(Natural(0))), + (NaturalTimes, _, Num(Natural(0))) => Ret::NirKind(Num(Natural(0))), + (NaturalTimes, Num(Natural(1)), _) => Ret::NirRef(y), + (NaturalTimes, _, Num(Natural(1))) => Ret::NirRef(x), + (NaturalTimes, Num(Natural(x)), Num(Natural(y))) => { + Ret::NirKind(Num(Natural(x * y))) } (ListAppend, EmptyListLit(_), _) => Ret::NirRef(y), @@ -211,13 +207,13 @@ fn apply_binop<'a>(o: BinOp, x: &'a Nir, y: &'a Nir) -> Option<Ret<'a>> { } #[allow(clippy::cognitive_complexity)] -pub(crate) fn normalize_one_layer(expr: ExprKind<Nir>, env: &NzEnv) -> NirKind { - use LitKind::Bool; +pub fn normalize_one_layer(expr: ExprKind<Nir>, env: &NzEnv) -> NirKind { use NirKind::{ - EmptyListLit, EmptyOptionalLit, Lit, NEListLit, NEOptionalLit, + EmptyListLit, EmptyOptionalLit, NEListLit, NEOptionalLit, Num, PartialExpr, RecordLit, RecordType, UnionConstructor, UnionLit, UnionType, }; + use NumKind::Bool; let ret = match expr { ExprKind::Import(..) | ExprKind::Completion(..) => { @@ -235,15 +231,11 @@ pub(crate) fn normalize_one_layer(expr: ExprKind<Nir>, env: &NzEnv) -> NirKind { ExprKind::Builtin(b) => Ret::Nir(Nir::from_builtin_env(b, env)), ExprKind::Assert(_) => Ret::Expr(expr), ExprKind::App(v, a) => Ret::Nir(v.app(a)), - ExprKind::Lit(l) => Ret::NirKind(Lit(l)), + ExprKind::Num(l) => Ret::NirKind(Num(l)), ExprKind::SomeLit(e) => Ret::NirKind(NEOptionalLit(e)), ExprKind::EmptyListLit(t) => { let arg = match t.kind() { - NirKind::AppliedBuiltin(BuiltinClosure { - b: Builtin::List, - args, - .. - }) if args.len() == 1 => args[0].clone(), + NirKind::ListType(t) => t.clone(), _ => panic!("internal type error"), }; Ret::NirKind(NirKind::EmptyListLit(arg)) @@ -271,12 +263,12 @@ pub(crate) fn normalize_one_layer(expr: ExprKind<Nir>, env: &NzEnv) -> NirKind { } ExprKind::BoolIf(ref b, ref e1, ref e2) => { match b.kind() { - Lit(Bool(true)) => Ret::NirRef(e1), - Lit(Bool(false)) => Ret::NirRef(e2), + Num(Bool(true)) => Ret::NirRef(e1), + Num(Bool(false)) => Ret::NirRef(e2), _ => { match (e1.kind(), e2.kind()) { // Simplify `if b then True else False` - (Lit(Bool(true)), Lit(Bool(false))) => Ret::NirRef(b), + (Num(Bool(true)), Num(Bool(false))) => Ret::NirRef(b), _ if e1 == e2 => Ret::NirRef(e1), _ => Ret::Expr(expr), } @@ -442,12 +434,8 @@ pub(crate) fn normalize_one_layer(expr: ExprKind<Nir>, env: &NzEnv) -> NirKind { ExprKind::ToMap(ref v, ref annot) => match v.kind() { RecordLit(kvs) if kvs.is_empty() => { match annot.as_ref().map(|v| v.kind()) { - Some(NirKind::AppliedBuiltin(BuiltinClosure { - b: Builtin::List, - args, - .. - })) if args.len() == 1 => { - Ret::NirKind(EmptyListLit(args[0].clone())) + Some(NirKind::ListType(t)) => { + Ret::NirKind(EmptyListLit(t.clone())) } _ => Ret::Expr(expr), } @@ -476,7 +464,7 @@ pub(crate) fn normalize_one_layer(expr: ExprKind<Nir>, env: &NzEnv) -> NirKind { } /// Normalize Hir into WHNF -pub(crate) fn normalize_hir_whnf(env: &NzEnv, hir: &Hir) -> NirKind { +pub fn normalize_hir_whnf(env: &NzEnv, hir: &Hir) -> NirKind { match hir.kind() { HirKind::Var(var) => env.lookup_val(*var), HirKind::Import(hir, _) => normalize_hir_whnf(env, hir), diff --git a/dhall/src/semantics/nze/var.rs b/dhall/src/semantics/nze/var.rs index 413c759..302dbb7 100644 --- a/dhall/src/semantics/nze/var.rs +++ b/dhall/src/semantics/nze/var.rs @@ -8,10 +8,10 @@ pub struct Binder { } impl Binder { - pub(crate) fn new(name: Label) -> Self { + pub fn new(name: Label) -> Self { Binder { name } } - pub(crate) fn to_label(&self) -> Label { + pub fn to_label(&self) -> Label { self.clone().into() } } diff --git a/dhall/src/semantics/parse.rs b/dhall/src/semantics/parse.rs index 45860d0..2326471 100644 --- a/dhall/src/semantics/parse.rs +++ b/dhall/src/semantics/parse.rs @@ -9,33 +9,33 @@ use crate::syntax::binary; use crate::syntax::parse_expr; use crate::Parsed; -pub(crate) fn parse_file(f: &Path) -> Result<Parsed, Error> { +pub fn parse_file(f: &Path) -> Result<Parsed, Error> { let text = std::fs::read_to_string(f)?; let expr = parse_expr(&text)?; let root = ImportLocation::Local(f.to_owned()); Ok(Parsed(expr, root)) } -pub(crate) fn parse_remote(url: Url) -> Result<Parsed, Error> { +pub fn parse_remote(url: Url) -> Result<Parsed, Error> { let body = reqwest::blocking::get(url.clone()).unwrap().text().unwrap(); let expr = parse_expr(&body)?; let root = ImportLocation::Remote(url); Ok(Parsed(expr, root)) } -pub(crate) fn parse_str(s: &str) -> Result<Parsed, Error> { +pub fn parse_str(s: &str) -> Result<Parsed, Error> { let expr = parse_expr(s)?; let root = ImportLocation::Missing; Ok(Parsed(expr, root)) } -pub(crate) fn parse_binary(data: &[u8]) -> Result<Parsed, Error> { +pub fn parse_binary(data: &[u8]) -> Result<Parsed, Error> { let expr = binary::decode(data)?; let root = ImportLocation::Missing; Ok(Parsed(expr, root)) } -pub(crate) fn parse_binary_file(f: &Path) -> Result<Parsed, Error> { +pub fn parse_binary_file(f: &Path) -> Result<Parsed, Error> { let mut buffer = Vec::new(); File::open(f)?.read_to_end(&mut buffer)?; let expr = binary::decode(&buffer)?; diff --git a/dhall/src/semantics/resolve/env.rs b/dhall/src/semantics/resolve/env.rs index fe8c178..d7ff0ae 100644 --- a/dhall/src/semantics/resolve/env.rs +++ b/dhall/src/semantics/resolve/env.rs @@ -5,24 +5,24 @@ use crate::semantics::{AlphaVar, ImportLocation, TypedHir, VarEnv}; use crate::syntax::{Label, V}; /// Environment for resolving names. -#[derive(Debug, Clone)] -pub(crate) struct NameEnv { +#[derive(Debug, Clone, Default)] +pub struct NameEnv { names: Vec<Label>, } -pub(crate) type ImportCache = HashMap<ImportLocation, TypedHir>; -pub(crate) type ImportStack = Vec<ImportLocation>; +pub type ImportCache = HashMap<ImportLocation, TypedHir>; +pub type ImportStack = Vec<ImportLocation>; /// Environment for resolving imports -#[derive(Debug, Clone)] -pub(crate) struct ImportEnv { +#[derive(Debug, Clone, Default)] +pub struct ImportEnv { cache: ImportCache, stack: ImportStack, } impl NameEnv { pub fn new() -> Self { - NameEnv { names: Vec::new() } + NameEnv::default() } pub fn as_varenv(&self) -> VarEnv { VarEnv::from_size(self.names.len()) @@ -66,10 +66,7 @@ impl NameEnv { impl ImportEnv { pub fn new() -> Self { - ImportEnv { - cache: HashMap::new(), - stack: Vec::new(), - } + ImportEnv::default() } pub fn handle_import( diff --git a/dhall/src/semantics/resolve/hir.rs b/dhall/src/semantics/resolve/hir.rs index fa2989f..9256425 100644 --- a/dhall/src/semantics/resolve/hir.rs +++ b/dhall/src/semantics/resolve/hir.rs @@ -1,7 +1,7 @@ use crate::error::TypeError; use crate::semantics::{type_with, NameEnv, Nir, NzEnv, Tir, TyEnv, Type}; use crate::syntax::{Expr, ExprKind, Span, V}; -use crate::{NormalizedExpr, ToExprOptions}; +use crate::ToExprOptions; /// Stores an alpha-normalized variable. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -10,7 +10,7 @@ pub struct AlphaVar { } #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum HirKind { +pub enum HirKind { /// A resolved variable (i.e. a DeBruijn index) Var(AlphaVar), /// Result of resolving an import. @@ -21,16 +21,16 @@ pub(crate) enum HirKind { // An expression with resolved variables and imports. #[derive(Debug, Clone)] -pub(crate) struct Hir { +pub struct Hir { kind: Box<HirKind>, span: Span, } impl AlphaVar { - pub(crate) fn new(idx: usize) -> Self { + pub fn new(idx: usize) -> Self { AlphaVar { idx } } - pub(crate) fn idx(self) -> usize { + pub fn idx(self) -> usize { self.idx } } @@ -51,15 +51,15 @@ impl Hir { } /// Converts a closed Hir expr back to the corresponding AST expression. - pub fn to_expr(&self, opts: ToExprOptions) -> NormalizedExpr { + pub fn to_expr(&self, opts: ToExprOptions) -> Expr { hir_to_expr(self, opts, &mut NameEnv::new()) } /// Converts a closed Hir expr back to the corresponding AST expression. - pub fn to_expr_noopts(&self) -> NormalizedExpr { + pub fn to_expr_noopts(&self) -> Expr { let opts = ToExprOptions { alpha: false }; self.to_expr(opts) } - pub fn to_expr_tyenv(&self, env: &TyEnv) -> NormalizedExpr { + pub fn to_expr_tyenv(&self, env: &TyEnv) -> Expr { let opts = ToExprOptions { alpha: false }; let mut env = env.as_nameenv().clone(); hir_to_expr(self, opts, &mut env) @@ -85,19 +85,9 @@ impl Hir { pub fn eval_closed_expr(&self) -> Nir { self.eval(NzEnv::new()) } - /// Eval a closed Hir fully and recursively; - pub fn rec_eval_closed_expr(&self) -> Nir { - let val = self.eval_closed_expr(); - val.normalize(); - val - } } -fn hir_to_expr( - hir: &Hir, - opts: ToExprOptions, - env: &mut NameEnv, -) -> NormalizedExpr { +fn hir_to_expr(hir: &Hir, opts: ToExprOptions, env: &mut NameEnv) -> Expr { let kind = match hir.kind() { HirKind::Var(v) if opts.alpha => ExprKind::Var(V("_".into(), v.idx())), HirKind::Var(v) => ExprKind::Var(env.label_var(*v)), diff --git a/dhall/src/semantics/resolve/mod.rs b/dhall/src/semantics/resolve/mod.rs index 517907b..33b477e 100644 --- a/dhall/src/semantics/resolve/mod.rs +++ b/dhall/src/semantics/resolve/mod.rs @@ -1,6 +1,6 @@ pub mod env; pub mod hir; pub mod resolve; -pub(crate) use env::*; -pub(crate) use hir::*; -pub(crate) use resolve::*; +pub use env::*; +pub use hir::*; +pub use resolve::*; diff --git a/dhall/src/semantics/resolve/resolve.rs b/dhall/src/semantics/resolve/resolve.rs index f3fda4b..7745e0b 100644 --- a/dhall/src/semantics/resolve/resolve.rs +++ b/dhall/src/semantics/resolve/resolve.rs @@ -13,17 +13,17 @@ use crate::syntax::{ BinOp, Builtin, Expr, ExprKind, FilePath, FilePrefix, ImportMode, ImportTarget, Span, UnspannedExpr, URL, }; -use crate::{Parsed, ParsedExpr, Resolved}; +use crate::{Parsed, Resolved}; // TODO: evaluate import headers -pub(crate) type Import = syntax::Import<()>; +pub type Import = syntax::Import<()>; /// Owned Hir with a type. Different from Tir because the Hir is owned. -pub(crate) type TypedHir = (Hir, Type); +pub type TypedHir = (Hir, Type); /// The location of some data, usually some dhall code. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) enum ImportLocation { +pub enum ImportLocation { /// Local file Local(PathBuf), /// Remote file @@ -227,7 +227,7 @@ fn resolve_one_import( } ImportMode::Location => { let expr = location.into_location(); - let hir = skip_resolve(&expr)?; + let hir = skip_resolve_expr(&expr)?; let ty = hir.typecheck_noenv()?.ty().clone(); Ok((hir, ty)) } @@ -329,16 +329,22 @@ fn resolve_with_env( Ok(Resolved(resolved)) } -pub(crate) fn resolve(parsed: Parsed) -> Result<Resolved, Error> { +pub fn resolve(parsed: Parsed) -> Result<Resolved, Error> { resolve_with_env(&mut ImportEnv::new(), parsed) } -pub(crate) fn skip_resolve(expr: &ParsedExpr) -> Result<Hir, Error> { +pub fn skip_resolve_expr(expr: &Expr) -> Result<Hir, Error> { traverse_resolve_expr(&mut NameEnv::new(), expr, &mut |import| { Err(ImportError::UnexpectedImport(import).into()) }) } +pub fn skip_resolve(parsed: Parsed) -> Result<Resolved, Error> { + let Parsed(expr, _) = parsed; + let resolved = skip_resolve_expr(&expr)?; + Ok(Resolved(resolved)) +} + pub trait Canonicalize { fn canonicalize(&self) -> Self; } diff --git a/dhall/src/semantics/tck/env.rs b/dhall/src/semantics/tck/env.rs index 6dd5076..1fa66f0 100644 --- a/dhall/src/semantics/tck/env.rs +++ b/dhall/src/semantics/tck/env.rs @@ -2,21 +2,21 @@ use crate::semantics::{AlphaVar, NameEnv, Nir, NzEnv, NzVar, Type, ValEnv}; use crate::syntax::Label; /// Environment for indexing variables. -#[derive(Debug, Clone, Copy)] -pub(crate) struct VarEnv { +#[derive(Debug, Clone, Copy, Default)] +pub struct VarEnv { size: usize, } /// Environment for typing expressions. #[derive(Debug, Clone)] -pub(crate) struct TyEnv { +pub struct TyEnv { names: NameEnv, items: ValEnv<Type>, } impl VarEnv { pub fn new() -> Self { - VarEnv { size: 0 } + VarEnv::default() } pub fn from_size(size: usize) -> Self { VarEnv { size } diff --git a/dhall/src/semantics/tck/mod.rs b/dhall/src/semantics/tck/mod.rs index 93c8f48..6dddfc5 100644 --- a/dhall/src/semantics/tck/mod.rs +++ b/dhall/src/semantics/tck/mod.rs @@ -1,6 +1,6 @@ pub mod env; pub mod tir; pub mod typecheck; -pub(crate) use env::*; -pub(crate) use tir::*; -pub(crate) use typecheck::*; +pub use env::*; +pub use tir::*; +pub use typecheck::*; diff --git a/dhall/src/semantics/tck/tir.rs b/dhall/src/semantics/tck/tir.rs index aeb7bf9..89a8027 100644 --- a/dhall/src/semantics/tck/tir.rs +++ b/dhall/src/semantics/tck/tir.rs @@ -1,15 +1,14 @@ use crate::error::{ErrorBuilder, TypeError}; use crate::semantics::{mkerr, Hir, Nir, NirKind, NzEnv, TyEnv, VarEnv}; -use crate::syntax::{Builtin, Const, Span}; -use crate::NormalizedExpr; +use crate::syntax::{Builtin, Const, Expr, Span}; /// The type of a type. 0 is `Type`, 1 is `Kind`, etc... #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] -pub(crate) struct Universe(u8); +pub struct Universe(u8); /// An expression representing a type #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct Type { +pub struct Type { val: Nir, univ: Universe, } @@ -17,7 +16,7 @@ pub(crate) struct Type { /// A hir expression plus its inferred type. /// Stands for "Typed intermediate representation" #[derive(Debug, Clone)] -pub(crate) struct Tir<'hir> { +pub struct Tir<'hir> { hir: &'hir Hir, ty: Type, } @@ -101,7 +100,7 @@ impl Type { pub fn to_hir(&self, venv: VarEnv) -> Hir { self.val.to_hir(venv) } - pub fn to_expr_tyenv(&self, tyenv: &TyEnv) -> NormalizedExpr { + pub fn to_expr_tyenv(&self, tyenv: &TyEnv) -> Expr { self.val.to_hir(tyenv.as_varenv()).to_expr_tyenv(tyenv) } } @@ -124,7 +123,7 @@ impl<'hir> Tir<'hir> { pub fn as_hir(&self) -> &Hir { &self.hir } - pub fn to_expr_tyenv(&self, env: &TyEnv) -> NormalizedExpr { + pub fn to_expr_tyenv(&self, env: &TyEnv) -> Expr { self.as_hir().to_expr_tyenv(env) } diff --git a/dhall/src/semantics/tck/typecheck.rs b/dhall/src/semantics/tck/typecheck.rs index 173b76d..c3334b5 100644 --- a/dhall/src/semantics/tck/typecheck.rs +++ b/dhall/src/semantics/tck/typecheck.rs @@ -5,11 +5,11 @@ use std::collections::HashMap; use crate::error::{ErrorBuilder, TypeError, TypeMessage}; use crate::semantics::merge_maps; use crate::semantics::{ - type_of_builtin, Binder, BuiltinClosure, Closure, Hir, HirKind, Nir, - NirKind, Tir, TyEnv, Type, + type_of_builtin, Binder, Closure, Hir, HirKind, Nir, NirKind, Tir, TyEnv, + Type, }; use crate::syntax::{ - BinOp, Builtin, Const, ExprKind, InterpolatedTextContents, LitKind, Span, + BinOp, Builtin, Const, ExprKind, InterpolatedTextContents, NumKind, Span, }; fn check_rectymerge( @@ -53,14 +53,11 @@ fn function_check(a: Const, b: Const) -> Const { } } -pub(crate) fn mkerr<T, S: ToString>(msg: S) -> Result<T, TypeError> { +pub fn mkerr<T, S: ToString>(msg: S) -> Result<T, TypeError> { Err(TypeError::new(TypeMessage::Custom(msg.to_string()))) } -pub(crate) fn mk_span_err<T, S: ToString>( - span: Span, - msg: S, -) -> Result<T, TypeError> { +pub fn mk_span_err<T, S: ToString>(span: Span, msg: S) -> Result<T, TypeError> { mkerr( ErrorBuilder::new(msg.to_string()) .span_err(span, msg.to_string()) @@ -96,14 +93,14 @@ fn type_one_layer( let t_hir = type_of_builtin(*b); typecheck(&t_hir)?.eval_to_type(env)? } - ExprKind::Lit(LitKind::Bool(_)) => Type::from_builtin(Builtin::Bool), - ExprKind::Lit(LitKind::Natural(_)) => { + ExprKind::Num(NumKind::Bool(_)) => Type::from_builtin(Builtin::Bool), + ExprKind::Num(NumKind::Natural(_)) => { Type::from_builtin(Builtin::Natural) } - ExprKind::Lit(LitKind::Integer(_)) => { + ExprKind::Num(NumKind::Integer(_)) => { Type::from_builtin(Builtin::Integer) } - ExprKind::Lit(LitKind::Double(_)) => { + ExprKind::Num(NumKind::Double(_)) => { Type::from_builtin(Builtin::Double) } ExprKind::TextLit(interpolated) => { @@ -121,11 +118,7 @@ fn type_one_layer( ExprKind::EmptyListLit(t) => { let t = t.eval_to_type(env)?; match t.kind() { - NirKind::AppliedBuiltin(BuiltinClosure { - b: Builtin::List, - args, - .. - }) if args.len() == 1 => {} + NirKind::ListType(..) => {} _ => return span_err("InvalidListType"), }; t @@ -376,10 +369,7 @@ fn type_one_layer( } ExprKind::BinOp(BinOp::ListAppend, l, r) => { match l.ty().kind() { - NirKind::AppliedBuiltin(BuiltinClosure { - b: Builtin::List, - .. - }) => {} + NirKind::ListType(..) => {} _ => return span_err("BinOpTypeMismatch"), } @@ -435,12 +425,7 @@ fn type_one_layer( let union_type = union.ty(); let variants = match union_type.kind() { NirKind::UnionType(kts) => Cow::Borrowed(kts), - NirKind::AppliedBuiltin(BuiltinClosure { - b: Builtin::Optional, - args, - .. - }) if args.len() == 1 => { - let ty = &args[0]; + NirKind::OptionalType(ty) => { let mut kts = HashMap::new(); kts.insert("None".into(), None); kts.insert("Some".into(), Some(ty.clone())); @@ -595,11 +580,7 @@ fn type_one_layer( let err_msg = "The type of `toMap x` must be of the form \ `List { mapKey : Text, mapValue : T }`"; let arg = match annot_val.kind() { - NirKind::AppliedBuiltin(BuiltinClosure { - b: Builtin::List, - args, - .. - }) if args.len() == 1 => &args[0], + NirKind::ListType(t) => t, _ => return span_err(err_msg), }; let kts = match arg.kind() { @@ -704,7 +685,7 @@ fn type_one_layer( /// `type_with` typechecks an expression in the provided environment. Optionally pass an annotation /// to compare with. -pub(crate) fn type_with<'hir>( +pub fn type_with<'hir>( env: &TyEnv, hir: &'hir Hir, annot: Option<Type>, @@ -801,15 +782,15 @@ pub(crate) fn type_with<'hir>( /// Typecheck an expression and return the expression annotated with types if type-checking /// succeeded, or an error if type-checking failed. -pub(crate) fn typecheck<'hir>(hir: &'hir Hir) -> Result<Tir<'hir>, TypeError> { +pub fn typecheck<'hir>(hir: &'hir Hir) -> Result<Tir<'hir>, TypeError> { type_with(&TyEnv::new(), hir, None) } /// Like `typecheck`, but additionally checks that the expression's type matches the provided type. -pub(crate) fn typecheck_with<'hir>( +pub fn typecheck_with<'hir>( hir: &'hir Hir, - ty: Hir, + ty: &Hir, ) -> Result<Tir<'hir>, TypeError> { - let ty = typecheck(&ty)?.eval_to_type(&TyEnv::new())?; + let ty = typecheck(ty)?.eval_to_type(&TyEnv::new())?; type_with(&TyEnv::new(), hir, Some(ty)) } diff --git a/dhall/src/syntax/ast/expr.rs b/dhall/src/syntax/ast/expr.rs index b53e6cb..6ba6649 100644 --- a/dhall/src/syntax/ast/expr.rs +++ b/dhall/src/syntax/ast/expr.rs @@ -22,7 +22,7 @@ pub enum Const { } impl Const { - pub(crate) fn to_universe(self) -> Universe { + pub fn to_universe(self) -> Universe { Universe::from_const(self) } } @@ -112,9 +112,9 @@ pub struct Expr { pub type UnspannedExpr = ExprKind<Expr>; -/// Simple literals +/// Numeric literals #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum LitKind { +pub enum NumKind { /// `True` Bool(bool), /// `1` @@ -132,7 +132,7 @@ pub enum LitKind { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ExprKind<SubExpr> { Const(Const), - Lit(LitKind), + Num(NumKind), /// `x` /// `x@n` Var(V), @@ -205,7 +205,7 @@ impl<SE> ExprKind<SE> { }) } - pub(crate) fn traverse_ref<'a, SE2, Err>( + pub fn traverse_ref<'a, SE2, Err>( &'a self, mut visit_subexpr: impl FnMut(&'a SE) -> Result<SE2, Err>, ) -> Result<ExprKind<SE2>, Err> { @@ -239,17 +239,17 @@ impl<SE> ExprKind<SE> { } impl Expr { - pub(crate) fn as_ref(&self) -> &UnspannedExpr { + pub fn as_ref(&self) -> &UnspannedExpr { &self.kind } pub fn kind(&self) -> &UnspannedExpr { &self.kind } - pub(crate) fn span(&self) -> Span { + pub fn span(&self) -> Span { self.span.clone() } - pub(crate) fn new(kind: UnspannedExpr, span: Span) -> Self { + pub fn new(kind: UnspannedExpr, span: Span) -> Self { Expr { kind: Box::new(kind), span, diff --git a/dhall/src/syntax/ast/mod.rs b/dhall/src/syntax/ast/mod.rs index 5e20c5d..1950154 100644 --- a/dhall/src/syntax/ast/mod.rs +++ b/dhall/src/syntax/ast/mod.rs @@ -5,7 +5,7 @@ pub use import::*; mod label; pub use label::*; mod span; -pub(crate) use span::*; +pub use span::*; mod text; pub use text::*; pub mod map; diff --git a/dhall/src/syntax/ast/span.rs b/dhall/src/syntax/ast/span.rs index 2e09863..e250602 100644 --- a/dhall/src/syntax/ast/span.rs +++ b/dhall/src/syntax/ast/span.rs @@ -2,7 +2,7 @@ use std::rc::Rc; /// A location in the source text #[derive(Debug, Clone)] -pub(crate) struct ParsedSpan { +pub struct ParsedSpan { input: Rc<str>, /// # Safety /// @@ -15,7 +15,7 @@ pub(crate) struct ParsedSpan { } #[derive(Debug, Clone)] -pub(crate) enum Span { +pub enum Span { /// A location in the source text Parsed(ParsedSpan), /// Desugarings @@ -30,12 +30,12 @@ pub(crate) enum Span { } impl ParsedSpan { - pub(crate) fn to_input(&self) -> String { + pub fn to_input(&self) -> String { self.input.to_string() } /// Convert to a char range for consumption by annotate_snippets. /// This compensates for https://github.com/rust-lang/annotate-snippets-rs/issues/24 - pub(crate) fn as_char_range(&self) -> (usize, usize) { + pub fn as_char_range(&self) -> (usize, usize) { ( char_idx_from_byte_idx(&self.input, self.start), char_idx_from_byte_idx(&self.input, self.end), @@ -44,7 +44,7 @@ impl ParsedSpan { } impl Span { - pub(crate) fn make(input: Rc<str>, sp: pest::Span) -> Self { + pub fn make(input: Rc<str>, sp: pest::Span) -> Self { Span::Parsed(ParsedSpan { input, start: sp.start(), @@ -55,7 +55,7 @@ impl Span { /// Takes the union of the two spans, i.e. the range of input covered by the two spans plus any /// input between them. Assumes that the spans come from the same input. Fails if one of the /// spans does not point to an input location. - pub(crate) fn union(&self, other: &Span) -> Self { + pub fn union(&self, other: &Span) -> Self { use std::cmp::{max, min}; use Span::*; match (self, other) { diff --git a/dhall/src/syntax/ast/visitor.rs b/dhall/src/syntax/ast/visitor.rs index c361bc1..0a0c5ef 100644 --- a/dhall/src/syntax/ast/visitor.rs +++ b/dhall/src/syntax/ast/visitor.rs @@ -51,7 +51,7 @@ where .collect() } -pub(crate) fn visit_ref<'a, F, SE1, SE2, Err>( +pub fn visit_ref<'a, F, SE1, SE2, Err>( input: &'a ExprKind<SE1>, mut f: F, ) -> Result<ExprKind<SE2>, Err> @@ -91,7 +91,7 @@ where Annot(x, t) => Annot(expr!(x)?, expr!(t)?), Const(k) => Const(*k), Builtin(v) => Builtin(*v), - Lit(l) => Lit(l.clone()), + Num(n) => Num(n.clone()), TextLit(t) => TextLit(t.traverse_ref(|e| expr!(e))?), BinOp(o, x, y) => BinOp(*o, expr!(x)?, expr!(y)?), BoolIf(b, t, f) => BoolIf(expr!(b)?, expr!(t)?, expr!(f)?), diff --git a/dhall/src/syntax/binary/decode.rs b/dhall/src/syntax/binary/decode.rs index 2ecd7e0..3c93419 100644 --- a/dhall/src/syntax/binary/decode.rs +++ b/dhall/src/syntax/binary/decode.rs @@ -6,12 +6,12 @@ use crate::error::DecodeError; use crate::syntax; use crate::syntax::{ Expr, ExprKind, FilePath, FilePrefix, Hash, ImportMode, ImportTarget, - Integer, InterpolatedText, Label, LitKind, Natural, Scheme, Span, + Integer, InterpolatedText, Label, Natural, NumKind, Scheme, Span, UnspannedExpr, URL, V, }; -use crate::DecodedExpr; +type DecodedExpr = Expr; -pub(crate) fn decode(data: &[u8]) -> Result<DecodedExpr, DecodeError> { +pub fn decode(data: &[u8]) -> Result<DecodedExpr, DecodeError> { match serde_cbor::de::from_slice(data) { Ok(v) => cbor_value_to_dhall(&v), Err(e) => Err(DecodeError::CBORError(e)), @@ -31,8 +31,8 @@ fn cbor_value_to_dhall(data: &cbor::Value) -> Result<DecodedExpr, DecodeError> { String(s) => match Builtin::parse(s) { Some(b) => ExprKind::Builtin(b), None => match s.as_str() { - "True" => Lit(LitKind::Bool(true)), - "False" => Lit(LitKind::Bool(false)), + "True" => Num(NumKind::Bool(true)), + "False" => Num(NumKind::Bool(false)), "Type" => Const(Const::Type), "Kind" => Const(Const::Kind), "Sort" => Const(Const::Sort), @@ -44,8 +44,8 @@ fn cbor_value_to_dhall(data: &cbor::Value) -> Result<DecodedExpr, DecodeError> { }, }, U64(n) => Var(V(Label::from("_"), *n as usize)), - F64(x) => Lit(LitKind::Double((*x).into())), - Bool(b) => Lit(LitKind::Bool(*b)), + F64(x) => Num(NumKind::Double((*x).into())), + Bool(b) => Num(NumKind::Bool(*b)), Array(vec) => match vec.as_slice() { [String(l), U64(n)] => { if l.as_str() == "_" { @@ -224,9 +224,9 @@ fn cbor_value_to_dhall(data: &cbor::Value) -> Result<DecodedExpr, DecodeError> { let z = cbor_value_to_dhall(&z)?; BoolIf(x, y, z) } - [U64(15), U64(x)] => Lit(LitKind::Natural(*x as Natural)), - [U64(16), U64(x)] => Lit(LitKind::Integer(*x as Integer)), - [U64(16), I64(x)] => Lit(LitKind::Integer(*x as Integer)), + [U64(15), U64(x)] => Num(NumKind::Natural(*x as Natural)), + [U64(16), U64(x)] => Num(NumKind::Integer(*x as Integer)), + [U64(16), I64(x)] => Num(NumKind::Integer(*x as Integer)), [U64(18), String(first), rest @ ..] => { TextLit(InterpolatedText::from(( first.clone(), diff --git a/dhall/src/syntax/binary/encode.rs b/dhall/src/syntax/binary/encode.rs index 9e6948e..8d22a9b 100644 --- a/dhall/src/syntax/binary/encode.rs +++ b/dhall/src/syntax/binary/encode.rs @@ -10,7 +10,7 @@ use crate::syntax::{ Scheme, V, }; -pub(crate) fn encode(expr: &Expr) -> Result<Vec<u8>, EncodeError> { +pub fn encode(expr: &Expr) -> Result<Vec<u8>, EncodeError> { serde_cbor::ser::to_vec(&Serialize::Expr(expr)) .map_err(EncodeError::CBORError) } @@ -48,7 +48,7 @@ where use std::iter::once; use syntax::Builtin; use syntax::ExprKind::*; - use syntax::LitKind::*; + use syntax::NumKind::*; use self::Serialize::{RecordDupMap, RecordMap, UnionMap}; fn expr(x: &Expr) -> self::Serialize<'_> { @@ -63,10 +63,10 @@ where match e.as_ref() { Const(c) => ser.serialize_str(&c.to_string()), Builtin(b) => ser.serialize_str(&b.to_string()), - Lit(Bool(b)) => ser.serialize_bool(*b), - Lit(Natural(n)) => ser_seq!(ser; tag(15), U64(*n as u64)), - Lit(Integer(n)) => ser_seq!(ser; tag(16), I64(*n as i64)), - Lit(Double(n)) => { + Num(Bool(b)) => ser.serialize_bool(*b), + Num(Natural(n)) => ser_seq!(ser; tag(15), U64(*n as u64)), + Num(Integer(n)) => ser_seq!(ser; tag(16), I64(*n as i64)), + Num(Double(n)) => { let n: f64 = (*n).into(); ser.serialize_f64(n) } diff --git a/dhall/src/syntax/binary/mod.rs b/dhall/src/syntax/binary/mod.rs index 7ed1f6e..98e0520 100644 --- a/dhall/src/syntax/binary/mod.rs +++ b/dhall/src/syntax/binary/mod.rs @@ -1,4 +1,4 @@ mod decode; mod encode; -pub(crate) use decode::decode; -pub(crate) use encode::encode; +pub use decode::decode; +pub use encode::encode; diff --git a/dhall/src/syntax/text/parser.rs b/dhall/src/syntax/text/parser.rs index 03211c7..6f5949f 100644 --- a/dhall/src/syntax/text/parser.rs +++ b/dhall/src/syntax/text/parser.rs @@ -9,7 +9,7 @@ use pest_consume::{match_nodes, Parser}; use crate::syntax::map::{DupTreeMap, DupTreeSet}; use crate::syntax::ExprKind::*; -use crate::syntax::LitKind::*; +use crate::syntax::NumKind::*; use crate::syntax::{ Double, Expr, FilePath, FilePrefix, Hash, ImportMode, ImportTarget, Integer, InterpolatedText, InterpolatedTextContents, Label, NaiveDouble, @@ -135,7 +135,7 @@ fn insert_recordlit_entry(map: &mut BTreeMap<Label, Expr>, l: Label, e: Expr) { entry.insert(e); } Entry::Occupied(mut entry) => { - let dummy = Expr::new(Lit(Bool(false)), Span::Artificial); + let dummy = Expr::new(Num(Bool(false)), Span::Artificial); let other = entry.insert(dummy); entry.insert(Expr::new( BinOp(RecursiveRecordMerge, other, e), @@ -390,8 +390,8 @@ impl DhallParser { let e = match crate::syntax::Builtin::parse(s) { Some(b) => Builtin(b), None => match s { - "True" => Lit(Bool(true)), - "False" => Lit(Bool(false)), + "True" => Num(Bool(true)), + "False" => Num(Bool(false)), "Type" => Const(crate::syntax::Const::Type), "Kind" => Const(crate::syntax::Const::Kind), "Sort" => Const(crate::syntax::Const::Sort), @@ -924,9 +924,9 @@ impl DhallParser { #[alias(expression, shortcut = true)] fn primitive_expression(input: ParseInput) -> ParseResult<Expr> { Ok(match_nodes!(input.children(); - [double_literal(n)] => spanned(input, Lit(Double(n))), - [natural_literal(n)] => spanned(input, Lit(Natural(n))), - [integer_literal(n)] => spanned(input, Lit(Integer(n))), + [double_literal(n)] => spanned(input, Num(Double(n))), + [natural_literal(n)] => spanned(input, Num(Natural(n))), + [integer_literal(n)] => spanned(input, Num(Integer(n))), [double_quote_literal(s)] => spanned(input, TextLit(s)), [single_quote_literal(s)] => spanned(input, TextLit(s)), [record_type_or_literal(e)] => spanned(input, e), diff --git a/dhall/src/syntax/text/printer.rs b/dhall/src/syntax/text/printer.rs index 2b7bc2e..378f408 100644 --- a/dhall/src/syntax/text/printer.rs +++ b/dhall/src/syntax/text/printer.rs @@ -201,7 +201,7 @@ impl<SE: Display + Clone> Display for ExprKind<SE> { Var(a) => a.fmt(f)?, Const(k) => k.fmt(f)?, Builtin(v) => v.fmt(f)?, - Lit(a) => a.fmt(f)?, + Num(a) => a.fmt(f)?, TextLit(a) => a.fmt(f)?, RecordType(a) if a.is_empty() => f.write_str("{}")?, RecordType(a) => fmt_list("{ ", ", ", " }", a, f, |(k, t), f| { @@ -240,9 +240,9 @@ impl Display for Expr { } } -impl Display for LitKind { +impl Display for NumKind { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - use LitKind::*; + use NumKind::*; match self { Bool(true) => f.write_str("True")?, Bool(false) => f.write_str("False")?, diff --git a/dhall/src/tests.rs b/dhall/src/tests.rs index a5a278c..2cd354f 100644 --- a/dhall/src/tests.rs +++ b/dhall/src/tests.rs @@ -10,8 +10,8 @@ use std::io::{Read, Write}; use std::path::PathBuf; use crate::error::{ErrorKind, Result}; -use crate::syntax::binary; -use crate::{Normalized, NormalizedExpr, Parsed, Resolved, Typed}; +use crate::syntax::{binary, Expr}; +use crate::{Normalized, Parsed, Resolved, Typed}; macro_rules! assert_eq_display { ($left:expr, $right:expr) => {{ @@ -111,7 +111,7 @@ impl TestFile { env::var("UPDATE_TEST_FILES") == Ok("1".to_string()) } /// Write the provided expression to the pointed file. - fn write_expr(&self, expr: impl Into<NormalizedExpr>) -> Result<()> { + fn write_expr(&self, expr: impl Into<Expr>) -> Result<()> { let expr = expr.into(); let path = self.path(); create_dir_all(path.parent().unwrap())?; @@ -142,7 +142,7 @@ impl TestFile { } /// Check that the provided expression matches the file contents. - pub fn compare(&self, expr: impl Into<NormalizedExpr>) -> Result<()> { + pub fn compare(&self, expr: impl Into<Expr>) -> Result<()> { let expr = expr.into(); if !self.path().is_file() { return self.write_expr(expr); @@ -159,7 +159,7 @@ impl TestFile { Ok(()) } /// Check that the provided expression matches the file contents. - pub fn compare_debug(&self, expr: impl Into<NormalizedExpr>) -> Result<()> { + pub fn compare_debug(&self, expr: impl Into<Expr>) -> Result<()> { let expr = expr.into(); if !self.path().is_file() { return self.write_expr(expr); @@ -176,10 +176,7 @@ impl TestFile { Ok(()) } /// Check that the provided expression matches the file contents. - pub fn compare_binary( - &self, - expr: impl Into<NormalizedExpr>, - ) -> Result<()> { + pub fn compare_binary(&self, expr: impl Into<Expr>) -> Result<()> { let expr = expr.into(); match self { TestFile::Binary(_) => {} @@ -302,7 +299,7 @@ fn run_test(test: Test) -> Result<()> { expected.compare(expr)?; } ImportFailure(expr, expected) => { - let err = expr.parse()?.resolve().unwrap_err(); + let err = expr.resolve().unwrap_err(); expected.compare_ui(err)?; } TypeInferenceSuccess(expr, expected) => { diff --git a/dhall/tests/version_numbers.rs b/dhall/tests/version_numbers.rs new file mode 100644 index 0000000..9f1d04a --- /dev/null +++ b/dhall/tests/version_numbers.rs @@ -0,0 +1,4 @@ +#[test] +fn test_html_root_url() { + version_sync::assert_html_root_url_updated!("src/lib.rs"); +} diff --git a/dhall_proc_macros/Cargo.toml b/dhall_proc_macros/Cargo.toml index 6de2850..48b55e8 100644 --- a/dhall_proc_macros/Cargo.toml +++ b/dhall_proc_macros/Cargo.toml @@ -17,3 +17,6 @@ itertools = "0.9.0" quote = "1.0" proc-macro2 = "1.0" syn = "1.0" + +[dev-dependencies] +version-sync = "0.8" diff --git a/dhall_proc_macros/src/derive.rs b/dhall_proc_macros/src/derive.rs index 48626a0..e484ec6 100644 --- a/dhall_proc_macros/src/derive.rs +++ b/dhall_proc_macros/src/derive.rs @@ -52,9 +52,11 @@ fn derive_for_struct( let ty = static_type(ty); quote!( (#name.to_owned(), #ty) ) }); - Ok(quote! { ::serde_dhall::value::Value::make_record_type( - vec![ #(#entries),* ].into_iter() - ) }) + Ok(quote! { + ::serde_dhall::SimpleType::Record( + vec![ #(#entries),* ].into_iter().collect() + ) + }) } fn derive_for_enum( @@ -89,9 +91,11 @@ fn derive_for_enum( }) .collect::<Result<_, Error>>()?; - Ok(quote! { ::serde_dhall::value::Value::make_union_type( - vec![ #(#entries),* ].into_iter() - ) }) + Ok(quote! { + ::serde_dhall::SimpleType::Union( + vec![ #(#entries),* ].into_iter().collect() + ) + }) } pub fn derive_static_type_inner( @@ -164,8 +168,7 @@ pub fn derive_static_type_inner( impl #impl_generics ::serde_dhall::StaticType for #ident #ty_generics #where_clause { - fn static_type() -> - ::serde_dhall::value::Value { + fn static_type() -> ::serde_dhall::SimpleType { #(#assertions)* #get_type } diff --git a/dhall_proc_macros/tests/version_numbers.rs b/dhall_proc_macros/tests/version_numbers.rs new file mode 100644 index 0000000..9f1d04a --- /dev/null +++ b/dhall_proc_macros/tests/version_numbers.rs @@ -0,0 +1,4 @@ +#[test] +fn test_html_root_url() { + version_sync::assert_html_root_url_updated!("src/lib.rs"); +} diff --git a/serde_dhall/Cargo.toml b/serde_dhall/Cargo.toml index b0030e9..6af883a 100644 --- a/serde_dhall/Cargo.toml +++ b/serde_dhall/Cargo.toml @@ -11,5 +11,11 @@ edition = "2018" [dependencies] serde = { version = "1.0", features = ["derive"] } -dhall = { version = "0.4.0", path = "../dhall" } -dhall_proc_macros = { version = "0.4.0", path = "../dhall_proc_macros" } +dhall = { version = "=0.4.0", path = "../dhall" } +dhall_proc_macros = { version = "=0.4.0", path = "../dhall_proc_macros" } +doc-comment = "0.3" +reqwest = { version = "0.10", features = ["blocking"] } +url = "2.1" + +[dev-dependencies] +version-sync = "0.8" diff --git a/serde_dhall/src/deserialize.rs b/serde_dhall/src/deserialize.rs new file mode 100644 index 0000000..92be2e9 --- /dev/null +++ b/serde_dhall/src/deserialize.rs @@ -0,0 +1,143 @@ +use serde::de::value::{ + MapAccessDeserializer, MapDeserializer, SeqDeserializer, +}; +use std::borrow::Cow; + +use dhall::syntax::NumKind; + +use crate::value::SimpleValue; +use crate::{Error, ErrorKind, Result, Value}; + +pub trait Sealed {} + +/// A data structure that can be deserialized from a Dhall expression. +/// +/// This is automatically implemented for any type that [serde] can deserialize. +/// In fact, this trait cannot be implemented manually. To implement it for your type, +/// use serde's derive mechanism. +/// +/// # Example +/// +/// ```rust +/// # fn main() -> serde_dhall::Result<()> { +/// use serde::Deserialize; +/// +/// // Use serde's derive +/// #[derive(Deserialize)] +/// struct Point { +/// x: u64, +/// y: u64, +/// } +/// +/// // Convert a Dhall string to a Point. +/// let point: Point = serde_dhall::from_str("{ x = 1, y = 1 + 1 }").parse()?; +/// # Ok(()) +/// # } +/// ``` +/// +/// [serde]: https://serde.rs +pub trait FromDhall: Sealed + Sized { + #[doc(hidden)] + fn from_dhall(v: &Value) -> Result<Self>; +} + +impl<T> Sealed for T where T: serde::de::DeserializeOwned {} + +struct Deserializer<'a>(Cow<'a, SimpleValue>); + +impl<T> FromDhall for T +where + T: serde::de::DeserializeOwned, +{ + fn from_dhall(v: &Value) -> Result<Self> { + let sval = v.to_simple_value().ok_or_else(|| { + Error(ErrorKind::Deserialize(format!( + "this cannot be deserialized into the serde data model: {}", + v + ))) + })?; + T::deserialize(Deserializer(Cow::Owned(sval))) + } +} + +impl<'de: 'a, 'a> serde::de::IntoDeserializer<'de, Error> for Deserializer<'a> { + type Deserializer = Deserializer<'a>; + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl<'de: 'a, 'a> serde::Deserializer<'de> for Deserializer<'a> { + type Error = Error; + + fn deserialize_any<V>(self, visitor: V) -> Result<V::Value> + where + V: serde::de::Visitor<'de>, + { + use std::convert::TryInto; + use NumKind::*; + use SimpleValue::*; + + let val = |x| Deserializer(Cow::Borrowed(x)); + match self.0.as_ref() { + Num(Bool(x)) => visitor.visit_bool(*x), + Num(Natural(x)) => { + if let Ok(x64) = (*x).try_into() { + visitor.visit_u64(x64) + } else if let Ok(x32) = (*x).try_into() { + visitor.visit_u32(x32) + } else { + unimplemented!() + } + } + Num(Integer(x)) => { + if let Ok(x64) = (*x).try_into() { + visitor.visit_i64(x64) + } else if let Ok(x32) = (*x).try_into() { + visitor.visit_i32(x32) + } else { + unimplemented!() + } + } + Num(Double(x)) => visitor.visit_f64((*x).into()), + Text(x) => visitor.visit_str(x), + List(xs) => { + visitor.visit_seq(SeqDeserializer::new(xs.iter().map(val))) + } + Optional(None) => visitor.visit_none(), + Optional(Some(x)) => visitor.visit_some(val(x)), + Record(m) => visitor.visit_map(MapDeserializer::new( + m.iter().map(|(k, v)| (k.as_ref(), val(v))), + )), + Union(field_name, Some(x)) => visitor.visit_enum( + MapAccessDeserializer::new(MapDeserializer::new( + Some((field_name.as_str(), val(x))).into_iter(), + )), + ), + Union(field_name, None) => visitor.visit_enum( + MapAccessDeserializer::new(MapDeserializer::new( + Some((field_name.as_str(), ())).into_iter(), + )), + ), + } + } + + fn deserialize_tuple<V>(self, _: usize, visitor: V) -> Result<V::Value> + where + V: serde::de::Visitor<'de>, + { + let val = |x| Deserializer(Cow::Borrowed(x)); + match self.0.as_ref() { + // Blindly takes keys in sorted order. + SimpleValue::Record(m) => visitor + .visit_seq(SeqDeserializer::new(m.iter().map(|(_, v)| val(v)))), + _ => self.deserialize_any(visitor), + } + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq + tuple_struct map struct enum identifier ignored_any + } +} diff --git a/serde_dhall/src/error.rs b/serde_dhall/src/error.rs new file mode 100644 index 0000000..896e8b9 --- /dev/null +++ b/serde_dhall/src/error.rs @@ -0,0 +1,40 @@ +use dhall::error::Error as DhallError; + +/// Alias for a `Result` with the error type `serde_dhall::Error`. +pub type Result<T> = std::result::Result<T, Error>; + +/// Errors that can occur when deserializing Dhall data. +#[derive(Debug)] +pub struct Error(pub(crate) ErrorKind); + +#[derive(Debug)] +pub(crate) enum ErrorKind { + Dhall(DhallError), + Deserialize(String), +} + +impl From<ErrorKind> for Error { + fn from(kind: ErrorKind) -> Error { + Error(kind) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match &self.0 { + ErrorKind::Dhall(err) => write!(f, "{}", err), + ErrorKind::Deserialize(err) => write!(f, "{}", err), + } + } +} + +impl std::error::Error for Error {} + +impl serde::de::Error for Error { + fn custom<T>(msg: T) -> Self + where + T: std::fmt::Display, + { + ErrorKind::Deserialize(msg.to_string()).into() + } +} diff --git a/serde_dhall/src/lib.rs b/serde_dhall/src/lib.rs index 0a53420..c478b2a 100644 --- a/serde_dhall/src/lib.rs +++ b/serde_dhall/src/lib.rs @@ -1,4 +1,5 @@ #![doc(html_root_url = "https://docs.rs/serde_dhall/0.4.0")] +#![warn(missing_docs, missing_doc_code_examples)] //! [Dhall][dhall] is a programmable configuration language that provides a non-repetitive //! alternative to JSON and YAML. //! @@ -16,20 +17,20 @@ //! //! # Basic usage //! -//! The main entrypoint of this library is the [`from_str`][from_str] function. It reads a string +//! The main entrypoint of this library is the [`from_str`](fn.from_str.html) function. It reads a string //! containing a Dhall expression and deserializes it into any serde-compatible type. //! //! This could mean a common Rust type like `HashMap`: //! //! ```rust -//! # fn main() -> serde_dhall::de::Result<()> { +//! # fn main() -> serde_dhall::Result<()> { //! use std::collections::HashMap; //! //! // Some Dhall data //! let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }"; //! //! // Deserialize it to a Rust type. -//! let deserialized_map: HashMap<String, usize> = serde_dhall::from_str(data)?; +//! let deserialized_map: HashMap<String, usize> = serde_dhall::from_str(data).parse()?; //! //! let mut expected_map = HashMap::new(); //! expected_map.insert("x".to_string(), 1); @@ -43,10 +44,10 @@ //! or a custom datatype, using serde's `derive` mechanism: //! //! ```rust -//! # fn main() -> serde_dhall::de::Result<()> { +//! # fn main() -> serde_dhall::Result<()> { //! use serde::Deserialize; //! -//! #[derive(Debug, Deserialize)] +//! #[derive(Deserialize)] //! struct Point { //! x: u64, //! y: u64, @@ -56,7 +57,7 @@ //! let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }"; //! //! // Convert the Dhall string to a Point. -//! let point: Point = serde_dhall::from_str(data)?; +//! let point: Point = serde_dhall::from_str(data).parse()?; //! assert_eq!(point.x, 1); //! assert_eq!(point.y, 2); //! @@ -64,67 +65,82 @@ //! # } //! ``` //! -//! # Type correspondence -//! -//! The following Dhall types correspond to the following Rust types: -//! -//! Dhall | Rust -//! -------|------ -//! `Bool` | `bool` -//! `Natural` | `u64`, `u32`, ... -//! `Integer` | `i64`, `i32`, ... -//! `Double` | `f64`, `f32`, ... -//! `Text` | `String` -//! `List T` | `Vec<T>` -//! `Optional T` | `Option<T>` -//! `{ x: T, y: U }` | structs -//! `{ _1: T, _2: U }` | `(T, U)`, structs -//! `{ x: T, y: T }` | `HashMap<String, T>`, structs -//! `< x: T \| y: U >` | enums -//! `T -> U` | unsupported -//! `Prelude.JSON.Type` | unsupported -//! `Prelude.Map.Type T U` | unsupported -//! -//! //! # Replacing `serde_json` or `serde_yaml` //! -//! If you used to consume JSON or YAML, you only need to replace [serde_json::from_str] or -//! [serde_yaml::from_str] with [serde_dhall::from_str][from_str]. +//! If you used to consume JSON or YAML, you only need to replace [`serde_json::from_str`] or +//! [`serde_yaml::from_str`] with [`serde_dhall::from_str(…).parse()`](fn.from_str.html). //! -//! [serde_json::from_str]: https://docs.serde.rs/serde_json/de/fn.from_str.html -//! [serde_yaml::from_str]: https://docs.serde.rs/serde_yaml/fn.from_str.html +//! [`serde_json::from_str`]: https://docs.serde.rs/serde_json/fn.from_str.html +//! [`serde_yaml::from_str`]: https://docs.serde.rs/serde_yaml/fn.from_str.html //! //! //! # Additional Dhall typechecking //! //! When deserializing, normal type checking is done to ensure that the returned value is a valid -//! Dhall value, and that it can be deserialized into the required Rust type. However types are -//! first-class in Dhall, and this library allows you to additionally check that some input data +//! Dhall value. However types are +//! first-class in Dhall, and this library allows you to additionally check that the input data //! matches a given Dhall type. That way, a type error will be caught on the Dhall side, and have //! pretty and explicit errors that point to the source file. //! -//! There are two ways to typecheck a Dhall value: you can provide the type as Dhall text or you -//! can let Rust infer it for you. +//! There are two ways to typecheck a Dhall value in this way: you can provide the type manually or +//! you can let Rust infer it for you. //! -//! To provide a type written in Dhall, first parse it into a [`serde_dhall::Value`][Value], then -//! pass it to [`from_str_check_type`][from_str_check_type]. +//! To let Rust infer the appropriate Dhall type, use the [StaticType](trait.StaticType.html) +//! trait. //! //! ```rust -//! # fn main() -> serde_dhall::de::Result<()> { -//! use serde_dhall::Value; +//! # fn main() -> serde_dhall::Result<()> { +//! use serde::Deserialize; +//! use serde_dhall::StaticType; +//! +//! #[derive(Deserialize, StaticType)] +//! struct Point { +//! x: u64, +//! y: u64, +//! } +//! +//! // Some Dhall data +//! let data = "{ x = 1, y = 1 + 1 }"; +//! +//! // Convert the Dhall string to a Point. +//! let point = serde_dhall::from_str(data) +//! .static_type_annotation() +//! .parse::<Point>()?; +//! assert_eq!(point.x, 1); +//! assert_eq!(point.y, 2); +//! +//! // Invalid data fails the type validation +//! let invalid_data = "{ x = 1, z = 0.3 }"; +//! assert!( +//! serde_dhall::from_str(invalid_data) +//! .static_type_annotation() +//! .parse::<Point>() +//! .is_err() +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! To provide a type manually, you need a [`SimpleType`](enum.SimpleType.html) value. You +//! can parse it from some Dhall text like you would parse any other value. +//! +//! ```rust +//! # fn main() -> serde_dhall::Result<()> { +//! use serde_dhall::SimpleType; //! use std::collections::HashMap; //! //! // Parse a Dhall type //! let point_type_str = "{ x: Natural, y: Natural }"; -//! let point_type: Value = serde_dhall::from_str(point_type_str)?; +//! let point_type = serde_dhall::from_str(point_type_str).parse::<SimpleType>()?; //! //! // Some Dhall data //! let point_data = "{ x = 1, y = 1 + 1 }"; //! //! // Deserialize the data to a Rust type. This checks that //! // the data matches the provided type. -//! let deserialized_map: HashMap<String, usize> = -//! serde_dhall::from_str_check_type(point_data, &point_type)?; +//! let deserialized_map = serde_dhall::from_str(point_data) +//! .type_annotation(&point_type) +//! .parse::<HashMap<String, usize>>()?; //! //! let mut expected_map = HashMap::new(); //! expected_map.insert("x".to_string(), 1); @@ -135,207 +151,35 @@ //! # } //! ``` //! -//! You can also let Rust infer the appropriate Dhall type, using the [StaticType] trait. -//! -//! ```rust -//! # fn main() -> serde_dhall::de::Result<()> { -//! use serde::Deserialize; -//! use serde_dhall::StaticType; +//! # Controlling deserialization //! -//! #[derive(Debug, Deserialize, StaticType)] -//! struct Point { -//! x: u64, -//! y: u64, -//! } -//! -//! // Some Dhall data -//! let data = "{ x = 1, y = 1 + 1 }"; -//! -//! // Convert the Dhall string to a Point. -//! let point: Point = serde_dhall::from_str_auto_type(data)?; -//! assert_eq!(point.x, 1); -//! assert_eq!(point.y, 2); -//! -//! // Invalid data fails the type validation -//! let invalid_data = "{ x = 1, z = 0.3 }"; -//! assert!(serde_dhall::from_str_auto_type::<Point>(invalid_data).is_err()); -//! # Ok(()) -//! # } -//! ``` +//! If you need more control over the process of reading Dhall values, e.g. disabling +//! imports, see the [`Deserializer`] methods. //! +//! [`Deserializer`]: struct.Deserializer.html //! [dhall]: https://dhall-lang.org/ //! [serde]: https://docs.serde.rs/serde/ //! [serde::Deserialize]: https://docs.serde.rs/serde/trait.Deserialize.html -mod serde; +#[cfg(doctest)] +mod test_readme { + doc_comment::doctest!("../../README.md"); +} + +mod deserialize; +mod error; +mod options; mod static_type; +/// Dhall values +mod value; -#[doc(inline)] -pub use de::{from_str, from_str_auto_type, from_str_check_type}; #[doc(hidden)] pub use dhall_proc_macros::StaticType; -pub use static_type::StaticType; -#[doc(inline)] -pub use value::Value; - -// A Dhall value. -#[doc(hidden)] -pub mod value { - use dhall::syntax::Builtin; - use dhall::{Normalized, NormalizedExpr, Parsed}; - - use super::de::{Error, Result}; - - /// A Dhall value - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct Value(Normalized); - impl Value { - pub fn from_str(s: &str, ty: Option<&Value>) -> Result<Self> { - Value::from_str_using_dhall_error_type(s, ty).map_err(Error::Dhall) - } - fn from_str_using_dhall_error_type( - s: &str, - ty: Option<&Value>, - ) -> dhall::error::Result<Self> { - let resolved = Parsed::parse_str(s)?.resolve()?; - let typed = match ty { - None => resolved.typecheck()?, - Some(t) => resolved.typecheck_with(t.as_normalized())?, - }; - Ok(Value(typed.normalize())) - } - pub(crate) fn to_expr(&self) -> NormalizedExpr { - self.0.to_expr() - } - pub(crate) fn as_normalized(&self) -> &Normalized { - &self.0 - } - - pub(crate) fn make_builtin_type(b: Builtin) -> Self { - Value(Normalized::make_builtin_type(b)) - } - pub(crate) fn make_optional_type(t: Value) -> Self { - Value(Normalized::make_optional_type(t.0)) - } - pub(crate) fn make_list_type(t: Value) -> Self { - Value(Normalized::make_list_type(t.0)) - } - // Made public for the StaticType derive macro - #[doc(hidden)] - pub fn make_record_type( - kts: impl Iterator<Item = (String, Value)>, - ) -> Self { - Value(Normalized::make_record_type(kts.map(|(k, t)| (k, t.0)))) - } - #[doc(hidden)] - pub fn make_union_type( - kts: impl Iterator<Item = (String, Option<Value>)>, - ) -> Self { - Value(Normalized::make_union_type( - kts.map(|(k, t)| (k, t.map(|t| t.0))), - )) - } - } - - impl super::de::sealed::Sealed for Value {} - - impl super::de::Deserialize for Value { - fn from_dhall(v: &Value) -> Result<Self> { - Ok(v.clone()) - } - } -} - -/// Deserialize Dhall data to a Rust data structure. -pub mod de { - use super::StaticType; - use super::Value; - pub use error::{Error, Result}; - - mod error { - use dhall::error::Error as DhallError; - - pub type Result<T> = std::result::Result<T, Error>; - - #[derive(Debug)] - #[non_exhaustive] - pub enum Error { - Dhall(DhallError), - Deserialize(String), - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Error::Dhall(err) => write!(f, "{}", err), - Error::Deserialize(err) => write!(f, "{}", err), - } - } - } - - impl std::error::Error for Error {} - - impl serde::de::Error for Error { - fn custom<T>(msg: T) -> Self - where - T: std::fmt::Display, - { - Error::Deserialize(msg.to_string()) - } - } - } - - pub(crate) mod sealed { - pub trait Sealed {} - } - - /// A data structure that can be deserialized from a Dhall expression - /// - /// This is automatically implemented for any type that [serde][serde] - /// can deserialize. - /// - /// This trait cannot be implemented manually. - pub trait Deserialize: sealed::Sealed + Sized { - /// See [serde_dhall::from_str][crate::from_str] - fn from_dhall(v: &Value) -> Result<Self>; - } - - /// Deserialize an instance of type `T` from a string of Dhall text. - /// - /// This will recursively resolve all imports in the expression, and - /// typecheck it before deserialization. Relative imports will be resolved relative to the - /// provided file. More control over this process is not yet available - /// but will be in a coming version of this crate. - pub fn from_str<T>(s: &str) -> Result<T> - where - T: Deserialize, - { - T::from_dhall(&Value::from_str(s, None)?) - } - - /// Deserialize an instance of type `T` from a string of Dhall text, - /// additionally checking that it matches the supplied type. - /// - /// Like [from_str], but this additionally checks that - /// the type of the provided expression matches the supplied type. - pub fn from_str_check_type<T>(s: &str, ty: &Value) -> Result<T> - where - T: Deserialize, - { - T::from_dhall(&Value::from_str(s, Some(ty))?) - } - - /// Deserialize an instance of type `T` from a string of Dhall text, - /// additionally checking that it matches the type of `T`. - /// - /// Like [from_str], but this additionally checks that - /// the type of the provided expression matches the output type `T`. The [StaticType] trait - /// captures Rust types that are valid Dhall types. - pub fn from_str_auto_type<T>(s: &str) -> Result<T> - where - T: Deserialize + StaticType, - { - from_str_check_type(s, &<T as StaticType>::static_type()) - } -} +pub use deserialize::FromDhall; +pub(crate) use deserialize::Sealed; +pub(crate) use error::ErrorKind; +pub use error::{Error, Result}; +pub use options::{from_file, from_str, Deserializer}; +pub use static_type::StaticType; +pub use value::{SimpleType, Value}; diff --git a/serde_dhall/src/options.rs b/serde_dhall/src/options.rs new file mode 100644 index 0000000..06a4368 --- /dev/null +++ b/serde_dhall/src/options.rs @@ -0,0 +1,368 @@ +use std::path::{Path, PathBuf}; + +use dhall::Parsed; + +use crate::SimpleType; +use crate::{Error, ErrorKind, FromDhall, Result, StaticType, Value}; + +#[derive(Debug, Clone)] +enum Source<'a> { + Str(&'a str), + File(PathBuf), + // Url(&'a str), +} + +#[derive(Debug, Clone, Copy)] +pub struct NoAnnot; +#[derive(Debug, Clone, Copy)] +pub struct ManualAnnot<'ty>(&'ty SimpleType); +#[derive(Debug, Clone, Copy)] +pub struct StaticAnnot; + +pub trait HasAnnot<A> { + fn get_annot(a: &A) -> Option<SimpleType>; +} +impl<T> HasAnnot<NoAnnot> for T { + fn get_annot(_: &NoAnnot) -> Option<SimpleType> { + None + } +} +impl<'ty, T> HasAnnot<ManualAnnot<'ty>> for T { + fn get_annot(a: &ManualAnnot<'ty>) -> Option<SimpleType> { + Some(a.0.clone()) + } +} +impl<T: StaticType> HasAnnot<StaticAnnot> for T { + fn get_annot(_: &StaticAnnot) -> Option<SimpleType> { + Some(T::static_type()) + } +} + +/// Controls how a Dhall value is read. +/// +/// This builder exposes the ability to configure how a value is deserialized and what operations +/// are permitted during evaluation. +/// +/// Generally speaking, when using [`Deserializer`], you'll create it with [`from_str`] or [`from_file`], then +/// chain calls to methods to set each option, then call [`parse`]. This will give you a +/// [`Result<T>`] where `T` is a deserializable type of your choice. +/// +/// [`Deserializer`]: struct.Deserializer.html +/// [`from_str`]: fn.from_str.html +/// [`from_file`]: fn.from_file.html +/// [`parse`]: struct.Deserializer.html#method.parse +/// [`Result<T>`]: type.Result.html +/// +/// # Examples +/// +/// Reading from a file: +/// +/// ```no_run +/// # fn main() -> serde_dhall::Result<()> { +/// use serde_dhall::from_file; +/// +/// let data = from_file("foo.dhall").parse::<u64>()?; +/// # Ok(()) +/// # } +/// ``` +/// +/// Reading from a file and checking the value against a provided type: +/// +/// ```no_run +/// # fn main() -> serde_dhall::Result<()> { +/// use std::collections::HashMap; +/// use serde_dhall::{from_file, from_str}; +/// +/// let ty = from_str("{ x: Natural, y: Natural }").parse()?; +/// let data = from_file("foo.dhall") +/// .type_annotation(&ty) +/// .parse::<HashMap<String, usize>>()?; +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug, Clone)] +pub struct Deserializer<'a, A> { + source: Source<'a>, + annot: A, + allow_imports: bool, + // allow_remote_imports: bool, + // use_cache: bool, +} + +impl<'a> Deserializer<'a, NoAnnot> { + fn default_with_source(source: Source<'a>) -> Self { + Deserializer { + source, + annot: NoAnnot, + allow_imports: true, + // allow_remote_imports: true, + // use_cache: true, + } + } + fn from_str(s: &'a str) -> Self { + Self::default_with_source(Source::Str(s)) + } + fn from_file<P: AsRef<Path>>(path: P) -> Self { + Self::default_with_source(Source::File(path.as_ref().to_owned())) + } + // fn from_url(url: &'a str) -> Self { + // Self::default_with_source(Source::Url(url)) + // } + + /// Ensures that the parsed value matches the provided type. + /// + /// In many cases the Dhall type that corresponds to a Rust type can be inferred automatically. + /// See the [`StaticType`] trait and the [`static_type_annotation`] method for that. + /// + /// # Example + /// + /// ``` + /// # fn main() -> serde_dhall::Result<()> { + /// use std::collections::HashMap; + /// use serde::Deserialize; + /// use serde_dhall::{from_str, SimpleType}; + /// + /// // Parse a Dhall type + /// let type_str = "{ x: Natural, y: Natural }"; + /// let ty = from_str(type_str).parse::<SimpleType>()?; + /// + /// // Parse some Dhall data. + /// let data = "{ x = 1, y = 1 + 1 }"; + /// let point = from_str(data) + /// .type_annotation(&ty) + /// .parse::<HashMap<String, usize>>()?; + /// assert_eq!(point.get("y"), Some(&2)); + /// + /// // Invalid data fails the type validation; deserialization would have succeeded otherwise. + /// let invalid_data = "{ x = 1, z = 3 }"; + /// assert!( + /// from_str(invalid_data) + /// .type_annotation(&ty) + /// .parse::<HashMap<String, usize>>() + /// .is_err() + /// ); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`static_type_annotation`]: struct.Deserializer.html#method.static_type_annotation + /// [`StaticType`]: trait.StaticType.html + pub fn type_annotation<'ty>( + self, + ty: &'ty SimpleType, + ) -> Deserializer<'a, ManualAnnot<'ty>> { + Deserializer { + annot: ManualAnnot(ty), + source: self.source, + allow_imports: self.allow_imports, + } + } + + /// Ensures that the parsed value matches the type of `T`. + /// + /// `T` must implement the [`StaticType`] trait. If it doesn't, you can use [`type_annotation`] + /// to provide a type manually. + /// + /// # Example + /// + /// ``` + /// # fn main() -> serde_dhall::Result<()> { + /// use serde::Deserialize; + /// use serde_dhall::StaticType; + /// + /// #[derive(Deserialize, StaticType)] + /// struct Point { + /// x: u64, + /// y: Option<u64>, + /// } + /// + /// // Some Dhall data + /// let data = "{ x = 1, y = Some (1 + 1) }"; + /// + /// // Convert the Dhall string to a Point. + /// let point = serde_dhall::from_str(data) + /// .static_type_annotation() + /// .parse::<Point>()?; + /// assert_eq!(point.x, 1); + /// assert_eq!(point.y, Some(2)); + /// + /// // Invalid data fails the type validation; deserialization would have succeeded otherwise. + /// let invalid_data = "{ x = 1 }"; + /// assert!( + /// serde_dhall::from_str(invalid_data) + /// .static_type_annotation() + /// .parse::<Point>() + /// .is_err() + /// ); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`type_annotation`]: struct.Deserializer.html#method.type_annotation + /// [`StaticType`]: trait.StaticType.html + pub fn static_type_annotation(self) -> Deserializer<'a, StaticAnnot> { + Deserializer { + annot: StaticAnnot, + source: self.source, + allow_imports: self.allow_imports, + } + } +} + +impl<'a, A> Deserializer<'a, A> { + /// Sets whether to enable imports. + /// + /// By default, imports are enabled. + /// + /// # Example + /// + /// ``` + /// # fn main() -> serde_dhall::Result<()> { + /// use serde::Deserialize; + /// use serde_dhall::SimpleType; + /// + /// let data = "12 + ./other_file.dhall : Natural"; + /// assert!( + /// serde_dhall::from_str(data) + /// .imports(false) + /// .parse::<u64>() + /// .is_err() + /// ); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`static_type_annotation`]: struct.Deserializer.html#method.static_type_annotation + /// [`StaticType`]: trait.StaticType.html + pub fn imports(self, imports: bool) -> Self { + Deserializer { + allow_imports: imports, + ..self + } + } + + // /// TODO + // pub fn remote_imports(&mut self, imports: bool) -> &mut Self { + // self.allow_remote_imports = imports; + // if imports { + // self.allow_imports = true; + // } + // self + // } + + fn _parse<T>(&self) -> dhall::error::Result<Value> + where + T: HasAnnot<A>, + { + let parsed = match &self.source { + Source::Str(s) => Parsed::parse_str(s)?, + Source::File(p) => Parsed::parse_file(p.as_ref())?, + }; + let resolved = if self.allow_imports { + parsed.resolve()? + } else { + parsed.skip_resolve()? + }; + let typed = match &T::get_annot(&self.annot) { + None => resolved.typecheck()?, + Some(ty) => resolved.typecheck_with(ty.to_value().as_hir())?, + }; + Ok(Value::from_nir(typed.normalize().as_nir())) + } + + /// Parses the chosen dhall value with the options provided. + /// + /// If you enabled static annotations, `T` is required to implement [`StaticType`]. + /// + /// + /// # Example + /// + /// ``` + /// # fn main() -> serde_dhall::Result<()> { + /// let data = serde_dhall::from_str("6 * 7").parse::<u64>()?; + /// assert_eq!(data, 42); + /// # Ok(()) + /// # } + /// ``` + /// [`StaticType`]: trait.StaticType.html + pub fn parse<T>(&self) -> Result<T> + where + T: FromDhall + HasAnnot<A>, + { + let val = self + ._parse::<T>() + .map_err(ErrorKind::Dhall) + .map_err(Error)?; + T::from_dhall(&val) + } +} + +/// Deserialize a value from a string of Dhall text. +/// +/// This returns a [`Deserializer`] object. Call the [`parse`] method to get the deserialized +/// value, or use other [`Deserializer`] methods to control the deserialization process. +/// +/// # Example +/// +/// ```rust +/// # fn main() -> serde_dhall::Result<()> { +/// use serde::Deserialize; +/// +/// // We use serde's derive feature +/// #[derive(Deserialize)] +/// struct Point { +/// x: u64, +/// y: u64, +/// } +/// +/// // Some Dhall data +/// let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }"; +/// +/// // Parse the Dhall string as a Point. +/// let point: Point = serde_dhall::from_str(data).parse()?; +/// +/// assert_eq!(point.x, 1); +/// assert_eq!(point.y, 2); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`Deserializer`]: struct.Deserializer.html +/// [`parse`]: struct.Deserializer.html#method.parse +pub fn from_str(s: &str) -> Deserializer<'_, NoAnnot> { + Deserializer::from_str(s) +} + +/// Deserialize a value from a Dhall file. +/// +/// This returns a [`Deserializer`] object. Call the [`parse`] method to get the deserialized +/// value, or use other [`Deserializer`] methods to control the deserialization process. +/// +/// # Example +/// +/// ```no_run +/// # fn main() -> serde_dhall::Result<()> { +/// use serde::Deserialize; +/// +/// // We use serde's derive feature +/// #[derive(Deserialize)] +/// struct Point { +/// x: u64, +/// y: u64, +/// } +/// +/// // Parse the Dhall file as a Point. +/// let point: Point = serde_dhall::from_file("foo.dhall").parse()?; +/// # Ok(()) +/// # } +/// ``` +/// +/// [`Deserializer`]: struct.Deserializer.html +/// [`parse`]: struct.Deserializer.html#method.parse +pub fn from_file<'a, P: AsRef<Path>>(path: P) -> Deserializer<'a, NoAnnot> { + Deserializer::from_file(path) +} + +// pub fn from_url(url: &str) -> Deserializer<'_, NoAnnot> { +// Deserializer::from_url(url) +// } diff --git a/serde_dhall/src/serde.rs b/serde_dhall/src/serde.rs deleted file mode 100644 index 4fd7815..0000000 --- a/serde_dhall/src/serde.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::borrow::Cow; - -use serde::de::value::{ - MapAccessDeserializer, MapDeserializer, SeqDeserializer, -}; - -use dhall::syntax::{ExprKind, LitKind}; -use dhall::NormalizedExpr; - -use crate::de::{Deserialize, Error, Result}; -use crate::Value; - -impl<'a, T> crate::de::sealed::Sealed for T where T: serde::Deserialize<'a> {} - -impl<'a, T> Deserialize for T -where - T: serde::Deserialize<'a>, -{ - fn from_dhall(v: &Value) -> Result<Self> { - T::deserialize(Deserializer(Cow::Owned(v.to_expr()))) - } -} - -struct Deserializer<'a>(Cow<'a, NormalizedExpr>); - -impl<'de: 'a, 'a> serde::de::IntoDeserializer<'de, Error> for Deserializer<'a> { - type Deserializer = Deserializer<'a>; - fn into_deserializer(self) -> Self::Deserializer { - self - } -} - -impl<'de: 'a, 'a> serde::Deserializer<'de> for Deserializer<'a> { - type Error = Error; - - fn deserialize_any<V>(self, visitor: V) -> Result<V::Value> - where - V: serde::de::Visitor<'de>, - { - use std::convert::TryInto; - use ExprKind::*; - use LitKind::*; - let expr = self.0.as_ref(); - let not_serde_compatible = || { - Err(Error::Deserialize(format!( - "this cannot be deserialized into the serde data model: {}", - expr - ))) - }; - - match expr.kind() { - Lit(Bool(x)) => visitor.visit_bool(*x), - Lit(Natural(x)) => { - if let Ok(x64) = (*x).try_into() { - visitor.visit_u64(x64) - } else if let Ok(x32) = (*x).try_into() { - visitor.visit_u32(x32) - } else { - unimplemented!() - } - } - Lit(Integer(x)) => { - if let Ok(x64) = (*x).try_into() { - visitor.visit_i64(x64) - } else if let Ok(x32) = (*x).try_into() { - visitor.visit_i32(x32) - } else { - unimplemented!() - } - } - Lit(Double(x)) => visitor.visit_f64((*x).into()), - TextLit(x) => { - // Normal form ensures that the tail is empty. - assert!(x.tail().is_empty()); - visitor.visit_str(x.head()) - } - EmptyListLit(..) => { - visitor.visit_seq(SeqDeserializer::new(None::<()>.into_iter())) - } - NEListLit(xs) => visitor.visit_seq(SeqDeserializer::new( - xs.iter().map(|x| Deserializer(Cow::Borrowed(x))), - )), - SomeLit(x) => visitor.visit_some(Deserializer(Cow::Borrowed(x))), - App(f, x) => match f.kind() { - Builtin(dhall::syntax::Builtin::OptionalNone) => { - visitor.visit_none() - } - Field(y, name) => match y.kind() { - UnionType(..) => { - let name: String = name.into(); - visitor.visit_enum(MapAccessDeserializer::new( - MapDeserializer::new( - Some((name, Deserializer(Cow::Borrowed(x)))) - .into_iter(), - ), - )) - } - _ => not_serde_compatible(), - }, - _ => not_serde_compatible(), - }, - RecordLit(m) => visitor - .visit_map(MapDeserializer::new(m.iter().map(|(k, v)| { - (k.as_ref(), Deserializer(Cow::Borrowed(v))) - }))), - Field(y, name) => match y.kind() { - UnionType(..) => { - let name: String = name.into(); - visitor.visit_enum(MapAccessDeserializer::new( - MapDeserializer::new(Some((name, ())).into_iter()), - )) - } - _ => not_serde_compatible(), - }, - Const(..) | Var(..) | Lam(..) | Pi(..) | Let(..) | Annot(..) - | Assert(..) | Builtin(..) | BinOp(..) | BoolIf(..) - | RecordType(..) | UnionType(..) | Merge(..) | ToMap(..) - | Projection(..) | ProjectionByExpr(..) | Completion(..) - | Import(..) => not_serde_compatible(), - } - } - - fn deserialize_tuple<V>(self, _: usize, visitor: V) -> Result<V::Value> - where - V: serde::de::Visitor<'de>, - { - use ExprKind::*; - let expr = self.0.as_ref(); - - match expr.kind() { - // Blindly takes keys in sorted order. - RecordLit(m) => visitor.visit_seq(SeqDeserializer::new( - m.iter().map(|(_, v)| Deserializer(Cow::Borrowed(v))), - )), - _ => self.deserialize_any(visitor), - } - } - - serde::forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq - tuple_struct map struct enum identifier ignored_any - } -} diff --git a/serde_dhall/src/static_type.rs b/serde_dhall/src/static_type.rs index 1eb9150..26c70cd 100644 --- a/serde_dhall/src/static_type.rs +++ b/serde_dhall/src/static_type.rs @@ -1,27 +1,92 @@ -use dhall::syntax::Builtin; - -use crate::Value; +use crate::SimpleType; /// A Rust type that can be represented as a Dhall type. /// -/// A typical example is `Option<bool>`, -/// represented by the dhall expression `Optional Bool`. +/// A typical example is `Option<bool>`, represented by the Dhall expression `Optional Bool`. +/// +/// This trait can be automatically derived, and this is the recommended way of implementing it. +/// +/// Some Rust types cannot implement this trait, because there isn't a single Dhall type that +/// corresponds to them. For example, `HashMap<String, u64>` could correspond to multiple different +/// Dhall types, e.g. `{ foo: Natural, bar: Natural }` and `{ baz: Natural }`. +/// +/// # Example +/// +/// ```rust +/// # fn main() -> serde_dhall::Result<()> { +/// use serde_dhall::{SimpleType, StaticType}; +/// +/// #[derive(StaticType)] +/// struct Foo { +/// x: bool, +/// y: Vec<u64>, +/// } +/// +/// let ty: SimpleType = +/// serde_dhall::from_str("{ x: Bool, y: List Natural }").parse()?; +/// +/// assert_eq!(Foo::static_type(), ty); +/// # Ok(()) +/// # } +/// ``` +/// +/// # Type correspondence /// -/// This trait can and should be automatically derived. +/// The following Dhall types correspond to the following Rust types: /// -/// The representation needs to be independent of the value. -/// For this reason, something like `HashMap<String, bool>` cannot implement -/// [StaticType] because each different value would -/// have a different Dhall record type. +/// Dhall | Rust +/// -------|------ +/// `Bool` | `bool` +/// `Natural` | `u64`, `u32`, ... +/// `Integer` | `i64`, `i32`, ... +/// `Double` | `f64`, `f32`, ... +/// `Text` | `String` +/// `List T` | `Vec<T>` +/// `Optional T` | `Option<T>` +/// `{ x: T, y: U }` | structs +/// `{ _1: T, _2: U }` | `(T, U)`, structs +/// `{ x: T, y: T }` | `HashMap<String, T>`, structs +/// `< x: T \| y: U >` | enums +/// `T -> U` | unsupported +/// `Prelude.JSON.Type` | unsupported +/// `Prelude.Map.Type T U` | unsupported pub trait StaticType { - fn static_type() -> Value; + /// Return the Dhall type that represents this type. + /// + /// # Example + /// + /// ```rust + /// # fn main() -> serde_dhall::Result<()> { + /// use serde::Deserialize; + /// use serde_dhall::{SimpleType, StaticType}; + /// + /// // Using `derive(StaticType)` here would give it the type `{ _1: List Natural }`. + /// #[derive(Deserialize)] + /// #[serde(transparent)] + /// struct Foo(Vec<u64>); + /// + /// impl StaticType for Foo { + /// fn static_type() -> SimpleType { + /// SimpleType::List(Box::new(SimpleType::Natural)) + /// } + /// } + /// + /// let foo = serde_dhall::from_str("[ 1, 2 ]") + /// .static_type_annotation() + /// .parse::<Foo>()?; + /// + /// assert_eq!(foo.0, vec![1, 2]); + /// # Ok(()) + /// # } + /// ``` + fn static_type() -> SimpleType; } macro_rules! derive_builtin { - ($ty:ty, $builtin:ident) => { - impl StaticType for $ty { - fn static_type() -> Value { - Value::make_builtin_type(Builtin::$builtin) + ($rust_ty:ty, $dhall_ty:ident) => { + impl StaticType for $rust_ty { + fn static_type() -> SimpleType { + SimpleType::$dhall_ty } } }; @@ -43,13 +108,14 @@ where A: StaticType, B: StaticType, { - fn static_type() -> Value { - Value::make_record_type( + fn static_type() -> SimpleType { + SimpleType::Record( vec![ ("_1".to_owned(), A::static_type()), ("_2".to_owned(), B::static_type()), ] - .into_iter(), + .into_iter() + .collect(), ) } } @@ -59,13 +125,14 @@ where T: StaticType, E: StaticType, { - fn static_type() -> Value { - Value::make_union_type( + fn static_type() -> SimpleType { + SimpleType::Union( vec![ ("Ok".to_owned(), Some(T::static_type())), ("Err".to_owned(), Some(E::static_type())), ] - .into_iter(), + .into_iter() + .collect(), ) } } @@ -74,8 +141,8 @@ impl<T> StaticType for Option<T> where T: StaticType, { - fn static_type() -> Value { - Value::make_optional_type(T::static_type()) + fn static_type() -> SimpleType { + SimpleType::Optional(Box::new(T::static_type())) } } @@ -83,8 +150,8 @@ impl<T> StaticType for Vec<T> where T: StaticType, { - fn static_type() -> Value { - Value::make_list_type(T::static_type()) + fn static_type() -> SimpleType { + SimpleType::List(Box::new(T::static_type())) } } @@ -92,7 +159,7 @@ impl<'a, T> StaticType for &'a T where T: StaticType, { - fn static_type() -> Value { + fn static_type() -> SimpleType { T::static_type() } } diff --git a/serde_dhall/src/value.rs b/serde_dhall/src/value.rs new file mode 100644 index 0000000..d6631da --- /dev/null +++ b/serde_dhall/src/value.rs @@ -0,0 +1,287 @@ +use std::collections::{BTreeMap, HashMap}; + +use dhall::semantics::{Hir, HirKind, Nir, NirKind}; +use dhall::syntax::{Builtin, Expr, ExprKind, NumKind, Span}; + +use crate::{Error, ErrorKind, FromDhall, Result, Sealed}; + +#[doc(hidden)] +/// An arbitrary Dhall value. +#[derive(Debug, Clone)] +pub struct Value { + /// Invariant: in normal form + hir: Hir, + /// Cached conversions because they are annoying to construct from Hir. + /// At most one of them will be `Some`. + as_simple_val: Option<SimpleValue>, + as_simple_ty: Option<SimpleType>, +} + +/// A simple value of the kind that can be decoded with serde +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum SimpleValue { + Num(NumKind), + Text(String), + Optional(Option<Box<SimpleValue>>), + List(Vec<SimpleValue>), + Record(BTreeMap<String, SimpleValue>), + Union(String, Option<Box<SimpleValue>>), +} + +/// The type of a value that can be decoded by `serde_dhall`, e.g. `{ x: Bool, y: List Natural }`. +/// +/// A `SimpleType` is used when deserializing values to ensure they are of the expected type. +/// Rather than letting `serde` handle potential type mismatches, this uses the type-checking +/// capabilities of Dhall to catch errors early and cleanly indicate in the user's code where the +/// mismatch happened. +/// +/// You would typically not manipulate `SimpleType`s by hand but rather let Rust infer it for your +/// datatype by deriving the [`StaticType`] trait, and using +/// [`Deserializer::static_type_annotation`]. If you need to supply a `SimpleType` manually, you +/// can either deserialize it like any other Dhall value, or construct it manually. +/// +/// [`StaticType`]: trait.StaticType.html +/// [`Deserializer::static_type_annotation`]: options/struct.Deserializer.html#method.static_type_annotation +/// +/// # Examples +/// +/// ```rust +/// # fn main() -> serde_dhall::Result<()> { +/// use serde_dhall::{SimpleType, StaticType}; +/// +/// #[derive(StaticType)] +/// struct Foo { +/// x: bool, +/// y: Vec<u64>, +/// } +/// +/// let ty: SimpleType = +/// serde_dhall::from_str("{ x: Bool, y: List Natural }").parse()?; +/// +/// assert_eq!(Foo::static_type(), ty); +/// # Ok(()) +/// # } +/// ``` +/// +/// ```rust +/// # fn main() -> serde_dhall::Result<()> { +/// use std::collections::HashMap; +/// use serde_dhall::SimpleType; +/// +/// let ty: SimpleType = +/// serde_dhall::from_str("{ x: Natural, y: Natural }").parse()?; +/// +/// let mut map = HashMap::new(); +/// map.insert("x".to_string(), SimpleType::Natural); +/// map.insert("y".to_string(), SimpleType::Natural); +/// assert_eq!(ty, SimpleType::Record(map)); +/// # Ok(()) +/// # } +/// ``` +/// +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SimpleType { + /// Corresponds to the Dhall type `Bool` + Bool, + /// Corresponds to the Dhall type `Natural` + Natural, + /// Corresponds to the Dhall type `Integer` + Integer, + /// Corresponds to the Dhall type `Double` + Double, + /// Corresponds to the Dhall type `Text` + Text, + /// Corresponds to the Dhall type `Optional T` + Optional(Box<SimpleType>), + /// Corresponds to the Dhall type `List T` + List(Box<SimpleType>), + /// Corresponds to the Dhall type `{ x : T, y : U }` + Record(HashMap<String, SimpleType>), + /// Corresponds to the Dhall type `< x : T | y : U >` + Union(HashMap<String, Option<SimpleType>>), +} + +impl Value { + pub(crate) fn from_nir(x: &Nir) -> Self { + Value { + hir: x.to_hir_noenv(), + as_simple_val: SimpleValue::from_nir(x), + as_simple_ty: SimpleType::from_nir(x), + } + } + + pub(crate) fn as_hir(&self) -> &Hir { + &self.hir + } + + /// Converts a Value into a SimpleValue. + pub(crate) fn to_simple_value(&self) -> Option<SimpleValue> { + self.as_simple_val.clone() + } + + /// Converts a Value into a SimpleType. + pub(crate) fn to_simple_type(&self) -> Option<SimpleType> { + self.as_simple_ty.clone() + } + + /// Converts a value back to the corresponding AST expression. + pub(crate) fn to_expr(&self) -> Expr { + self.hir.to_expr(Default::default()) + } +} + +impl SimpleValue { + pub(crate) fn from_nir(nir: &Nir) -> Option<Self> { + Some(match nir.kind() { + NirKind::Num(lit) => SimpleValue::Num(lit.clone()), + NirKind::TextLit(x) => SimpleValue::Text( + x.as_text() + .expect("Normal form should ensure the text is a string"), + ), + NirKind::EmptyOptionalLit(_) => SimpleValue::Optional(None), + NirKind::NEOptionalLit(x) => { + SimpleValue::Optional(Some(Box::new(Self::from_nir(x)?))) + } + NirKind::EmptyListLit(_) => SimpleValue::List(vec![]), + NirKind::NEListLit(xs) => SimpleValue::List( + xs.iter().map(Self::from_nir).collect::<Option<_>>()?, + ), + NirKind::RecordLit(kvs) => SimpleValue::Record( + kvs.iter() + .map(|(k, v)| Some((k.into(), Self::from_nir(v)?))) + .collect::<Option<_>>()?, + ), + NirKind::UnionLit(field, x, _) => SimpleValue::Union( + field.into(), + Some(Box::new(Self::from_nir(x)?)), + ), + NirKind::UnionConstructor(field, ty) + if ty.get(field).map(|f| f.is_some()) == Some(false) => + { + SimpleValue::Union(field.into(), None) + } + _ => return None, + }) + } +} + +impl SimpleType { + pub(crate) fn from_nir(nir: &Nir) -> Option<Self> { + Some(match nir.kind() { + NirKind::BuiltinType(b) => match b { + Builtin::Bool => SimpleType::Bool, + Builtin::Natural => SimpleType::Natural, + Builtin::Integer => SimpleType::Integer, + Builtin::Double => SimpleType::Double, + Builtin::Text => SimpleType::Text, + _ => unreachable!(), + }, + NirKind::OptionalType(t) => { + SimpleType::Optional(Box::new(Self::from_nir(t)?)) + } + NirKind::ListType(t) => { + SimpleType::List(Box::new(Self::from_nir(t)?)) + } + NirKind::RecordType(kts) => SimpleType::Record( + kts.iter() + .map(|(k, v)| Some((k.into(), Self::from_nir(v)?))) + .collect::<Option<_>>()?, + ), + NirKind::UnionType(kts) => SimpleType::Union( + kts.iter() + .map(|(k, v)| { + Some(( + k.into(), + v.as_ref() + .map(|v| Ok(Self::from_nir(v)?)) + .transpose()?, + )) + }) + .collect::<Option<_>>()?, + ), + _ => return None, + }) + } + + pub(crate) fn to_value(&self) -> Value { + Value { + hir: self.to_hir(), + as_simple_val: None, + as_simple_ty: Some(self.clone()), + } + } + pub(crate) fn to_hir(&self) -> Hir { + let hir = |k| Hir::new(HirKind::Expr(k), Span::Artificial); + hir(match self { + SimpleType::Bool => ExprKind::Builtin(Builtin::Bool), + SimpleType::Natural => ExprKind::Builtin(Builtin::Natural), + SimpleType::Integer => ExprKind::Builtin(Builtin::Integer), + SimpleType::Double => ExprKind::Builtin(Builtin::Double), + SimpleType::Text => ExprKind::Builtin(Builtin::Text), + SimpleType::Optional(t) => ExprKind::App( + hir(ExprKind::Builtin(Builtin::Optional)), + t.to_hir(), + ), + SimpleType::List(t) => { + ExprKind::App(hir(ExprKind::Builtin(Builtin::List)), t.to_hir()) + } + SimpleType::Record(kts) => ExprKind::RecordType( + kts.iter() + .map(|(k, t)| (k.as_str().into(), t.to_hir())) + .collect(), + ), + SimpleType::Union(kts) => ExprKind::UnionType( + kts.iter() + .map(|(k, t)| { + (k.as_str().into(), t.as_ref().map(|t| t.to_hir())) + }) + .collect(), + ), + }) + } +} + +impl Sealed for Value {} +impl Sealed for SimpleValue {} +impl Sealed for SimpleType {} + +impl FromDhall for Value { + fn from_dhall(v: &Value) -> Result<Self> { + Ok(v.clone()) + } +} +impl FromDhall for SimpleValue { + fn from_dhall(v: &Value) -> Result<Self> { + v.to_simple_value().ok_or_else(|| { + Error(ErrorKind::Deserialize(format!( + "this cannot be deserialized into a simple type: {}", + v + ))) + }) + } +} +impl FromDhall for SimpleType { + fn from_dhall(v: &Value) -> Result<Self> { + v.to_simple_type().ok_or_else(|| { + Error(ErrorKind::Deserialize(format!( + "this cannot be deserialized into a simple type: {}", + v + ))) + }) + } +} + +impl Eq for Value {} +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + self.hir == other.hir + } +} +impl std::fmt::Display for Value { + fn fmt( + &self, + f: &mut std::fmt::Formatter, + ) -> std::result::Result<(), std::fmt::Error> { + self.to_expr().fmt(f) + } +} diff --git a/serde_dhall/tests/de.rs b/serde_dhall/tests/de.rs index 74912dd..a5c42fd 100644 --- a/serde_dhall/tests/de.rs +++ b/serde_dhall/tests/de.rs @@ -1,10 +1,10 @@ use serde::Deserialize; -use serde_dhall::{from_str, from_str_auto_type, StaticType}; +use serde_dhall::{from_str, FromDhall, StaticType}; #[test] fn test_de_typed() { - fn parse<T: serde_dhall::de::Deserialize + StaticType>(s: &str) -> T { - from_str_auto_type(s).unwrap() + fn parse<T: FromDhall + StaticType>(s: &str) -> T { + from_str(s).static_type_annotation().parse().unwrap() } assert_eq!(parse::<bool>("True"), true); @@ -51,12 +51,17 @@ fn test_de_typed() { Y(i64), } assert_eq!(parse::<Baz>("< X | Y: Integer >.X"), Baz::X); + + assert!(from_str("< X | Y: Integer >.Y") + .static_type_annotation() + .parse::<Baz>() + .is_err()); } #[test] fn test_de_untyped() { - fn parse<T: serde_dhall::de::Deserialize>(s: &str) -> T { - from_str(s).unwrap() + fn parse<T: FromDhall>(s: &str) -> T { + from_str(s).parse().unwrap() } // Test tuples on record of wrong type @@ -83,6 +88,17 @@ fn test_de_untyped() { expected_map ); + #[derive(Debug, PartialEq, Eq, Deserialize)] + struct Foo { + x: u64, + y: Option<u64>, + } + // Omit optional field + assert_eq!(parse::<Foo>("{ x = 1 }"), Foo { x: 1, y: None }); + // https://github.com/Nadrieril/dhall-rust/issues/155 - assert!(from_str::<bool>("List/length [True, 42]").is_err()); + assert!(from_str("List/length [True, 42]").parse::<bool>().is_err()); } + +// TODO: test various builder configurations +// In particular test cloning and reusing builder diff --git a/serde_dhall/tests/traits.rs b/serde_dhall/tests/traits.rs index 15a91ed..3c6fbfe 100644 --- a/serde_dhall/tests/traits.rs +++ b/serde_dhall/tests/traits.rs @@ -1,9 +1,9 @@ -use serde_dhall::{from_str, StaticType, Value}; +use serde_dhall::{from_str, SimpleType, StaticType}; #[test] fn test_static_type() { - fn parse(s: &str) -> Value { - from_str(s).unwrap() + fn parse(s: &str) -> SimpleType { + from_str(s).parse().unwrap() } assert_eq!(bool::static_type(), parse("Bool")); diff --git a/serde_dhall/tests/version_numbers.rs b/serde_dhall/tests/version_numbers.rs new file mode 100644 index 0000000..8307e47 --- /dev/null +++ b/serde_dhall/tests/version_numbers.rs @@ -0,0 +1,17 @@ +#[test] +fn test_readme_deps() { + version_sync::assert_markdown_deps_updated!("../README.md"); +} + +#[test] +fn test_html_root_url() { + version_sync::assert_html_root_url_updated!("src/lib.rs"); +} + +#[test] +fn test_readme_mentions_version() { + version_sync::assert_contains_regex!( + "../README.md", + "^#### \\[{version}\\]" + ); +} diff --git a/test.dhall b/test.dhall new file mode 100644 index 0000000..c91ac54 --- /dev/null +++ b/test.dhall @@ -0,0 +1 @@ +{ x = 0 } with x = (1 + 1) diff --git a/tests_buffer b/tests_buffer index 74e76bc..6f5aae5 100644 --- a/tests_buffer +++ b/tests_buffer @@ -7,6 +7,11 @@ x.({ a : Bool, b }) x.({ a }) x.{ a : Bool } s/QuotedVariable/VariableQuoted/ +From https://github.com/dhall-lang/dhall-lang/issues/280 : + "${ not_really_an_expression ;-) }" + ''${ not_an_expression ;-) }'' + {- {- -} 1 +{ x = 0 } with x = 1 + 1 import: failure/ |